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

MySQL 直方图怎样检查采样比例是否过低

来源:17golang原创

时间:2026-11-04 04:45:21 309浏览 收藏

MySQL 直方图是否“采样过少”,不能只看一个抽象的质量分数。更可靠的做法是读取目标列的 HISTOGRAM JSON,查看其中的 sampling-rate,再结合生成时的内存上限和查询估算判断。sampling-rate 等于 1 表示生成这份直方图时读到了全部数据;小于 1 表示生成过程采用了部分采样,但不自动等于统计信息不可用。

先查 INFORMATION_SCHEMA.COLUMN_STATISTICS,确认采样比例和生成时间;如果比例偏低且关键查询的过滤估算持续偏离,再评估提高 histogram_generation_max_mem_size 或针对目标列重新生成。不要为了追求 1.0 对每张表反复采集。

官方地址:https://dev.mysql.com/doc/refman/8.0/en/analyze-table.html

本文只讨论 MySQL 直方图生成时的采样比例、内存预算和维护判断,不把它混同于 InnoDB 索引基数统计,也不提供虚构的执行结果。

先区分直方图采样与索引统计

直方图是列级统计信息,帮助优化器估计某个列值或范围的分布;它不是索引,也不会为没有访问路径的条件自动创建索引。ANALYZE TABLE 的普通统计和 UPDATE HISTOGRAM 也不是同一件事:前者更新表或索引相关统计,后者针对指定列生成直方图。

因此,检查采样比例前先回答两个问题:目标查询是否真的依赖这列的分布估算,以及当前直方图是否已经存在。若查询变慢实际来自锁等待、磁盘读取或索引缺失,单纯把采样比例提高到 1.0 也不会解决根因。

从 COLUMN_STATISTICS 读取 sampling-rate

MySQL 把列直方图放在 INFORMATION_SCHEMA.COLUMN_STATISTICS 中,具体分布信息位于 HISTOGRAM JSON。下面的查询只读取元数据,不修改表;把示例库名、表名和列名替换成自己的对象。

-- 读取目标列直方图的生成时间、采样比例和桶数。
SELECT
    SCHEMA_NAME,
    TABLE_NAME,
    COLUMN_NAME,
    HISTOGRAM->>'$."sampling-rate"' AS sampling_rate,
    HISTOGRAM->>'$."last-updated"' AS updated_utc,
    HISTOGRAM->>'$."histogram-type"' AS histogram_type,
    HISTOGRAM->>'$."number-of-buckets-specified"' AS requested_buckets,
    JSON_LENGTH(HISTOGRAM->'$.buckets') AS actual_buckets
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE SCHEMA_NAME = 'shop'
  AND TABLE_NAME = 'orders'
  AND COLUMN_NAME = 'fulfillment_state';

如果查询返回空集,先确认库、表、列名和当前账号可见权限,再区分“从未生成直方图”和“已有直方图但数据后来发生变化”。如果能读到 HISTOGRAM,重点关注 sampling-rate、last-updated 和桶信息。

这里的比例描述的是上一次生成动作:1.0 说明当时没有因为直方图内存预算而减少读取范围;小于 1.0 说明 MySQL 使用了采样。它不表示当前数据仍然有多大比例被统计,也不表示查询返回结果的命中率。

COLUMN_STATISTICS 中的直方图 JSON、sampling-rate 与维护判断之间的数据关系说明图
图1:采样比例检查结构图,展示 COLUMN_STATISTICS 中的 sampling-rate 如何连接到维护判断;这是静态说明图,不是数据库截图或运行证据。

结合内存上限判断为何发生采样

生成直方图时,MySQL 需要把用于构建分布的数据读入内存。如果估算需要的内存超过 histogram_generation_max_mem_size,官方文档描述的行为是改用均匀分布的页级采样,而不是无条件读取整张表。采样比例因此首先是资源预算的结果。

-- 查看当前会话的直方图生成内存上限,避免把全局值误当成会话值。
SHOW VARIABLES LIKE 'histogram_generation_max_mem_size';

-- 仅在评估过资源成本的维护窗口中临时提高会话预算。
-- 数值只是示例,不能直接套用到所有实例。
SET SESSION histogram_generation_max_mem_size = 2000000;

-- 只为明确需要的列生成直方图,并控制请求的桶数。
ANALYZE TABLE shop.orders
UPDATE HISTOGRAM ON fulfillment_state WITH 32 BUCKETS;

示例中的 2000000 和 32 只是语法演示。内存预算越大并不意味着应该无限提高:采集本身会消耗读取和内存资源,桶数也要匹配列的取值分布。先记录原值、目标列、请求桶数和维护窗口,才能在生成后解释比例变化。

histogram_generation_max_mem_size 影响 MySQL 直方图全量读取与页级采样的关系说明图
图2:直方图生成关系说明图,展示内存上限影响全量读取或页级采样的概念分支;这是静态结构图,不是运行结果。

把采样比例和直方图内容放在一起读

采样比例不是唯一指标。读到 sampling-rate 后,还要一起看 histogram-type、请求桶数和实际桶数。取值很少的列可能形成 singleton,实际桶数小于请求桶数不一定是失败;连续值或高度倾斜的列则要关注桶是否足以表达关键区间。

-- 把 HISTOGRAM JSON 保留下来,便于刷新前后做人工对比。
SELECT HISTOGRAM
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE SCHEMA_NAME = 'shop'
  AND TABLE_NAME = 'orders'
  AND COLUMN_NAME = 'fulfillment_state';

-- 只读取 JSON 中的采样比例,不把它当作百分比字符串参与比较。
SELECT
    HISTOGRAM->>'$."sampling-rate"' AS sampling_rate
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE SCHEMA_NAME = 'shop'
  AND TABLE_NAME = 'orders'
  AND COLUMN_NAME = 'fulfillment_state';

如果采样比例较低,但列分布平滑、查询估算稳定,可能已经足够。反过来,比例接近 1 也不保证直方图永远准确:批量导入、集中修改状态或新取值大量出现后,生成时读到的全量数据也会变成历史数据。

选择调整预算还是重新生成

一个可复盘的维护动作应把“为什么处理”和“处理了什么”写清楚。先用已有监控或业务汇总确认目标列分布变化,再选定列执行 UPDATE HISTOGRAM。不要为了提升比例而把整张表所有列一起重建。

-- 先查看当前列的直方图,保留刷新前的 JSON 和采样比例。
SELECT HISTOGRAM
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE SCHEMA_NAME = 'shop'
  AND TABLE_NAME = 'orders'
  AND COLUMN_NAME = 'fulfillment_state';

-- 只更新已确认会影响关键过滤估算的列。
ANALYZE TABLE shop.orders
UPDATE HISTOGRAM ON fulfillment_state WITH 32 BUCKETS;

-- 刷新后再次读取元数据,记录新的比例和生成时间。
SELECT HISTOGRAM->>'$."sampling-rate"' AS sampling_rate,
       HISTOGRAM->>'$."last-updated"' AS updated_utc
FROM INFORMATION_SCHEMA.COLUMN_STATISTICS
WHERE SCHEMA_NAME = 'shop'
  AND TABLE_NAME = 'orders'
  AND COLUMN_NAME = 'fulfillment_state';

如果新的比例仍然偏低,不要立即连续重跑同一语句。先考虑表规模、列宽、实例内存和采集窗口,确认提高会话预算是否可承受。若为了保持线上稳定只能接受部分采样,就应把这个事实记录进维护说明,而不是把它伪装成全量结果。

用估算偏差决定是否值得处理

最终判断应落在真实查询上:同一个谓词、相近的数据快照和可比较的参数,是否出现了稳定的估算偏差。如果 EXPLAIN 估算已经接近实际,刷新直方图可能没有收益;如果关键取值比例发生变化且过滤估算持续偏离,再把采样比例与内存预算纳入处理方案。

-- 先看优化器估算,不执行目标查询。
EXPLAIN FORMAT=JSON
SELECT id
FROM shop.orders
WHERE fulfillment_state = 'waiting';

-- 只有在可承受的环境或维护窗口中,才观察实际执行数据。
-- 该语句会真实执行查询,耗时和读取量必须由现场环境决定。
EXPLAIN ANALYZE
SELECT id
FROM shop.orders
WHERE fulfillment_state = 'waiting';
观察到的情况处理建议
sampling-rate=1,估算稳定保持现状,按数据变化观察
比例较低,但估算与实际接近不必为了追求 1.0 反复重采集
比例较低,关键取值估算持续偏离评估会话内存预算、桶数和目标列的定向刷新
比例较高,刷新后仍然偏离检查数据快照、列相关性、索引和其他访问路径

常见问题

sampling-rate 小于 1 是不是采集失败? 不是。它通常表示受内存预算影响采用了页级采样,是否可接受要结合列分布和查询估算判断。

把 histogram_generation_max_mem_size 调大就一定能变成 1 吗? 不一定。还要看表规模、列数据和本次生成的内存需求;调整会话值也必须在资源可承受的窗口进行。

为什么刷新后执行计划没有变化? 优化器可能仍然认为原访问路径成本最低,或者当前估算并非由这份直方图主导。生成时间更新不等于计划必须改变。

普通 ANALYZE TABLE 会自动修复直方图吗? 不应一概而论。普通表统计与显式直方图管理有不同语法和更新语义,是否联动还要以所用 MySQL 版本的官方文档为准。

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