直方图仅在优化器需精细分布信息时有效,如列值倾斜且频繁用于查询条件;盲目创建会增加开销并可能导致误判。判断依据是优化器是否因数据分布不准而选错执行计划,典型信号包括预估行数与实际扫描行数相差一个数量级以上、高基数列实际分布高度集中、多表连接基数估计偏差大等。

直方图不是“开了就灵”的开关,它只在优化器真正需要精细分布信息时才起作用——比如列值严重倾斜、且该列又频繁出现在 WHERE、JOIN 或 ORDER BY 条件中。盲目建直方图反而增加分析开销和存储负担,还可能因过期导致误判。
哪些列值得建直方图
判断依据不是“有没有索引”,而是“优化器是否因数据分布不准而选错计划”。典型信号包括:
-
EXPLAIN显示预估行数(rows)和实际扫描行数相差一个数量级以上,尤其在WHERE status = 'inactive'这类低频值查询时仍走全表扫描 - 某列的
CARDINALITY看似很高,但实际绝大多数值集中在少数几个(例如status列 95% 是'active',其余 5% 分散在 20 个状态里) - 该列参与多表连接,且连接结果集大小波动剧烈,
EXPLAIN FORMAT=TREE中显示连接基数估计偏差大
用 ANALYZE TABLE UPDATE HISTOGRAM 创建直方图
语法必须带 UPDATE HISTOGRAM ON,否则只是更新索引统计,不生成直方图:
ANALYZE TABLE orders UPDATE HISTOGRAM ON order_status, created_at WITH 128 BUCKETS;
关键点:
- 每次只能指定一张表,不能批量对多张表建直方图
-
BUCKETS数建议 100–256:太少无法刻画倾斜,太多增加存储和解析开销;对百万级以下表,128 是较稳妥起点 - 类型默认是
SINGLE_VALUE(适合离散值,如状态码、类别),若列含大量连续时间戳或浮点数,可显式指定TARGET类型:UPDATE HISTOGRAM ON created_at TYPE TARGET WITH 64 BUCKETS - 操作会加读锁,务必避开业务高峰;若表上有长事务,
ANALYZE会卡在waiting for table flush
验证直方图是否生效及是否准确
直方图建完不等于优化器立刻用上,得看执行计划和底层统计是否同步:
- 查直方图元数据:
SELECT histogram FROM mysql.column_stats WHERE table_name = 'orders' AND column_name = 'order_status';—— 返回非空 JSON 才算写入成功 - 确认优化器读取了它:执行
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE order_status = 'shipped';,在输出 JSON 的query_block->table->access_type下找filtered字段,若该值接近真实选择率(比如 12.3% 而非粗略的 1% 或 50%),说明直方图被采纳 - 注意缓存时效:
information_schema_stats_expiry默认为 86400(24 小时),若刚建完直方图但EXPLAIN没反应,先设SET SESSION information_schema_stats_expiry = 0;强制刷新
直方图失效或误导的常见原因
它不是万能解药,很多问题出在“建了但没管”:
- 数据持续写入后,直方图未更新:比如订单表每天新增 50 万行,但直方图一个月没重刷,此时低频状态(如
'cancelled')的桶频次严重失真 - 建在高 NDV(唯一值数量)列上却用太少桶数:对
user_id这种几乎每行都不同的列建 32 桶直方图,等同于扔了个空 JSON 进去 - 和
innodb_stats_persistent = OFF共存:非持久化统计重启即丢,直方图虽在字典里,但优化器可能因其他统计失效而降级使用旧策略 - 复合条件仍依赖独立性假设:直方图只解决单列分布,
WHERE status = 'error' AND priority > 5这种双条件,优化器仍按P(status) × P(priority)估算,除非用 openGauss 那类多列图模型
最常被忽略的一点:直方图本身不改变索引结构,也不强制优化器走某条路径。它只是让优化器“看清”数据——看清之后选错,问题可能出在索引设计、参数配置或更深层的代价模型上,这时候就得结合 EXPLAIN FORMAT=TREE 和 optimizer_trace 往下挖了。


















