必须开启innodb_stats_persistent=ON以防重启后统计丢失导致执行计划突变;关闭自动重算并人工控制ANALYZE时机;调高采样页数避免基数低估。

必须确保 innodb_stats_persistent=ON,否则重启后统计信息丢失,执行计划大概率突变。
检查并强制开启持久化统计
MySQL 8.0 默认是 ON,但不能只信默认值——生产环境必须验证。很多线上事故源于配置被覆盖或误改。
- 执行
SHOW VARIABLES LIKE 'innodb_stats_persistent';,返回值必须是ON - 若为
OFF,立即执行SET PERSIST innodb_stats_persistent = ON;(比改my.cnf更可靠,写入mysqld-auto.cnf且优先级更高) - 该参数不可动态关闭:设为
ON后无需再动,重启也生效;设为OFF则必须重启 MySQL 才能生效,所以别试
关掉自动重算,改用人工控制时机
innodb_stats_auto_recalc=ON 看似省心,实则在高频小表上频繁触发重采样,拖慢后台线程,还可能因采样延迟导致统计滞后。
- 全局关闭:
SET PERSIST innodb_stats_auto_recalc = OFF; - 对核心热表(如
orders),单独启用:ALTER TABLE orders STATS_AUTO_RECALC = 1; - 对低频冷表(如配置缓存表
config_cache),明确禁用:ALTER TABLE config_cache STATS_AUTO_RECALC = 0; - 所有重算统一走定时任务,在凌晨低峰期执行
ANALYZE TABLE orders, users;,避免干扰业务
调高采样页数并验证基数准确性
默认 innodb_stats_persistent_sample_pages=20 对千万级以上表严重不足,尤其当字段分布倾斜(如状态码、性别、区域ID)时,Cardinality 会被严重低估,优化器直接弃用索引。
- 先查倾斜程度:
SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders;,结果 - 调高采样页:
SET GLOBAL innodb_stats_persistent_sample_pages = 200;(上限默认 2000,够用) - 必须配合
ANALYZE TABLE orders;才生效——只改参数不刷统计,等于没改 - 验证是否修好:
SHOW INDEX FROM orders;查Cardinality,再对比SELECT COUNT(DISTINCT status) FROM orders;,两者差距超过 5 倍就还得调
直方图比调参更治本,但只适用于 MySQL 8.0+
采样页调到 200 还不准?说明数据分布太复杂(比如含大量 NULL、空字符串、JSON 字段),这时靠直方图比硬调 sample_pages 更可靠。
- 创建等深直方图:
ANALYZE TABLE orders UPDATE HISTOGRAM ON status, created_at; - 直方图会记录实际值频次分布,优化器能据此判断
WHERE status = 'paid'是否真的只占 2%,而不是靠采样猜 - 注意:直方图不替代
ANALYZE TABLE,它只补充列级分布信息;表级行数、索引页数等仍依赖传统统计 - 删直方图用:
ANALYZE TABLE orders DROP HISTOGRAM ON status;
真正容易被忽略的点是:ANALYZE TABLE 不是“一劳永逸”,它只固化当前快照;如果后续有批量导入、逻辑删除、历史归档等操作,必须重新触发。把 ANALYZE 当成 DDL 一样纳入上线 checklist,比事后调优成本低得多。


















