迁移后索引“还在但不走”主因是统计信息未更新,ANALYZE TABLE可强制刷新Cardinality并立即生效;Cardinality=0表明优化器误判索引无用,而非索引丢失。

迁移后索引“还在但不走”,90% 是 ANALYZE TABLE 没跑,不是索引坏了,是优化器“不认识”新数据。
为什么迁移后 EXPLAIN 显示 type=ALL 却查不到索引丢失?
因为 SHOW INDEX FROM t 里所有索引都存在,但关键字段 Cardinality 全是 0。InnoDB 的优化器靠这个值估算选择性,0 就等于告诉它“这索引没用”。这不是迁移漏建索引,而是统计信息压根没刷新——mysqldump 导入、DRS 全量同步、LOAD DATA INFILE 等方式都不会自动触发采样。
-
Cardinality = 0是硬信号:统计缺失,哪怕索引物理存在也形同虚设 - 跨版本或跨字符集迁移(如
utf8 → utf8mb4)可能让索引被静默截断,但那是另一类问题,先排除统计问题再看建表语句 - 检查
information_schema.INNODB_INDEX_STATS表的last_update字段,比看SHOW INDEX更准
执行 ANALYZE TABLE 前必须确认的三件事
它不是万能药,盲目执行反而浪费 I/O。先快速验证是否真该跑:
- 刚批量导入/删除超过 5% 行数的数据(比如千万级表新增百万行)
-
EXPLAIN预估rows和实际扫描量差一个数量级以上(例如预估 120 行,SELECT COUNT(*)实际 80 万) - 新增或重建过索引后,查询仍没走新索引(
ALTER TABLE t ADD INDEX不会自动更新统计)
注意:innodb_stats_auto_recalc=OFF 或 table_definition_cache 过小导致 stat_modified_counter 归零,也会让自动更新失效——这类表必须手动干预。
ANALYZE TABLE 在大表上怎么跑才安全?
它不锁表(InnoDB 仅加 MDL 读锁),但会触发磁盘 I/O,尤其对未开启持久化统计的大表。别直接全库扫:
- 单表执行最稳妥:
ANALYZE TABLE stock_in_detail; - 批量生成语句(先在测试库验证):
SELECT CONCAT('ANALYZE TABLE ', table_schema, '.', table_name, ';') FROM information_schema.tables WHERE table_schema = 'your_db' AND table_rows > 100000; - MySQL 8.0.23+ 可加超时控制:
ANALYZE TABLE t WITH TIMEOUT 60; - 若
innodb_stats_persistent_sample_pages还是默认 20,大表误差大,建议提前调到 100(上限 2000)
执行后立刻生效,不用重启,EXPLAIN 的 key 和 rows_examined 会马上变正常。
Cardinality 刷新后还是不准?重点查 innodb_stats_method
常见于含大量 NULL 的列(如状态字段允许 NULL),默认 innodb_stats_method = nulls_equal 会把所有 NULL 当成同一值,严重低估基数:
- 临时改配置再分析:
SET SESSION innodb_stats_method = 'nulls_unequal'; ANALYZE TABLE t; - 验证是否改善:
SELECT COUNT(DISTINCT col) FROM t WHERE col IS NOT NULL;对比SHOW INDEX输出的Cardinality - 低选择性列(如只有 0/1/2 的
status)即使基数准,优化器也大概率不走索引——这时不是统计问题,是索引设计问题,应考虑覆盖索引或业务层过滤
真正容易被忽略的是:统计信息本质是概率采样,再调参也做不到 100% 匹配实时分布;而 ANALYZE TABLE 后 Cardinality 仍为 0,往往意味着表元数据损坏或权限不足,得查 error log。


















