直方图仅在目标列无索引或复合索引前导列未命中、值分布严重倾斜、且出现在WHERE等条件中时,通过提升过滤比例估算精度间接影响索引选择;其他情况均无效。

直方图不能直接让优化器“选中”某个索引,它只在无索引、或索引未被命中前导列、且值分布严重倾斜时,帮优化器更准地估算 WHERE 条件的过滤比例——这个估算一变,原本被低估的索引才可能从“成本太高”变成“值得用”。
直方图真正影响索引选择的三个硬条件
缺一不可,否则 EXPLAIN 里的 type 和 key 不会变:
- 目标列上没有可用索引,或者虽有复合索引(如
(status, created_at)),但查询只用了created_at—— 前导列没命中,索引失效 - 该列值分布严重倾斜:比如
status中'active'占 95%,'failed'实际只占 0.2%,但优化器默认按均匀分布猜,以为会返回 5% 行 - 该列出现在
WHERE/JOIN/GROUP BY条件里,且优化器需要靠它估算行数来权衡“全表扫描 vs 走某索引再回表”的成本
常见无效场景:WHERE UPPER(status) = 'FAILED'、字段是 JSON 或压缩 TEXT、查询跨分区但没指定分区键——这些情况下直方图压根不被读取。
建错类型或桶数,反而让优化器更迷糊
MySQL 默认用 EQ_HEIGHT,但它对枚举类字段是灾难性的:
- 低基数离散值(如
status、order_type)→ 必须用SINGLETON,桶数 ≈ 唯一值数量 ±20%。12 种状态就写WITH 14 BUCKETS TYPE SINGLETON - 高基数连续值(如
created_at、user_id)→ 用EQ_HEIGHT,桶数控制在100–256。超过1000会让解析变慢,information_schema.COLUMN_STATISTICS表体积激增 - 字符串列平均长度 > 200 字节 → 直接建会报
ER_TOO_LONG_STRING;得先截断:ANALYZE TABLE orders UPDATE HISTOGRAM ON SUBSTRING(status, 1, 100) WITH 20 BUCKETS,但要注意精度损失
不指定 TYPE 就建,大概率让优化器把频次信息全丢掉——它看到的是“等高区间”,不是“每个状态的真实出现次数”。
建完不等于生效:必须手动触发统计重载
ANALYZE TABLE t UPDATE HISTOGRAM ON col; 成功返回,只代表元数据写进去了,不代表优化器加载了它:
- 查元数据确认落地:
SELECT HISTOGRAM FROM information_schema.COLUMN_STATISTICS WHERE TABLE_NAME = 't' AND COLUMN_NAME = 'col';—— 返回非空 JSON 才算真建好 - 强制重载统计:
ANALYZE TABLE t;(注意:不是带UPDATE HISTOGRAM的那条),否则优化器仍用旧估算 - 对比
EXPLAIN FORMAT=JSON中的"filtered"和"rows":比如WHERE status = 'failed',建前"filtered": 33.33,建后变成"filtered": 0.37,且"rows"同步下降,才算真正起效
最容易忽略的一点:直方图不会把 type: ALL 变成 type: ref。它只影响多个候选路径之间的成本比较——比如让优化器放弃全表扫描,转而选择“走 idx_user_id 索引 + 回表”,前提是那个索引本身存在且能覆盖查询需求。


















