大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
同一句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条件里用了函数。
-- 直方图建在 status 列上,但这里用了 UPPER()
SELECT * FROM orders WHERE UPPER(status) = 'ACTIVE';
直方图基于原始列值的分布生成。一旦WHERE条件对列做了函数操作,优化器无法用直方图的分布去估算,直方图被完全绕过。
场景二:分区表查询未命中分区。
如果表是分区表,而查询条件没有明确命中某个分区(比如没写PARTITION(p2024)),优化器可能忽略直方图。MySQL 8.0.19修复了部分情况,但仍有遗漏。
场景三:直方图建在主库,但从库上没有。
直方图存储在information_schema.COLUMN_STATISTICS中,这个表不参与主从复制。如果直方图只在主库建了,从库的优化器完全没有这份信息,主从的EXPLAIN结果可能不一致。读写分离场景下,从库上的慢查询可能因为“没有直方图”而选错计划。
五、直方图的更新:建完不是一劳永逸
直方图不会自动更新。ANALYZE TABLE刷新的是基础统计信息(行数、索引基数),不会重建直方图。重建直方图必须显式执行:
ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 50 BUCKETS;
过期直方图比没有直方图更危险:优化器会信任一份已经过时的分布信息,做出比采样估算更差的决策。数据分布变化频繁的列,直方图需要纳入定期维护计划。
六、小结
直方图不是“建了就灵”的银弹。它只在单列过滤、数据倾斜、且优化器没有更强索引可用的条件下生效。桶数选错等于白建,函数包装直接绕过,分区未命中可能忽略,主从不复制导致从库无感知,过期不更新比不建更危险。
建直方图之前,先确认三件事:这一列真的严重倾斜吗?这一列出现在WHERE里且没有可用索引吗?桶数跟列类型匹配吗? 三问都过了,再动手。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~