必须开启log_queries_not_using_indexes=on并设long_query_time=0,再执行无索引查询验证日志中rows_examined远大于rows_sent且extra含using where但无using index,才确认记录全表扫描。

如何确认慢日志里真有全表扫描语句
MySQL 8.0 默认不记录全表扫描,只记录执行时间超阈值的语句。哪怕一条 SELECT * 扫了千万行,只要没超 long_query_time,就不会进慢日志。所以第一步不是翻日志,而是确认配置是否开启扫描记录:
-
SET GLOBAL log_queries_not_using_indexes = ON—— 这个开关必须开,它会让所有未走索引的查询(包括全表扫描)强制记入慢日志,无论耗时多短 -
SET GLOBAL long_query_time = 0建议同步设为 0,避免漏掉快但低效的扫描(比如小表全扫) - 检查
slow_query_log_file路径权限,确保 MySQL 进程有写入权限,否则日志静默失效
开完后跑一条明显无索引的 SELECT * FROM users WHERE status = 'pending'(且 status 无索引),再查日志文件,看到该语句且含 Rows_examined: 123456 高数值,才算生效。
怎么从慢日志里快速定位全表扫描语句
直接 grep 不可靠:慢日志格式杂、字段多,Rows_examined 和 Rows_sent 的比值才是关键信号。全表扫描典型特征是前者远大于后者(比如 Rows_examined: 987654,Rows_sent: 10)。
- 用
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log先看耗时 Top 10,但注意它会合并相似 SQL,可能掩盖扫描本质 - 更准的做法是用 awk 提取高扫描量语句:
awk '/Rows_examined/ && $0 ~ /Rows_examined: [0-9]{5,}/ {getline; print}' /var/lib/mysql/slow.log | grep -A 5 "SELECT" - 重点盯
Extra字段:日志里若出现Extra: Using where且没Using index,基本就是全表扫描;若还有Using filesort或Using temporary,说明问题更重
EXPLAIN 验证时为什么显示 type=ALL 却没在慢日志里
常见错觉:用 EXPLAIN 看到 type: ALL 就以为一定进了慢日志。其实有三个硬性过滤条件:
- 语句必须实际执行过(
EXPLAIN不触发日志) - 必须满足
log_queries_not_using_indexes = ON,否则即使type: ALL也不记 - 如果语句被 query cache 缓存(MySQL 8.0 已默认禁用,但升级残留配置可能还开着),真实扫描不会发生,自然不记日志
验证方法:关掉缓存(SET SESSION query_cache_type = OFF),加 SQL_NO_CACHE 强制不走缓存,再执行并立刻查慢日志。
分析结果后改 SQL 还是加索引更有效
别急着加索引。先看 WHERE 条件字段的选择性:SELECT COUNT(*) FROM t WHERE col = 'x' / COUNT(*) FROM t 如果结果 > 0.2,说明该值太常见,加单列索引效果有限,甚至可能让优化器弃用。
- 优先覆盖
WHERE + ORDER BY + SELECT三部分:比如WHERE a=1 AND b=2 ORDER BY c,建联合索引(a,b,c)比单建a索引强得多 - 避免冗余索引:
(a)和(a,b)同时存在时,(a)几乎无用,删掉可减小写放大 - 对
LIKE '%abc'这类前导通配,B-tree 索引无效,考虑全文索引或倒排表,别硬加索引
真正难的是那些动态拼接的查询——参数组合太多,索引很难全覆盖。这种场景下,宁可拆成多个确定路径的语句,也别留一个“万能”全表扫描 SQL。











