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 使用了采样。它不表示当前数据仍然有多大比例被统计,也不表示查询返回结果的命中率。

结合内存上限判断为何发生采样
生成直方图时,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 只是语法演示。内存预算越大并不意味着应该无限提高:采集本身会消耗读取和内存资源,桶数也要匹配列的取值分布。先记录原值、目标列、请求桶数和维护窗口,才能在生成后解释比例变化。

把采样比例和直方图内容放在一起读
采样比例不是唯一指标。读到 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 版本的官方文档为准。
-
417 收藏
-
493 收藏
-
257 收藏
-
Golang · Go教程 | 1星期前 | goroutine · HTTP服务 · pprof · Go教程 · Go goroutine泄漏 Go诊断 net/http/pprof goroutine profile pprof.Handler318 收藏
-
240 收藏
-
137 收藏
-
488 收藏
-
428 收藏
-
152 收藏
-
498 收藏
-
493 收藏
-
325 收藏
-
117 收藏
-
207 收藏
-
331 收藏
-
101 收藏
-
160 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习