SQL 查询优化的常用模式:避免 N+1、合理分页与去重
SQL 查询优化的常用模式避免 N1、合理分页与去重一、识别 N1 查询问题N1 查询是 ORM 使用中最常见、影响最大的性能问题。场景:你需要查询 10 个用户及其各自的订单数量。代码可能是这样:// 第 1 个查询: 获取 10 个用户 const users await db.query(SELECT * FROM users LIMIT 10); // N 个查询对每个用户再查一次他们的订单 for (const user of users) { user.orders await db.query( SELECT * FROM orders WHERE user_id ?, [user.id] ); }总共执行了 11 次查询 (1 10)。如果有 100 个用户就是 101 次查询。如果每次查询耗时 5ms通常不会注意到但如果数据库部署在云端每次查询的网络往返 (RTT) 可能是 50ms101 次查询就是 5 秒——用户体验从「瞬间加载」变成了「明显卡顿」。这种性能瓶颈的核心在于查询结构的耗时分布。在 N1 查询模式下系统需要执行 1 次获取主记录的查询耗时约 5ms以及 N 次逐条获取关联记录的查询耗时约 N × 50ms导致总耗时随数据量线性增长5 50N ms。相比之下批量查询优化只需 1 次获取主记录5ms和 1 次批量获取所有关联记录耗时约 50ms总耗时固定为 55ms完全不受用户数量影响。解决方案:方案一JOIN 或子查询。用一条 SQL 完成所有查询:SELECT u.*, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id o.user_id GROUP BY u.id LIMIT 10。这样 1 次查询完成原来 1N 次查询的工作。方案二IN 批量查询。先查询所有用户的 ID 列表再用WHERE user_id IN (1,2,3,...,10)一次批量查询所有用户的订单在应用层按 user_id 分组。方案三ORM 的 eager loading。大多数 ORM 支持预加载关联数据。Prisma 中的include,Sequelize 中的include,ActiveRecord 中的includes——在查询主记录时就告诉 ORM「顺便把关联数据一起查了」。二、深分页的性能问题分页查询是独立产品中最常见的列表功能。用LIMIT 20 OFFSET 10000来获取第 10001 到 10020 条记录——这种「深分页」在某些数据库中是性能杀手,因为它需要扫描前 10000 条记录然后丢弃,只返回最后 20 条。解决方案:方案一:游标分页(Cursor-based Pagination)。用WHERE id last_id ORDER BY id LIMIT 20替代OFFSET。第一次查询返回前 20 条,记录最后一条的 ID,下次查询WHERE id 20 LIMIT 20。这种方式「只扫描需要的记录」,性能与页码无关。缺点:不能跳页(如直接从第 1 页跳到第 10 页)。方案二:延迟关联(Deferred Join)。先查 ID(索引扫描,快),再根据 ID 查完整数据。SELECT * FROM posts WHERE id IN (SELECT id FROM posts ORDER BY id LIMIT 20 OFFSET 10000)。内层查询只扫描索引,速度远快于全表扫描;外层查询用 IN 批量获取 20 条完整记录。方案三:对于小型数据量( 10 万条),OFFSET 分页完全够用。深分页优化只在数据量达到一定规模(百万级以上)时才需要。不要过早优化。三、重复数据的处理数据去重是独立产品中常见的数据质量需求。方案一:DISTINCT。最简单的去重方式:SELECT DISTINCT category FROM posts。DISTINCT 适合「对单列去重」或「取唯一组合」,但 DISTINCT 要求返回的所有字段的组合唯一。方案二:GROUP BY 聚合函数。与 DISTINCT 类似,但可以在去重的同时做聚合:SELECT category, COUNT(*) FROM posts GROUP BY category。方案三:ROW_NUMBER() 窗口函数。当需要「保留每组中的一条记录」时,使用ROW_NUMBER():为每个分组中的记录编号,取出每组编号为 1 的记录。WITH ranked AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY created_at DESC ) as rn FROM user_logs ) SELECT * FROM ranked WHERE rn 1; 这样能获取每个用户的最新一条日志,且一次查询完成,不需要循环遍历。 ## 四、查询计划分析与验证 优化 SQL 查询后,如何验证「优化确实有效」最重要的工具还是 EXPLAIN(或 EXPLAIN ANALYZE)。 优化前运行 EXPLAIN ANALYZE,记录扫描行数和实际执行时间。优化后再次运行,对比两个指标的变化。不要只凭「感觉」判断优化效果——用数据说话。具体验证流程如下 1. 发现慢查询后先通过 EXPLAIN ANALYZE 记录优化前的扫描行数和执行时间 2. 执行优化操作比如用 JOIN 替代 N1 查询、用游标替代 OFFSET 深分页等 3. 再次运行 EXPLAIN ANALYZE对比优化前后的扫描行数和执行时间 4. 若扫描行数显著减少比如从百万级降到几十行说明优化有效可上线若扫描行数无明显变化说明优化方向有误需继续分析调整。 ## 五、总结 SQL 查询优化的常用模式,核心是三个「避免」:**避免循环中的逐条查询(N1)→ 用 JOIN 或批量 IN 替代**;**避免扫描大量不需要的记录(OFFSET 深分页)→ 用游标或延迟关联**;**避免应用层处理重复数据(逐条过滤)→ 用窗口函数在数据库层完成**。 对于独立产品:在查询数据量不大( 10 万条)的阶段,用 ORM 的 eager loading(预加载)解决 N1 问题;数据增长到百万级后,逐步用游标分页和延迟关联优化;去重用窗口函数(数据库层)比应用层遍历高效得多。 SQL 优化的效果,用 EXPLAIN 前后的扫描行数变化来验证。扫描行数减少 90% 的优化,是值得做的;扫描行数几乎不变的优化,可能是方向上弄错了。

相关新闻

Intern-S2-Preview-397B API完全指南:从本地部署到官方服务调用

Intern-S2-Preview-397B API完全指南:从本地部署到官方服务调用

Intern-S2-Preview-397B API完全指南:从本地部署到官方服务调用 【免费下载链接】Intern-S2-Preview-397B 项目地址: https://ai.gitcode.com/InternLM/Intern-S2-Preview-397B 想要掌握Intern-S2-Preview-397B这一强大的3970亿参数多模态AI模型的API调用方…

2026/10/22 22:44:21 阅读更多 →
Devstral-Small-2-24B-Instruct-2512-5bit社区贡献指南:如何参与MLX生态建设

Devstral-Small-2-24B-Instruct-2512-5bit社区贡献指南:如何参与MLX生态建设

Devstral-Small-2-24B-Instruct-2512-5bit社区贡献指南:如何参与MLX生态建设 【免费下载链接】Devstral-Small-2-24B-Instruct-2512-5bit 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/Devstral-Small-2-24B-Instruct-2512-5bit 欢迎来到MLX…

2026/10/22 4:40:03 阅读更多 →
dbKoda架构解析:Electron + React构建现代桌面应用的完整技术栈

dbKoda架构解析:Electron + React构建现代桌面应用的完整技术栈

dbKoda架构解析:Electron React构建现代桌面应用的完整技术栈 【免费下载链接】dbkoda State of the art MongoDB IDE 项目地址: https://gitcode.com/gh_mirrors/db/dbkoda dbKoda作为一款面向MongoDB的现代化IDE,采用Electron与React技术栈构建…

2026/10/22 9:48:56 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/22 21:33:42 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 19:06:46 阅读更多 →