真正危险的sql是rows_examined远大于rows_sent的语句,如比值超100倍,表明存在全表扫描或索引未覆盖,需通过pt-query-digest过滤rows_examined>10000并结合explain验证索引使用情况。

直接看 Rows_examined 和 Rows_sent 的比值
慢日志里真正危险的不是 Query_time 长的 SQL,而是扫描行数远大于返回行数的语句。比如 Rows_examined: 482312 但 Rows_sent: 3,比值超 100 倍——说明 MySQL 扫了几十万行才凑出 3 行结果,极大概率是全表扫描或索引未覆盖查询条件。
这类语句在数据量增长后会雪崩,但单次执行可能不到 100ms,容易被 pt-query-digest 默认按响应时间排序时漏掉。
- 如果
Rows_examined接近表总行数(如 50 万行的表扫了 49 万),基本可断定缺有效索引 -
min_examined_row_limit = 1000是更精准的捕获方式,比log_queries_not_using_indexes更少噪音 - 别只依赖
mysqldumpslow——它不输出Rows_examined,也丢弃# User@Host:行,无法定位调用方
用 pt-query-digest 筛高扫描量 SQL
pt-query-digest 默认按响应时间排序,对“假快真伤”的 SQL 不敏感。必须加过滤参数主动聚焦扫描行为:
pt-query-digest --filter '$event->{Rows_examined} > 10000' /var/lib/mysql/mysql-slow.logpt-query-digest --group-by fingerprint --order-by 'sum(Rows_examined) DESC' /var/lib/mysql/mysql-slow.log- 重点关注报告中
Rows_examined/Rows_sent比值 > 100 的条目
注意:-a 参数虽能避免数字抽象,但会失去聚合能力;不如先用 grep "Query_time:" mysql-slow.log | sort -k2 -nr | head -20 提取原始 top20,再人工比对。
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一起用
复杂点在于:日志路径权限、long_query_time 作用域(SET SESSION 只影响当前连接)、slow_query_log_file 是否可写——这些都得逐项验证,否则日志开了也白开。











