登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL histogram 怎样辅助没有合适索引的列估算

来源:17golang原创

时间:2026-10-26 01:23:47 386浏览 收藏

当一张订单表的 status 列没有合适的单列索引时,MySQL 仍然需要估算“这个条件大概会命中多少行”。这时可以用 histogram 保存列值分布,让优化器在选择连接顺序、过滤方式和扫描计划时少一些盲猜。

官方资料:https://dev.mysql.com/doc/refman/8.4/en/optimizer-statistics.html

要点速览
  • histogram 是列值分布统计,不是索引;它改善的是基数估算,不能直接让全表扫描变成索引查找。
  • 对没有合适索引的列,可以用 ANALYZE TABLE ... UPDATE HISTOGRAM 建立统计,再从 INFORMATION_SCHEMA.COLUMN_STATISTICS 查看。
  • 最终要用 EXPLAIN 对照 rows、过滤条件和访问路径;统计过期、采样过低或条件不适用时,估算仍可能失真。

histogram 解决的是“估算错”,不是“增加索引”

先把职责分开。索引提供一条可访问的数据结构,histogram 则记录某个列的值分布。没有合适索引时,优化器仍可能选择扫描,但它可以借助分布统计估算过滤后的行数,从而更合理地比较扫描、连接顺序和后续过滤成本。

MySQL 官方文档明确把 histogram 主要定位在非索引列上;对于已经有索引的列,优化器可能通过 index dive 或 range optimizer 得到更直接的估算,histogram 不一定更有用。因此,不能把“建立 histogram”写成“给列补上了索引”,也不能只看到统计信息存在就断言查询一定变快。

下面把订单表的 status 当作示例列。假设业务查询经常筛选它,但当前没有合适的单列索引;这篇只观察估算信息,不把示例输出描述成已经在某台机器上实测的结果。

用 ANALYZE TABLE 建立并查看 histogram

先为目标列生成一个有限数量的桶。桶数量不是越大越好:它需要在分布表达能力和统计维护成本之间取平衡。MySQL 8.4 中 N 的范围是 1 到 1024,省略时默认使用 100。

-- 为没有合适单列索引的状态列建立持久化分布统计。
ANALYZE TABLE shop_orders
  UPDATE HISTOGRAM ON status WITH 32 BUCKETS;

-- 查看统计类型、桶数量和采样率,判断统计是否真的存在。
SELECT SCHEMA_NAME,
       TABLE_NAME,
       COLUMN_NAME,
       HISTOGRAM->>'$."histogram-type"' AS histogram_type,
       HISTOGRAM->>'$."number-of-buckets-specified"' AS bucket_count,
       HISTOGRAM->>'$."sampling-rate"' AS sampling_rate
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE TABLE_NAME = 'shop_orders'
  AND COLUMN_NAME = 'status';

COLUMN_STATISTICS 暴露的是一个 JSON 形式的 histogram。这里的查询只做观察,不要求先把完整 JSON 复制出来;重点是确认列名、类型、桶数量和采样率是否与当前维护动作相符。

MySQL 没有合适索引的列、ANALYZE TABLE、histogram、COLUMN_STATISTICS 与优化器估算之间的静态关系说明图
图1:结构说明图,展示非索引列的分布统计如何进入 COLUMN_STATISTICS 并被优化器用于估算,不是运行截图。

用 EXPLAIN 判断估算有没有被改善

建立 histogram 后,应该回到实际查询的执行计划。示例查询只用于展示观察入口:

-- 用执行计划观察过滤行数估算,不把示例输出当作实测结论。
EXPLAIN SELECT id, created_at, total_amount
FROM shop_orders
WHERE status = 'PAID'
  AND created_at >= '2026-01-01';

重点看三件事:第一,访问路径是否仍然是预期的表扫描或其他路径;第二,rows 是否反映了 status = 'PAID' 的实际分布,而不是把每个状态都当成差不多;第三,filtered 与其他条件叠加后,估算是否出现明显偏差。没有合适索引时,histogram 不能消除扫描本身,但更可信的行数会影响优化器对连接顺序和中间结果规模的判断。

观察项能说明什么不能说明什么
rows优化器预计检查的行数范围不是实际返回行数,也不是耗时
访问类型与键列计划选择了哪种访问路径不能单独证明 histogram 造成了变化
filtered剩余条件的估算过滤比例不是业务数据的精确统计报表

如果前后计划差异很小,也不代表 histogram 无效:可能是当前条件本身已经容易估算,或者 range optimizer 已经优先处理了这个条件。更稳妥的做法是保存建立统计前后的 EXPLAIN 结果,再结合真实数据分布和查询目标判断。

MySQL histogram 的列值分布、桶数量、采样率、rows 估算和计划选择边界说明图
图2:边界说明图,对比分布统计、采样率与 EXPLAIN 行数估算之间的关系,不是执行结果截图。

采样、自动更新和删除都要有边界

histogram 不是永久正确的快照。数据持续变化后,列值分布会逐渐偏离上次统计;如果生成时受 histogram_generation_max_mem_size 限制而采用采样,还要关注 sampling-rate。采样率较低时,稀有值和突变分布可能更容易被估算遗漏。

-- 让后续 ANALYZE TABLE 或 InnoDB 持久化统计重算时更新该 histogram。
ANALYZE TABLE shop_orders
  UPDATE HISTOGRAM ON status WITH 32 BUCKETS AUTO UPDATE;

-- 业务不再需要这份列统计时,显式删除它。
ANALYZE TABLE shop_orders
  DROP HISTOGRAM ON status;

AUTO UPDATE 是 MySQL 8.4 提供的管理方式;没有指定时默认是 MANUAL UPDATE。另外,histogram 不能用于所有列:官方文档列出了 JSON、空间类型、临时表和单列唯一索引等限制。对数据变化快的状态列,更新时机要和发布、归档或批量导入节奏一起设计。

常见问题

没有索引时建立 histogram,会不会让查询直接走索引?

不会。histogram 只提供列值分布统计,不创建 B-Tree 或其他访问结构;它可能改善优化器的行数估算,但最终仍可能选择扫描。

桶数量设置得越大,估算就一定越准吗?

不一定。桶更多可以表达更细的分布,但也会增加统计维护成本;应结合数据规模、分布变化和 EXPLAIN 的估算偏差调整。

为什么 COLUMN_STATISTICS 有记录,计划却没有明显变化?

优化器可能已经有更适合的 range 估算,或者当前条件不属于 histogram 的有效使用场景。记录存在只证明统计已保存,不证明每条查询都会采用它。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>