应优先筛选「稳定变慢」的SQL:用mysqldumpslow取前10后,结合时间窗口比对Count与Avg_time,过滤监控探针语句,重点关注Rows_examined显著升高而Rows_sent变化不大的查询。

怎么看慢查询日志里哪条SQL真拖了后腿
不是所有被记录的 slow_query_log 条目都值得优化——有些是偶发大结果集,有些是凌晨定时任务,还有些压根是监控探针发的无效请求。关键得先筛出「稳定变慢」的那批。
实操建议:
- 用
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log按平均执行时间倒序取前10,但别信这个结果直接开干;要结合时间窗口比对,比如对比上周同一小时段的Count和Avg_time - 过滤掉带
/* mysql-monitor */或SELECT SLEEP的行,这类是探测语句,不反映业务压力 - 重点关注
Rows_examined显著升高但Rows_sent变化不大的 SQL——说明索引失效或扫描范围扩大了
为什么用 pt-query-digest 做基线对比比直接看日志强
原生日志没有归一化能力,SELECT * FROM user WHERE id = 1 和 SELECT * FROM user WHERE id = 12345 被当成两条不同语句,根本没法看出模式变化。而基线对比必须基于抽象后的指纹(fingerprint)。
实操建议:
- 用
pt-query-digest --filter '$event->{fingerprint} =~ m/^SELECT.*FROM orders/i' /var/log/mysql/slow.log.20240401 > baseline.txt提前存好基线报告 - 新日志跑同样命令时加
--compare-baseline baseline.txt,它会自动标出Query_time_delta和Rows_examined_delta异常增长的指纹 - 注意
--limit 100%默认只分析前1000行,线上日志量大时务必显式设成--limit 1000000,否则漏掉低频但高耗时的语句
EXPLAIN 结果里哪些字段变动意味着真实退化
type 从 ref 降到 ALL、key 从实际索引名变成 NULL、rows 预估值翻倍——这些才是硬指标。但要注意:MySQL 8.0+ 的 EXPLAIN FORMAT=JSON 里 rows_examined_per_scan 才是真实扫描行数,比旧版 rows 更准。
实操建议:
- 别只看单次
EXPLAIN,用SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY avg_timer_wait DESC LIMIT 5查全局最耗时指纹,再对它EXPLAIN - 如果
Extra出现Using filesort或Using temporary,且和上次基线对比中该字段首次出现,基本可判定排序/分组逻辑触发了磁盘临时表 - 检查
table字段是否为derived或materialized——子查询物化在新版 MySQL 中可能因统计信息陈旧导致计划错误
统计信息过期怎么快速验证是不是罪魁祸首
MySQL 的查询优化器极度依赖 INFORMATION_SCHEMA.STATISTICS 和 mysql.innodb_table_stats,但 ANALYZE TABLE 不是全自动的,尤其在大批量写入后没触发重采样时,cardinality 就会严重失真。
实操建议:
- 查当前统计信息更新时间:
SELECT table_name, update_time FROM information_schema.tables WHERE table_schema = 'your_db' AND table_name = 'orders' - 对比
SHOW INDEX FROM orders里的Cardinality和实际行数(SELECT COUNT(*) FROM orders),若相差超5倍,大概率需要ANALYZE TABLE orders - 避免在高峰期执行
ANALYZE TABLE,它会锁表(InnoDB 行级锁但会阻塞 DDL),建议用innodb_stats_persistent=ON+ 定时UPDATE mysql.innodb_table_stats替代
真正难的不是发现某条 SQL 变慢,而是确认它是孤立现象还是底层统计、索引、数据分布发生系统性偏移。基线对比只是起点,pt-query-digest 输出的 Query_time_delta 数值背后,往往连着三个以上相互影响的变量。











