真正危险的SQL是扫描行数远大于返回行数的语句,如Rows_examined:482312而Rows_sent:3,比值超100倍,极可能因全表扫描或索引未覆盖导致;需用pt-query-digest筛选高扫描量SQL并结合EXPLAIN验证索引使用情况。

直接看 Rows_examined 和 Rows_sent 的比值
慢日志里真正危险的不是执行时间长的 SQL,而是扫描行数远大于返回行数的语句。比如 Rows_examined: 482312 但 Rows_sent: 3,比值超 100 倍——这说明 MySQL 扫了几十万行才凑出 3 行结果,极大概率是全表扫描或索引没覆盖查询条件。
这类语句在数据量增长后会雪崩,但单次执行可能不到 100ms,容易被 pt-query-digest 默认按响应时间排序时漏掉。
- 用
pt-query-digest --filter '$event->{Rows_examined} > 10000'主动筛高扫描量 SQL - 重点关注报告中
Rows_examined/Rows_sent比值异常高的条目 - 如果
Rows_examined接近表总行数(如 50 万行的表扫了 49 万),基本可断定缺有效索引
别只信 slow_query_log,必须跑 EXPLAIN 验证
慢日志本身不告诉你缺什么索引,它只记录“慢”,真正暴露缺失索引的是执行计划。拿到可疑 SQL 后,一定要在测试库上执行 EXPLAIN FORMAT=TRADITIONAL,盯紧三项:
-
type是ALL或index:全表扫描或全索引扫描,危险信号 -
key是NULL:完全没走索引;即使非 NULL,也要看是否用了你预期的索引 -
Extra含Using filesort或Using temporary:ORDER BY/GROUP BY 没命中索引,额外排序或临时表开销大
注意:建完索引后必须重跑 EXPLAIN 确认 key 字段已非 NULL、rows 显著下降,否则可能是统计信息过期(用 ANALYZE TABLE 更新)或隐式类型转换导致索引失效。
警惕 log_queries_not_using_indexes 的误用陷阱
这个参数不能长期开着。它会把所有没走索引的查询都记下来,包括 SELECT * FROM config WHERE id = 1 这种秒级小表查询。生产环境一开,日志几小时内就能涨到 GB 级,磁盘爆满、I/O 拉高、MySQL 写日志卡顿全跟着来。
真正该用的是 min_examined_row_limit:只记录扫描行数超阈值的语句。设为 1000,那 Rows_examined: 620016 的慢查才会被收录,既过滤噪音,又精准捕获“全表扫大表”这类典型索引缺失场景。
- 推荐配置:
log_queries_not_using_indexes = OFF(默认就是关,别手抖开) -
min_examined_row_limit = 1000(中小表可设 500,大表可调至 5000) - 临时排查索引覆盖问题时,才短时间开
log_queries_not_using_indexes,并配合long_query_time = 0一起用
死锁日志里 GEN_CLUST_INDEX 是索引缺失的强信号
死锁日志不直接说“缺索引”,但会暴露关键线索。重点看 WAITING FOR THIS LOCK TO BE GRANTED: 下面的 index 字段:
- 如果显示
PRIMARY或某个二级索引名,说明走了索引 - 如果显示
GEN_CLUST_INDEX(隐式聚簇索引),基本等于没走有效索引,正在做全表扫描级加锁 - 再结合
lock_mode X locks rec but not gap waiting出现在两个事务对同一张表的同一范围等待,且EXPLAIN显示type=ALL,基本可锁定是索引设计没覆盖查询条件或 SELECT 列
特别注意 SELECT ... FOR UPDATE 类语句:没走索引 → 锁整张表或大范围主键;用函数包裹索引列(如 WHERE DATE(created_at) = '2024-01-01')→ 索引失效;隐式类型转换(如 WHERE user_id = '123' 对比 INT 字段)→ 优化器放弃索引下推。



















