现象或背景:SQL 执行计划漂移,但不是参数没调对
有一条 SQL,执行计划漂移了,导致 P99 从 15ms 飙到 3.4s,但索引没变、SQL 文本没变,连 buffer pool 命中率也没掉。这种「执行计划漂移」比绝大多数「索引没建好」更隐蔽,因为它不报错、不锁表,只在 EXPLAIN 的 rows 估算里悄悄骗你。
原理或根因分析:统计信息失真导致优化器选错路径
InnoDB 优化器选路径时,依赖的是 information_schema.statistics 里的 CARDINALITY 和 LEAF_PAGES,而不是真实行数。统计信息不是每次 DML 都更新,而是在表首次被访问 / 执行 ANALYZE TABLE 时采样,之后当表内行数相对上次统计发生显著变化(默认阈值 innodb_stats_auto_recalc_threshold=1%)时才自动更新。
如果批跑任务往表里灌了大量数据,比如 2.3 亿行表,一次批跑翻转了 200–400 万行,但不到 1% 的阈值,auto-ANALYZE 根本不会触发。这时候只能靠应用层在批跑后主动 ANALYZE,或者把触发逻辑外置到监控脚本里按 DML 量阈值判定。
具体做法:检测控制 + 最小权限 + 回归预算门禁
1) 检测控制:给这条 SQL 装一个"烟雾报警器"
别等慢日志/告警 5 分钟后才发现计划漂了。用 performance_schema 做执行计划基线比对:
-- 基线:白天稳定期的 rows_examined 约 5 万
SELECT digest_text,
count_star,
ROUND(avg_rows_examined) AS avg_rows,
ROUND(max_rows_examined) AS max_rows,
avg_latency
FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE '%t_invoice_item%UNBILLED%LIMIT 500%'
ORDER BY last_seen DESC;
把 avg_rows_examined 的"健康阈值"设为 10 万(正常 5 万的 2 倍余量)。批跑窗口(02:00–05:00)内一旦 max_rows_examined > 500万,立刻触发告警 + 自动执行一次 ANALYZE TABLE。这比"定时 ANALYZE"聪明在哪:它只在漂移真的发生时才动手,而不是每隔 N 小时盲刷,既省 DML 压力又消灭漂移窗口。
2) 最小权限:别给批跑任务"万能钥匙"
如果走"批后自动 ANALYZE"路线,切记不要把批跑账号的 ANALYZE/ALTER 权限和主业务账号混用。安全加固的基本原则是职责分离:批跑账号只该有它那条 UPDATE 的权限,ANALYZE 应该由一个独立的、受审计的运维账号(或 RDS 控制台 API)触发。这样一旦批跑逻辑有 bug 误删了索引,你手里还有一条干净的审计链能回溯到是谁、哪个事务、什么时候动的手。
3) 回归预算门禁:把"计划漂移"拦在发布前
这是最容易被漏的一环。在你自己的 CI 里加一条执行计划回归测试:
# 伪代码:每次发布前,在预发跑一条"基线 SQL 计划指纹"
mysql -e "EXPLAIN FORMAT=JSON 'SELECT ... LIMIT 500'" | jq '.query_block.cost_info.query_cost'
# 断言:cost 必须 < 1000,且 access_type 必须是 ref 而不是 ALL
把这个断言写进 CI,谁改表结构、谁加索引、谁动了统计信息,谁就得对自己的计划指纹负责。这比事后救火便宜一个数量级。
踩过的坑或反直觉之处
innodb_stats_auto_recalc_threshold默认值是 1%,不是 10%,所以批跑翻转 200–400 万行可能卡在这个阈值上,导致 auto-ANALYZE 不触发。- ANALYZE 期间如果有长事务持有该表的写锁(比如 batch UPDATE 还没 commit),ANALYZE 会阻塞在 MDL 等待上,反过来阻塞后续所有 DML。
innodb_stats_persistent_sample_pages调大后 ANALYZE 耗时近似线性增长,务必在低峰窗口执行,并提前确认无长事务。
结论与可复用清单
- 检测控制:用
performance_schema做执行计划基线比对,设置健康阈值并自动触发ANALYZE。 - 最小权限:批跑账号仅限于它那条 UPDATE 的权限,ANALYZE 由独立运维账号触发。
- 回归预算门禁:在 CI 中加入执行计划回归测试,确保每次发布前计划指纹不漂移。
一句话总结: Hint/SPM/ANALYZE 是"怎么修",而检测 + 权限 + 门禁是"怎么保证不再发生"。前者是止血,后者是打疫苗。