先验证explain的rows预估与实际返回行数是否相差10倍以上,再比对show index的cardinality与select count(distinct col)结果是否严重偏离(如1/100或为0/1),同时排除where中函数、隐式转换及force index干扰,方可确认统计信息过期。

怎么确认真是统计信息过期,不是其他原因?
别一看到 EXPLAIN 里 key 是 NULL 或 type: ALL 就跑 ANALYZE TABLE。先交叉验证两组数据:
- 查
EXPLAIN的rows预估 vs 实际返回行数:差 10 倍以上(比如预估 800 万,实际只 500 行)是强信号 - 查
SHOW INDEX FROM table_name中的CARDINALITYvs 真实去重数:SELECT COUNT(DISTINCT col) FROM table_name,如果前者是后者的 1/100 或直接为 0/1,基本坐实 - 排除干扰项:
WHERE DATE(log_dt) = ...这类函数用法、city_id = '565'字符串传整型、FORCE INDEX硬编码覆盖——这些情况下ANALYZE TABLE完全无效
ANALYZE TABLE 执行时容易踩哪些坑?
ANALYZE TABLE 不是“刷新缓存”,而是重新采样索引页并重算基数,操作不当反而白忙:
- 默认只采样约 20 个页,对大表或数据倾斜严重(比如
status字段 99% 是 1)极易失真;可临时调高:SET GLOBAL innodb_stats_persistent_sample_pages = 100 - 加的是表级读锁,大表执行期间会阻塞
DROP TABLE、ALTER TABLE和写入,务必避开高峰期 - 对分区表,默认不分析具体分区数据页,必须显式指定:
ANALYZE TABLE t PARTITION(p2024) - 如果
innodb_stats_persistent = OFF(MySQL 5.7 及以前默认关),统计只存在内存,重启或自动更新后立刻回退
为什么 ANALYZE TABLE 执行完,EXPLAIN 还是没换索引?
不是命令没跑,而是优化器根本没“看见”新统计,或被更高优先级规则压过了:
- 查
information_schema.STATISTICS表,确认CARDINALITY字段值确实变了,不只是STATS_INITIALIZED时间戳更新 - 检查 SQL 里有没有
FORCE INDEX或USE INDEX,它们会完全绕过优化器决策 - 是否存在更优的覆盖索引?比如查询
WHERE a = ? AND b > ?,优化器可能放弃你期待的idx_a,转而选idx_a_b复合索引——这不是错,是更合理 -
ANALYZE TABLE不修复索引结构问题,也不解决谓词条件导致的选择性误判(如b > ?范围过大时,即使统计准,优化器也可能弃用idx_a_b)
FORCE INDEX 能不能当救命稻草用?
能,但得清楚它生效的前提和边界:
-
FORCE INDEX (idx_a_b)只强制使用该索引,但优化器仍可决定是否用它做排序或范围扫描;如果该索引根本无法满足WHERE条件(比如没包含所有等值谓词列),MySQL 会静默忽略并选其他索引 - 不能对 JOIN 中的右表(被驱动表)单独 force,必须写在对应表的
FROM子句后,例如:FROM orders o FORCE INDEX (dpcz_FK) JOIN pcz ... - 仅适合已确认某索引逻辑上可支撑查询、且实测比优化器自选更快的场景;线上紧急止损可用,但别长期依赖
- 它不影响索引合并(index merge),也不保证覆盖扫描(covering index),是否回表仍取决于
SELECT列和索引结构
EXPLAIN 时看到 key 字段有值,就以为索引还在工作,其实优化器早因基数崩了悄悄换路。











