MySQL 8.0直方图:统计信息升级了,执行计划为什么还是选错?

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

同一句SQL,周二跑50毫秒,周三跑5秒。SQL没改,表结构没动,数据量也没涨多少。

你第一反应可能是:统计信息过期了。于是ANALYZE TABLE,执行计划回来了。但过几天又飘了。

然后你听说MySQL 8.0有个新东西叫直方图,专治这种"数据倾斜导致优化器估不准"的病。建完直方图,满怀期待地跑了一下------执行计划还是全表扫描,rows还是估得离谱。

直方图建了,为什么还是没用?

问题往往不在直方图本身,而在一个更前置的判断:你建直方图的那一列,优化器到底会不会去看它?

一、先搞清楚:直方图到底治什么病

InnoDB的统计信息靠随机采样 生成。默认只抽innodb_stats_persistent_sample_pages个索引页(8.0默认20页),用这20页的分布去推算整张表。

对于数据分布均匀的列,采样够用。但对于严重倾斜 的列------比如status,95%的行是'ACTIVE',剩下5%分散在十几种状态里------20页的样本稍微偏一点,推出来的基数就能差出好几倍。

基数估错,优化器就会做出"诚实但错误"的判断:它以为status='DONE'只命中几千行,走索引+回表很划算;实际命中了两百万行,回表两百万次随机I/O,比全表顺序扫描还慢。

直方图的作用,就是在单列数据倾斜的场景下,给优化器一个更精确的分布信息,替代粗糙的采样估算。

二、边界一:直方图只救"单列过滤",不救索引缺失

这是最容易被误解的一点。直方图不会自动建索引,不会改变JOIN策略,不会优化ORDER BY或GROUP BY。

它只在一种场景下起作用:WHERE子句中对单个列做过滤,且该列没有可用索引(或优化器因统计不准弃用了索引) 。

如果你的慢查询根因是缺索引、JOIN顺序选错、或者排序走filesort,直方图帮不上忙。先补索引,再考虑直方图。

判断方法 :看EXPLAIN的rows和实际返回行数的差距。如果差距超过10倍,直方图才可能有价值;如果type=ALL但rows估算并不离谱,问题不在统计信息,在索引设计。

三、边界二:桶数设错,高基数列的直方图等于没建

直方图的精度由桶数(BUCKETS)决定。桶数太少,分布信息被压缩,优化器拿到的仍然是粗糙估算;桶数太多,直方图的维护开销和存储成本上升。

MySQL的默认桶数是100,但不同列类型需要不同的桶数:

  • 枚举/状态类字段 (status、order_type):20-50桶足够,覆盖所有常见值即可

  • 高基数数值列 (user_id、created_at):100桶几乎必然失效,需要256或512桶,但超过1000不推荐

  • 长字符串列 (utf8mb4平均长度>200字节):建直方图前先用SUBSTRING截断,否则可能报ER_TOO_LONG_STRING错误

桶数不是"建了就准",选错桶数等于白建。

四、边界三:直方图会被"函数包装"和"分区未命中"直接绕过

即使直方图建对了列、桶数也够,优化器仍然可能根本不读它。

场景一:WHERE条件里用了函数。

bash 复制代码
-- 直方图建在 status 列上,但这里用了 UPPER()
SELECT * FROM orders WHERE UPPER(status) = 'ACTIVE';

直方图基于原始列值的分布生成。一旦WHERE条件对列做了函数操作,优化器无法用直方图的分布去估算,直方图被完全绕过。

场景二:分区表查询未命中分区。

如果表是分区表,而查询条件没有明确命中某个分区(比如没写PARTITION(p2024)),优化器可能忽略直方图。MySQL 8.0.19修复了部分情况,但仍有遗漏。

场景三:直方图建在主库,但从库上没有。

直方图存储在information_schema.COLUMN_STATISTICS中,这个表不参与主从复制 。如果直方图只在主库建了,从库的优化器完全没有这份信息,主从的EXPLAIN结果可能不一致。读写分离场景下,从库上的慢查询可能因为"没有直方图"而选错计划。

五、直方图的更新:建完不是一劳永逸

直方图不会自动更新 。ANALYZE TABLE刷新的是基础统计信息(行数、索引基数),不会重建直方图。重建直方图必须显式执行:

bash 复制代码
ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 50 BUCKETS;

过期直方图比没有直方图更危险:优化器会信任一份已经过时的分布信息,做出比采样估算更差的决策。数据分布变化频繁的列,直方图需要纳入定期维护计划。

六、小结

直方图不是"建了就灵"的银弹。它只在单列过滤、数据倾斜、且优化器没有更强索引可用的条件下生效。桶数选错等于白建,函数包装直接绕过,分区未命中可能忽略,主从不复制导致从库无感知,过期不更新比不建更危险。

建直方图之前,先确认三件事:这一列真的严重倾斜吗?这一列出现在WHERE里且没有可用索引吗?桶数跟列类型匹配吗? 三问都过了,再动手。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
老纪的技术唠嗑局2 小时前
把 seekdb 塞进 iOS App:让混合检索嵌入手机
数据库
ClouGence3 小时前
开源数据库管理工具 CloudDM 新版本发布,支持 OpenSearch、IoTDB 数据源
数据库·开源
徐小黑ACG3 小时前
Redis-基础
数据库·redis·缓存
火山引擎开发者社区3 小时前
案例实践 | 让 Agent 读懂业务口径:ContextSearch 在广告数据查询中的实践
数据库
10年前端老司机5 小时前
Milvus 8种检索能力实践
数据库·python·agent
aiks_yuan5 小时前
MySQL 深分页 LIMIT 越翻越慢怎么办:300 万行实测 OFFSET、延迟关联和 keyset 的耗时差距(MySQL 8.2)
mysql
Decay__5 小时前
14_MySQL 高可用与容灾架构 —— MGR / InnoDB Cluster / Keepalived+VIP / 双主 / 读写分离 / 两地三中心
运维·数据库·sql·mysql·架构
freejackman5 小时前
【实战三】撕开脱敏盲区:深度解析 CASE WHEN 与窗口函数血缘
大数据·数据库·sql
codigger7 小时前
线上数据库连接池打满:从应急止损到架构根治的完整方案
数据库·sql·程序员