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

MySQL 直方图适合解决哪类选择性估计问题

来源:17golang原创

时间:2026-11-11 18:53:13 151浏览 收藏

我第一次认真用 MySQL 直方图,是在一个没有合适索引的筛选列上看到优化器估算偏差:SQL 本身不复杂,真正让执行计划犹豫的是它不知道某个常量条件到底能过滤掉多少行。直方图解决的不是“所有慢查询”,而是把列值分布补给优化器,让它在符合条件的常量比较上更接近真实选择性。

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

如果问题是列值分布太偏、列又没有能提供可靠估算的单列索引,并且查询谓词是对常量的比较,MySQL 直方图才值得优先考虑。它主要改善行数估算,不会自动创建索引,也不会保证执行计划一定变快。

先判断:你遇到的是估算问题吗

直方图适合处理的症状通常不是“磁盘突然变慢”,而是 EXPLAIN 中估算行数与实际返回规模相差很远,导致优化器在过滤顺序、连接方式或访问路径上做出不理想的判断。要先把两个问题分开:

  • 如果列上已经有高质量索引,且索引探测能够给出更好的等值估算,直方图未必会被优先使用。
  • 如果 SQL 本身读取了大量必须读取的数据,直方图不能替代索引、分区或查询改写。
  • 如果数据刚发生大规模变化,旧直方图可能反而把优化器带偏,需要先更新统计。

所以,适用边界可以收窄为:列分布有明显偏斜,查询用常量筛选,优化器缺少足够分布信息,且这个估算会影响后续计划选择。

直方图到底补足了什么信息

普通的列统计往往不能完整表达“哪些值集中出现、哪些值很少见”。直方图把列值范围划成若干桶,并记录累计频率、空值比例、采样比例等信息。优化器可以据此估计一个常量条件大约会保留多少行。

MySQL 8.4 文档列出的可参与直方图估算的谓词包括 =、、>、、>=、、IS NULL、IS NOT NULL、BETWEEN、NOT BETWEEN、IN 和 NOT IN 等与常量比较的形式。函数只要本身是常量表达式,也可能满足这个条件。

列值分布、直方图桶、常量谓词、选择性估计、无索引列和优化器行数估算之间的静态关系说明图
图1:结构说明图,展示列值分布如何通过直方图参与常量谓词的选择性估计。

可以把它理解成一条信息关系,而不是一条执行流水线:列值分布被压缩成直方图桶,常量谓词从桶中获得选择性线索,优化器再把线索转换为行数估算。图中的箭头表达静态依赖,不表示 SQL 一定按这个顺序执行。

无索引列为什么更容易体现价值

MySQL 官方把直方图描述为主要对非索引列有用。原因很直接:索引本身已经保存了排序或分布相关的信息,优化器在一些等值条件上还能通过 index dive 得到估算;没有索引的列则缺少同等细粒度的线索。

这不等于“只要没索引就应该建直方图”。如果这个列实际上是高频过滤条件,索引也许才是更根本的方案;如果查询很少使用该列,维护直方图的收益可能不足。直方图更像是对计划估算的补充,适合先解决“优化器不知道分布”的局部问题。

观察到的情况直方图的优先级判断理由
无索引列,值分布明显偏斜,谓词与常量比较较高直方图能补充缺失的分布信息
有单列索引,等值条件可通过索引探测估算较低或需对比优化器可能优先使用范围或索引估算
查询是表达式之间的复杂关联或常量不明确不确定未必落入直方图可参与的谓词范围
统计长时间未更新,数据分布已经变化先更新再判断过期统计会让估算失真

创建直方图时,桶数不是越多越好

使用 ANALYZE TABLE 的直方图语法可以为一列或多列建立统计。桶数的可选范围是 1 到 1024;省略时使用默认值。实际选择不必追求最大值,应该根据分布复杂度、生成成本和估算收益做一个有边界的尝试。

-- 为订单状态和渠道列建立直方图,桶数先取一个可解释的值
ANALYZE TABLE orders
  UPDATE HISTOGRAM ON status, channel WITH 64 BUCKETS;

-- 只更新一个列,不会顺带删除同表其他列的直方图
ANALYZE TABLE orders
  UPDATE HISTOGRAM ON channel WITH 128 BUCKETS;

如果列的不同值数量不超过桶数,MySQL 可以生成 singleton 类型;如果不同值更多,则可能生成 equi-height 类型,用范围桶表达累计频率。文章中的桶数只是起点,不代表对所有数据集都更优。

查看统计内容,别只看有没有一行记录

直方图通过 INFORMATION_SCHEMA.COLUMN_STATISTICS 查看。真正有用的不只是 HISTOGRAM 是否存在,还包括最后更新时间、采样比例、桶数和直方图类型。

-- 只查看目标表和列,避免把别的统计混进判断
SELECT
    SCHEMA_NAME,
    TABLE_NAME,
    COLUMN_NAME,
    HISTOGRAM->>'$."histogram-type"' AS histogram_type,
    HISTOGRAM->>'$."sampling-rate"' AS sampling_rate,
    HISTOGRAM->>'$."last-updated"' AS last_updated,
    JSON_LENGTH(HISTOGRAM->>'$."buckets"') AS bucket_count
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE TABLE_NAME = 'orders'
  AND COLUMN_NAME IN ('status', 'channel');

sampling-rate 小于 1 表示生成统计时只读取了部分数据。大表上这可能是合理的资源取舍,但它也提醒你:不要把一份采样得到的直方图当成永久精确事实。

用 EXPLAIN 看估算是否回到了合理范围

建立直方图后,应该回到原来的查询观察估算列,而不是只看到“创建成功”就结束。MySQL 文档指出,直方图发挥作用时,其影响可以在 EXPLAIN 的 filtered 等估算信息中体现。

-- 用同一条业务查询观察过滤条件的估算变化
EXPLAIN
SELECT order_id, customer_id
FROM orders
WHERE channel = 'partner'
  AND status = 'pending';

这里关注的是估算是否更贴近数据特征,以及后续计划是否因此更合理,而不是盲目追求某个固定百分比。若查询列已有索引,优化器可能继续使用范围估算或索引探测;这属于正常的优先级关系,不表示直方图创建失败。

哪些情况说明直方图帮不上忙

我会把以下几种情况视为停止扩展直方图的信号:

  1. 实际瓶颈是回表、排序、锁等待或磁盘 I/O,估算只是表面现象。
  2. 谓词没有稳定的常量比较,无法清楚地落到文档列出的适用形式。
  3. 列已有可靠索引估算,新增直方图并没有改变计划判断。
  4. 数据变化频繁,而直方图长期不更新,统计新鲜度本身成为新问题。
  5. 列是 JSON 或空间类型,不能直接按普通列直方图生成。

尤其要注意,直方图是在数据变更后按需创建或更新的统计,不会像索引那样随每次修改自动维护。它的维护成本低于索引变更,但过期也会逐渐积累。

不合适时如何撤销

如果对比后确认某列的直方图不再有用,可以只删除该列的统计,保留同表其他列。

-- 只移除 channel 的直方图,保留 status 等其他列的统计
ANALYZE TABLE orders
  DROP HISTOGRAM ON channel;

撤销的意义是把统计选择恢复到更简单的状态,方便重新观察索引和基础统计的行为。它不是回滚数据,也不会删除表上的索引。

ANALYZE TABLE、COLUMN_STATISTICS、histogram 元数据、EXPLAIN、索引估算和统计新鲜度之间的静态边界说明图
图2:边界说明图,展示直方图的管理入口、观察面和不应替代的索引估算关系。

最后用一张表做决定

问题回答“是”时的动作
估算行数和实际规模差距明显吗?继续确认谓词和列分布,不要直接把慢归因于直方图
列值分布明显偏斜吗?优先考虑建立直方图补充分布信息
查询是对常量的比较吗?检查是否属于等值、范围、IN、NULL 等支持形式
列没有可靠的索引估算吗?把直方图作为局部估算补充,而不是索引替代品
统计还新鲜吗?数据变化后重新生成,再用 EXPLAIN 对比

我的结论是:MySQL 直方图最适合解决“优化器不了解某列值分布”造成的选择性估计问题,尤其是无索引列上的常量过滤。它的价值在于让计划选择拥有更好的输入,而不是承诺一次设置就能解决所有查询性能问题。

相关问题

直方图会自动创建索引吗?

不会。直方图只是优化器统计,索引仍然需要单独设计和维护。

桶数应该直接设置成 1024 吗?

不建议把最大值当成默认答案。先根据分布复杂度和资源成本选择可解释的桶数,再用估算变化判断是否值得增加。

建立直方图后执行计划一定改变吗?

不一定。若原有范围估算或索引探测更可靠,优化器可以继续采用它们;直方图存在并不意味着必须使用。

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