MySQL 慢查询治理:从 slow log 到执行计划的三层排查
慢查询治理最常见的误区,是一上来就加索引。加完之后发现没变快,或者快了一阵又慢回去,于是继续加。真正有效的做法是先定位瓶颈类型,再决定动作。下面这套三层排查顺序,基本可以覆盖日常 80% 的场景。
一、第一层:用 slow log 做聚合,不做逐条分析
慢查询日志的价值不在单条 SQL,而在聚合后的分布。开启方式:
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
注意 long_query_time 建议先用 1 秒起步,不要一上来设 0.1,否则日志量会把磁盘和 IO 打满。log_queries_not_using_indexes 在小表多的实例上也会产生大量噪音,可以配合 min_examined_row_limit 限制。
拿到日志后不要肉眼翻,用 pt-query-digest 聚合:
pt-query-digest /var/log/mysql/slow.log > report.txt
重点看三个指标:
1)总耗时占比。排在前面的几条往往贡献了 60% 以上的总耗时,先解决它们。
2)执行次数。单次 3 秒但一天跑 1 次,和单次 30 毫秒但一天跑 10 万次,后者对系统的影响更大。
3)Rows_examined 与 Rows_sent 的比值。比值越大,说明扫了很多行才返回少量结果,通常是索引缺失或选择性差。
二、第二层:看执行计划,判断瓶颈类型
拿到可疑 SQL 后先 EXPLAIN,重点看这几列:
1)type。从好到坏大致是 system > const > eq_ref > ref > range > index > ALL。出现 ALL 代表全表扫描,通常是索引缺失;出现 index 代表扫了整个索引树,也不理想。
2)key。实际用到的索引。如果 possible_keys 有值但 key 为 NULL,说明优化器放弃了索引,常见原因是选择性太低或数据分布倾斜。
3)rows。预估扫描行数。和实际差很多时,说明统计信息过期。
4)Extra。出现 Using filesort 表示需要额外排序,Using temporary 表示用了临时表,这两个在数据量大时是主要开销来源。
MySQL 8.0.18 之后可以用 EXPLAIN ANALYZE,直接给出实际执行耗时和真实行数,比估算值可靠得多:
EXPLAIN ANALYZE SELECT ...;
如果发现 rows 估算与实际差一个数量级,先别急着改 SQL,跑一次 ANALYZE TABLE 更新统计信息,很多「突然变慢」的案例到这一步就解决了。
三、第三层:索引与写法,只在确认需要时动
到这一步再谈加索引,判断依据是两个:选择性和覆盖度。
1)选择性。选择性 = 不重复值 / 总行数。接近 1 的列适合做索引,接近 0 的列(比如性别、状态位)单独建索引收益很低。
2)联合索引的顺序。遵循最左前缀,把等值条件里选择性高的放前面,范围条件放最后。例如 WHERE tenant_id = ? AND status = ? AND created_at > ?,索引顺序建议 (tenant_id, status, created_at)。
3)覆盖索引。如果查询只取少数几列,把这几列带上可以避免回表。EXPLAIN 里 Extra 出现 Using index 就说明命中了覆盖索引。
几个容易被忽略的写法问题:
1)隐式类型转换。字符串字段用数字去比(WHERE phone = 13800138000)会让索引失效,必须写成字符串。
2)函数包裹列。WHERE DATE(created_at) = 2026-10-21 无法走索引,改成范围查询 WHERE created_at >= 2026-10-21 AND created_at < 2026-10-22。
3)前导通配符。LIKE %abc 无法走索引,只能靠全文索引或改写检索方式。
四、几个常见的判断误区
1)索引不是越多越好。每个索引都会带来写入放大,也会增加优化器的选择成本。一个高频更新表上堆十几个索引,写性能下降往往比查询收益更明显。
2)不要只看平均耗时。平均 50 毫秒但 P99 到 3 秒,说明存在偶发的执行计划劣化或锁等待,平均值会把它掩盖掉。
3)避免在从库上做分析。从库的数据分布、索引状态可能和主库不一致,得出的结论未必适用。
4)优化后要回归。改索引后观察一段时间,确认没有把别的 SQL 拖慢——优化器可能因此换了执行计划。
五、可以照抄的落地顺序
1. 打开 slow log,用 pt-query-digest 聚合出 Top 10
2. 按总耗时和执行次数排序,选出优先处理的三条
3. 逐条 EXPLAIN / EXPLAIN ANALYZE,判断是全表扫描、排序、临时表还是回表
4. 先试统计信息更新,再试索引调整,最后才改 SQL 结构
5. 每改一项,对比优化前后的 Rows_examined 与实际耗时
6. 把结论记录到文档,避免同样的问题反复排查
慢查询治理的难点从来不是「不知道加索引」,而是判断该不该加、加在哪、以及加完之后有没有副作用。把顺序固定下来,比记住更多技巧更有用。
- 点赞
- 收藏
- 关注作者
评论(0)