开放问答
MySQL 8.0 大表 DELETE 80% 后优化器仍选旧索引,ANALYZE TABLE 也没救,怎么彻底解决
该问题的正文、回答与评论。
提问者 陈砚舟 · 状态 open
MySQLInnoDB优化器统计信息索引选择慢查询
背景:MySQL 8.0.36,InnoDB,一张约 5000 万行的日志表 access_log,主键 id,二级索引 idx_path (path),idx_path_status (path, status),idx_time (create_time)。日常查询是 SELECT id, path FROM access_log WHERE path = ? AND status = ? LIMIT 20,之前走 idx_path_status 覆盖索引,P99 约 3ms。
现象:
- 上个月做了数据清理,DELETE 掉了 80% 的旧日志(约 4000 万行),表只剩约 1000 万行;
- 清理后,上面那条查询 EXPLAIN 显示走 idx_path(单列),key_len 只覆盖 path 一列,rows 估算约 60 万,实际扫描 55 万行,P99 从 3ms 涨到 280ms;
- 而 idx_path_status 明明还在,且 (path, status) 组合的选择性远高于单独 path,理论上应该走它;
- 我执行了
ANALYZE TABLE access_log,information_schema.TABLES 里 TABLE_ROWS 从 5000 万刷到 1000 万,但 EXPLAIN 结果不变,还是选 idx_path; - 换一条
WHERE path = ? AND status = ? AND create_time > '2025-09-01'的查询,优化器又切回 idx_path_status,说明它"知道"这个索引存在,但在只有 path+status 两个条件时就是不肯选。
已尝试:
ANALYZE TABLE access_log(刷新统计信息后 EXPLAIN 不变);SET optimizer_switch='index_merge=off'排除 index merge 干扰,无变化;- 用
FORCE INDEX (idx_path_status)验证,确实能走对索引,P99 回到 4ms,证明是优化器选错而非索引本身有问题; - 查了
information_schema.INNODB_TABLE_STATS,n_diff_p000/p001/p010/p100/p111 的值在 ANALYZE 前后有变化,但变化幅度看起来不足以让优化器"换心"; - 试过把 innodb_stats_persistent_sample_pages 从默认 20 调到 200 再 ANALYZE,EXPLAIN 结果依旧。
想请教:
- MySQL 8.0 的优化器在"两个二级索引都能服务同一条查询"时,具体的 cost 计算公式里,TABLE_ROWS(或更底层的 n_diff_* 采样)是怎么参与决策的?为什么 ANALYZE 刷新了 TABLE_ROWS 但 cost 排序没变?是不是因为采样页只采了 20 个、而 DELETE 后的行分布不均匀,导致采样仍然"觉得" idx_path 的选择性更高?
- 有没有比 FORCE INDEX 更优雅的"引导"手段?比如 optimizer hints(
/*+ INDEX(...) */)、或者调整 innodb_stats_persistent_sample_pages 到某个阈值后一定能纠正?想避免在业务代码里到处塞 FORCE INDEX。 - 这种"大比例 DELETE 后统计信息失真"的问题,生产上一般怎么处理?是定期跑 ANALYZE、还是用
ALTER TABLE ... ENGINE=InnoDB重建、还是直接改查询写法?有没有人踩过类似的坑并给出了稳定方案?
评论
暂无评论。