人人都会AI编程

13.1 查询优化通用思路

更新时间:2026-07-10

SQL 优化听起来像是 DBA 的专属技能,但实际工作中,大多数性能问题都源于开发者写出的 SQL 不够高效。掌握一套通用的排查和优化流程,远比背一堆零散的技巧更重要。这一节不堆砌命令,而是帮你建立从“发现问题”到“验证效果”的完整闭环。

13.1.1 优化之前的自我提问

并不是所有慢 SQL 都值得优化。真正陷入误区的是:花半天时间把一个每天只跑一次、耗时 2 秒的报表 SQL 优化到 0.5 秒,却对线上每分钟执行几万次、平均耗时 100 毫秒的查询视而不见。开始优化前,先问自己几个问题:

  • 频率多高? 高并发下,哪怕 10 毫秒的差异也会被放大成巨大的系统负载。
  • 影响多大? 这条 SQL 是否阻塞了核心业务流程,或导致连接池打满?
  • 能否绕开? 有些查询是否可以通过缓存、异步化、产品设计上的妥协来彻底避免?

把精力放在高频、高消耗、核心链路的 SQL 上,是优化工作的第一要务。

13.1.2 第一步:精准定位慢查询

慢查询不会自己跳出来,需要主动暴露。MySQL 提供了慢查询日志,这是定位问题的起点。

1 确保慢查询日志已开启

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.1;          -- 阈值设为 100 毫秒
SET GLOBAL log_queries_not_using_indexes = ON; -- 记录未使用索引的查询

线上环境通常配置为记录超过一定阈值(如 200 毫秒)的 SQL,阈值太小会刷爆磁盘,太大则会漏掉瓶颈。

2 分析慢查询日志

原生日志格式可读性一般,通常配合工具使用:

  • mysqldumpslow:MySQL 自带的日志分析命令,可以按耗时、扫描行数、出现次数排序。
  • pt-query-digest:Percona Toolkit 中的明星工具,能生成详细的报告,按查询指纹聚合,一眼看出哪类 SQL 最拖后腿。

如果你使用了云 RDS,控制台一般会直接展示慢 SQL 统计,也支持下载详细日志。

3 实时抓取运行中的慢查询

有时慢查询是偶发的,等你去看日志已经晚了。可以在线抓取:

-- 查看当前正在执行且耗时较长的线程
SELECT * FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 5;

通过 MySQL 8.0 的 sys.session 视图也能看到类似信息,甚至可以直接看到 SQL 执行进度(progress 列)。

13.1.3 第二步:用 EXPLAIN 看清执行计划

拿到一条慢 SQL 之后,下一个问题就是:“它到底是怎么执行的?”

EXPLAIN 是回答这个问题的核心工具。它会展示 MySQL 优化器为这条查询选择的执行路径,包括表访问顺序、使用的索引、扫描行数预估等。

EXPLAIN SELECT * FROM orders WHERE user_id = 1002 ORDER BY create_time DESC;

对于 DELETE、UPDATE、INSERT ... SELECT 等非查询语句,也可以通过 EXPLAIN 查看执行计划。MySQL 8.0 还支持 EXPLAIN ANALYZE,可以直接运行查询并返回每一步的实际耗时和行数,比单纯的预估更可靠。

解读 EXPLAIN 时,不要试图一次性记住所有字段,重点关注几个“信号灯”:

  • type:连接类型/访问方法。从优到劣大致是:consteq_refrefrangeindexALL。出现 ALL(全表扫描)就要高度警惕,index(全索引扫描)通常也不够理想。
  • key:实际选择的索引。如果这里是 NULL 而业务上应该有索引可用,说明索引设计或写法出了问题。
  • rows:优化器预估需要检查的行数。这是相对数,用于估算工作量,但可以和实际返回行数对比,看是否出现严重偏差。
  • Extra:包含重要的补充信息。
  • Using index:表示使用覆盖索引,不需要回表,是好信号。
  • Using where:表示在存储引擎返回行后进行了条件过滤,如果配合 rows 很大,可能说明索引不精确。
  • Using filesort:需要额外排序,如果排序列上没有索引,要注意。
  • Using temporary:需要临时表,通常在 GROUP BY、DISTINCT 或 UNION 时出现,可能是隐患。

简单判断法:如果 EXPLAIN 显示 type 是 ALL 或 index,且 rows 特别大(几十万、上百万),这条 SQL 几乎一定快不起来。优化方向就是想办法让 key 列出现合适的索引,让 type 提升为 ref 或 range,再让 Extra 尽量出现 Using index。

13.1.4 第三步:深入诊断执行细节

如果 EXPLAIN 已经告诉你查询走了索引,但实际仍然很慢,就需要进入更细粒度的诊断。

1 SHOW PROFILE(即将弃用但依然可用)

可以分析一条 SQL 在各阶段的耗时分布(打开表、关联、排序、发送数据等)。虽然官方推荐用 Performance Schema 替代,但在开发环境快速排查仍有价值。

SET profiling = 1;
SELECT ...;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

2 OPTIMIZER_TRACE(优化器追踪)

当怀疑优化器选错了索引时,OPTIMIZER_TRACE 是最好的诊断工具。它会输出优化器决策的全过程:评估了哪些执行计划,每个计划的预估成本是多少,为什么选择其中一个而放弃另一个。

SET optimizer_trace = 'enabled=on';
SELECT * FROM t WHERE ...;
SELECT * FROM information_schema.optimizer_trace\G

输出内容是 JSON 格式,可以清晰看到“considered_execution_plans”中不同索引的成本比较。有时明明有更好的索引,优化器却选了另一个,通常是因为索引统计信息不准确。此时一条 ANALYZE TABLE 可能就解决问题了。

3 Performance Schema 和 sys 库

MySQL 8.0 下,sys 库提供了很多直观的视图,比如 sys.schema_unused_indexes 可找出未使用的冗余索引,sys.statements_with_full_table_scans 可找出全表扫描的语句。虽然无法替代 EXPLAIN,但可以帮你从全局视角发现系统性风险。

13.1.5 第四步:制定并实施优化方案

经过前三步,问题的具体原因通常已经浮出水面。优化方向无外乎以下几种:

1 索引优化(最常见、最有效)

检查是否缺失必要的索引,或者索引列的顺序不对。是否可以利用覆盖索引避免回表?联合索引是否遵循最左前缀?是否存在索引失效的情况(如隐式类型转换、对列进行函数操作)?索引相关细节会在第 12 章展开,但思路是:用最小的索引代价,让查询的 type 提升到 ref/range,Extra 中消除 filesort 和 temporary。

2 SQL 改写

有时 SQL 本身就写得很“重”。常见的优化点:

  • 避免 SELECT \*:只取需要的列,既能减少数据传输,也更容易利用覆盖索引。
  • 分页优化:深分页(LIMIT 100000, 20)时要考虑基于索引的延迟关联或标记位分页,而不是让数据库扔掉前 10 万行。
  • 子查询转连接:有些子查询在优化器中会被自动转换,但显式写成 JOIN 往往更可控,性能更稳定。
  • 用 UNION ALL 代替 UNION:如果不需要去重,UNION ALL 不会产生临时表排序的开销。
  • 避免在 WHERE 子句中对列进行运算:如 WHERE YEAR(create_time) = 2024 会导致索引失效,应写成 WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'

3 架构与设计调整

如果索引和 SQL 本身已经没有明显问题,可能需要往更上层考虑:表是否可以垂直拆分,把大字段移出主表?是否可以用缓存(Redis)扛住热点读请求?读写分离是否能分担查询压力?这些属于架构范畴,但对某些极端查询,可能是唯一的解决思路。

13.1.6 第五步:验证优化效果并形成闭环

优化不是“感觉快了”就行,必须用数据说话。在相同的数据量和并发条件下,对比优化前后:

  • EXPLAIN 中的 type、key、rows、Extra 变化。
  • SQL 实际执行时间(可以用 SELECT BENCHMARK() 或客户端计时)。
  • 对系统整体负载(CPU、IO、连接数)的改善。

如果优化有效,记得把经验和修改固化下来:更新代码仓库中的 SQL 或 ORM 查询,必要时补充注释说明为什么用这个索引、为什么这样写。如果优化无效,也别怕退回到原方案,切忌为了优化而优化,以牺牲可读性或逻辑正确性为代价。

最后,查询优化是一项需要反复练习的技能,但它本质上是“观察—诊断—修复—验证”的科学流程,而非玄学。用好慢查询日志,看懂 EXPLAIN,敢于用 OPTIMIZER_TRACE 深挖,你就能解决 90% 以上的日常 SQL 性能问题。