SQL查询优化指南

skillgohub.com 中文指南 | 中文版

SQL查询优化指南

每一位资深工程师都见过这样的场景:一个在开发环境几毫秒跑完的查询,到了生产环境要爬 7 秒,然后默默自问:是数据库的问题、索引的问题、还是 SQL 本身写得烂?通常答案是后者。一条写得糟糕的查询,能让数据库 CPU 满负荷,却只扛下它本该承载的不到五分之一流量。好消息是:这个技能可学、可量化。这是一份以检查清单驱动的实战指南,覆盖具体的技巧——执行计划分析、索引、谓词调优、连接(JOIN)策略、存储过程取舍——把慢语句变成快语句。

一条写坏的 SQL 能让数据库服务器卡上几分钟,而旁边 99 条干净的查询毫秒级跑完。这是软件世界里少见的、只要一行 SQL 就决定成败的时刻——一个缺失的过滤条件、一次隐形的全表扫描、一个包在索引列上的函数——决定仪表盘是半秒加载,还是让当值工程师半夜三点被叫醒。查询优化不是黑魔法,它是一个可复现的过程:读执行计划、找到扫描、重构语句,让优化器用上你原本已经付费建好的索引。

先读执行计划,永远不要靠猜

最常见的优化错误就是靠猜:有人加了索引,或重写了 JOIN,指望它有用。正确做法是问数据库它打算怎么执行这条查询。每个主流数据库引擎都暴露这一点——PostgreSQL 的 EXPLAIN、MySQL 的 EXPLAIN、SQL Server 的 SET SHOWPLAN_ALL。执行计划会告诉你引擎是在顺序扫描整张表,还是能用上索引,以及昂贵步骤在哪里。一条本该只碰一千行、却扫了一百万行的查询,一眼就能暴露。这个技能不在记计划符号,而在读出唯一的那个最差步骤、修掉它、再跑一遍计划确认改进。几乎每条慢查询最终都收敛到某一个占主导的步骤,而计划总会把它点出来。

Sql Query Optimization Guide - featured image

索引才是硬通货,不是查询

索引是数据库里杠杆最高的工具,而多数慢查询说穿了其实是索引问题。要分清覆盖索引(covering index,包含查询所需的每一列,让引擎不必回表)和过滤索引(filtered index,只覆盖匹配某个谓词的行)。对常见模式而言,在 WHERE 和 ORDER BY 的列上、按正确顺序建一个复合索引,胜过几个单列索引。顺序有讲究:先放等值过滤,再放范围过滤,最后放排序列。如果你对 B 树索引到底怎么工作还没有清晰的心智模型,我们的数据分析基础能打好让索引调优不再靠猜的底子。如果你语言还不熟、需要先能写出正确高效的语句再操心执行计划,可以看英文站database design basics尽快上手可读的查询。

Sql Query Optimization Guide comparison and review

改写一:用 Sargable 谓词干掉扫描

一个经典灾难长这样:events 表里有一千万行,查询在存日期的列上过滤,却把函数裹进了 WHERE。写成 WHERE DATE(order_date) = '2026-01-01' 会让引擎没法用 order_date 上的索引,因为它必须对每一行都算一遍函数。修法是让谓词可 sargable(可以简单理解成"能被索引搜索")——改写成范围:WHERE order_date >= '2026-01-01' AND order_date < '2026-01-02'。表扫描变成索引范围扫描,一条原来要四秒的查询变成四十毫秒返回。这一个重写就能修好一整族慢查询,因为在任何函数里裹列——UPPER、CAST、DATE——都有同样的效果。保持干净、索引友好的表达式,这种纪律在更广泛的 SQL 数据库实践里同样成立。

Sql Query Optimization Guide step by step guide

改写二:停掉 JOIN 和 OR 条件里的隐形全表扫描

JOIN 是隐藏扫描的第二个常见来源。如果连接列缺索引,或两表连接列的类型不一致,引擎就会退回到 hash join 或嵌套循环加表扫描。修法往往不是新 SQL,而是检查:JOIN 里用到的每个外键是否都有索引、两侧是否用同一数据类型。一个更隐蔽的近亲是 OR 条件,比如 WHERE col_a = 1 OR col_b = 1,它可能完全拒绝用索引,因为优化器无法保证结果集。把它改写成两个能分别用索引的分支做 UNION,常常能让引擎每个分支各用一条索引。这些是机械的、可验证的改进——你改语句、再查计划、看昂贵步骤的箭头指向更便宜的地方。

Sql Query Optimization Guide cost and pricing analysis

瘦身列类型,别用 SELECT *

两个习惯会悄无声息地拖累你写的每条查询。一个是拉 SELECT *,它读取每一列,强迫引擎读比查询所需更宽的行,常常还阻止了索引覆盖扫描。相反,应该只列出你真正用到的列。第二个习惯是用超大型的列类型:给存两个字符的编码字段用 CHAR(255),会让每个索引更宽、每次扫描更慢;合适的 VARCHAR 或小 INT 能让数据紧凑。日积月累,把每列撑宽等于给每张表和它的索引的磁盘与内存成本乘以一个系数,这是几乎没团队审计过的永久性税收。保持类型最小化,就像保持查询最小化一样,是一个低投入高回报的习惯。想更系统地掌握,可看data engineering basics里关于良好 schema 设计的部分。

Sql Query Optimization Guide tools and features overview

优化工具与剖析器对比

平台/工具核心功能价格
PostgreSQL EXPLAIN详细查询计划、ANALYZE 会实际执行、缓冲与成本节点免费,内置于 PostgreSQL
MySQL EXPLAIN ANALYZE逐行显示实际耗时、标注文件排序和扫描免费,内置 MySQL 8.0+
pgBadger日志分析器,生成慢查询与瓶颈的 HTML 报告免费开源
pg_stat_statements按不同查询文本聚合性能统计免费,PostgreSQL 扩展
Percona Toolkitpt-query-digest 跨引擎分析慢查询日志免费开源
Liquibase追踪索引与迁移变更的 schema 变更管理开源核心免费;Pro 增加高级功能

对任何生产数据库,最好的第一步是开启慢查询日志,用 pg_stat_statements 或其等价物复查最糟的几条。免费的 PostgreSQL 和 MySQL 工具几乎覆盖全部真正的调优工作;付费剖析器加的是便利,很少能抓到执行计划本身已经揭示的东西。想把这些优化思路用到更广的数据工作上,可以看看数据分析基础

当改写不再够用:重新设计 Schema

某些慢查询只改语句修不好,因为底层 schema 在跟你作对。对写入友好的规范化设计会惩罚复杂的读,这正是物化视图(materialized view)和专用汇总表存在的原因。当一个报表查询要 JOIN 八张表去算一个每日聚合,考虑建一张预计算的汇总表供报表直接读,用定时任务或触发器刷新。同样,给一个你总在读的值加一列反规范化字段,可以省掉一次昂贵的 JOIN。这些是架构决策而非查询微调,它们应当跟随负载画像走,而不是跟随抽象教条。读与写的平衡考量,也正是你一开始选择存储方案的核心,英文站里关于引擎选择的 SQL 数据库指南有更多框架化讨论。

建立可复现的优化工作流,而不是一次性修补

目标不是修好本周这条慢查询,而是建一套能在用户之前发现它们的流程。开启慢查询日志,安排每周复查最糟的语句,维护一份覆盖大多数问题的四种改写的迷你操作手册:sargable 谓词、被索引的 JOIN 键、拆开的 OR 条件、具名列。部署变更之前,把旧计划和新计划并排抓下来,让改进可见。如果数据库又大又慢,把同样纪律化的诊断扩展到整个集群里排最前的查询,把每一条都当作一次小型调查。这种前后都要测量的习惯,正是区分"被动救火"团队和"平稳运行"团队的地方,也是 DevOps 工具把同样可衡量思维应用到整个系统的依据。想把你自己的技术成长也纳入可衡量的节奏,可看职业规划建议里关于持续学习的安排。

常见问题

是不是给每一列都加索引就能解决慢查询?

不是。每条索引都会拖慢插入、更新、删除,并占用磁盘和内存。只给你的工作负载真正会过滤、连接或排序的列加索引,并优先用匹配查询过滤顺序的复合索引,而不是一堆单列索引。

为什么把列包在函数里会让查询变慢?

因为它让谓词不可 sargable。如果必须先对每一行算函数,引擎就用不上原始列上的 B 树索引。把条件改写成基于原始列的范围,索引就又变得可用了。

找慢查询最有用的一件工具是什么?

慢查询日志配合 pg_stat_statements(PostgreSQL)或其各引擎的等价物。它精确聚合出哪些不同的查询文本最贵,让你先处理影响最大的几条而不是乱猜。再用 EXPLAIN 看执行计划。

缺索引和查询结构差,哪个更常见是元凶?

大多数真实的慢查询是索引问题——要么正确的索引不存在,要么查询用不上现有的索引,要么谓词不可 sargable。一旦索引状况健康了,真正的查询设计不当才成为下一个值得调查的瓶颈。

什么时候该停止调查询、改为重设计 schema?

当查询本身已经写得不错、也建了合适的索引,但读模式在根本上就很重时——比如要实时跨很多表算一个聚合。这时一张预计算的汇总表或物化视图能从源头消除成本,而不是在语句层面死磕。

这些技巧在 MySQL、PostgreSQL、SQL Server 上效果一样吗?

原则是通用的——sargable、复合索引顺序、JOIN 键索引、计划优先分析,各引擎都适用。具体的计划语法和某些索引选项会有差异,但"读计划、修主导步骤、重新测量"的工作流在任何跑 SQL 的地方都完全一样。

📌 Pinterest 🐦 Twitter 📘 Facebook