文章目录
- 每日一句正能量
- 1. 背景与问题:优化器不是看真实数据做计划,而是看“统计摘要”做判断
- 2. 环境与数据:为什么增长型大表最容易出现统计滞后
- 2.1 为什么表越大,“20%变化”越可怕?
- 2.2 表总行数增长只是第一层问题
- 2.3 MCV为什么重要?
- 2.4 n_distinct为什么会影响GROUP BY和JOIN?
- 2.5 日期直方图特别容易在追加型表里过期
- 3. 复现过程:ANALYZE前后,同一SQL从21秒降到3秒
- 3.1 基线SQL
- 3.2 采集前计划
- 3.3 先确认统计是否陈旧
- 3.4 单变量实验:只执行ANALYZE
- 3.5 ANALYZE后计划
- 3.6 为什么ANALYZE后计划会变?
- 4. 方案实施:普通ANALYZE还不够时,怎么继续提高判断精度
- 4.1 第一层:建立“统计新鲜度”指标
- 4.2 第二层:超大表单独调整auto analyze阈值
- 4.3 不要全库统一把scale factor调很低
- 4.4 第三层:提高关键列statistics target
- 4.5 为什么不建议全局statistics target直接1000?
- 4.6 第四层:多列相关条件使用扩展统计
- 4.7 创建扩展统计
- 4.8 扩展统计解决什么问题?
- 4.9 第五层:采集后做计划回归
- 4.10 统计采集后的验证指标
- 4.11 什么时候不要第一时间加Hint?
- 4.12 统计信息不是越“实时”越好
- 4.13 超大追加表要结合业务波次
- 4.14 分区表应关注“新分区”统计
- 4.15 统计信息采样存在天然误差
- 5. 结果对比:统计采集本身就能让SQL从21.6秒降到3.4秒
- E0:旧统计
- E1:普通ANALYZE
- E2:提高关键列statistics target
- E3:扩展统计
- 5.1 汇总
- 5.2 优化收益不只是Execution Time
- 5.3 统计信息还会影响Hash内存
- 5.4 统计信息也会影响GROUP BY
- 5.5 P99更能暴露热点估算错误
- 6. 风险与复盘:统计信息采集不是“越多越好”,而是要让优化器获得足够准确的世界模型
- 6.1 风险一:全库ANALYZE造成资源冲击
- 6.2 风险二:statistics target过高
- 6.3 风险三:只看last_analyze时间
- 6.4 风险四:自动ANALYZE阈值太低
- 6.5 风险五:扩展统计对象太多
- 6.6 风险六:统计更新后计划变化
- 6.7 风险七:出现回归就删除新统计
- 推荐统计治理模型
- 推荐诊断顺序
- 回退方案
- 最终复盘
- 附录 A:最小统计采集
- 附录 B:提高列统计目标
- 附录 C:扩展统计
- 附录 D:最低验收门禁
每日一句正能量
珍惜是柴米油盐里小心翼翼的呵护。
珍惜不是空话,是做饭时记得对方的口味,是疲惫时递上的一杯热水。浪漫的真谛,就藏在日复一日的琐碎与平淡中,被一双珍视的眼睛看见,被一双用心的手打捞起来。
主题:统计信息 / 增长型大表 / 优化器判断
重点:ANALYZE、estimated rows、actual rows、MCV、直方图、n_distinct、default_statistics_target、扩展统计、auto analyze、执行计划前后对比
适用场景:KingbaseES 中订单、流水、日志、交易明细、审计记录等持续快速增长的大表,尤其适用于“SQL 没变、索引没变,但某天开始突然变慢”的生产问题。
1. 背景与问题:优化器不是看真实数据做计划,而是看“统计摘要”做判断
数据库优化器在生成计划时,并不会真的先执行:
SELECT COUNT(*)去确认每个条件会返回多少行。
它必须在 SQL 真正运行之前,根据:
表行数 页面数 NULL比例 n_distinct 最常见值 MCV 直方图 列相关性估算:
这个条件会返回多少行? 这个 Join 两边有多大? 应该走索引还是全表扫描? 应该 Nested Loop 还是 Hash Join? Hash 要准备多少内存? Sort 会处理多少行?KingbaseES 官方优化器统计信息文档明确说明,数据库采用基于成本的优化器 CBO,而统计信息就是代价估算的基础。统计信息是通过采样收集的数据概览,不是查询执行时的实时真相。
因此统计信息一旦过期,真正发生的事情不是:
数据库不知道表有多少行这么简单。
而是:
优化器的整个成本模型开始建立在一个已经过时的数据世界里。
例如:
统计采集时: 表 1亿行 租户A占5% 两个月后: 表 3亿行 新增数据大量属于租户A 租户A实际占30% 统计仍然认为: 租户A≈5%那么查询:
WHEREtenant_id='A'优化器可能估:
150万实际却:
9000万这会直接影响:
Index Scan vs Bitmap/Seq Scan以及:
Nested Loop vs Hash Join的选择。
KingbaseES 官方 SQL 调优资料也明确指出:陈旧统计信息可能使优化器产生低效执行计划。
本文的核心观点是:
统计信息过期不是一个“维护动作没做”的小问题,而是优化器用于判断 Scan、Join、Aggregate、Sort 和内存成本的输入数据已经失真。
2. 环境与数据:为什么增长型大表最容易出现统计滞后
示例系统:
数据库: KingbaseES V9 表: growing_order 初始: 1亿行 两个月后: 3亿行 每日新增: 800万~1200万 热点租户: 过去5% 现在30%+ 热点状态: status=1 比例从20%增长到75% 最近日期: 新增数据高度集中表结构:
CREATETABLEgrowing_order(order_idBIGINTPRIMARYKEY,tenant_idBIGINTNOTNULL,customer_idBIGINTNOTNULL,statusINTNOTNULL,biz_dateDATENOTNULL,amountNUMERIC(18,2));索引:
CREATEINDEXidx_growing_order_tenant_dateONgrowing_order(tenant_id,biz_date);CREATEINDEXidx_growing_order_tenant_status_dateONgrowing_order(tenant_id,status,biz_date);2.1 为什么表越大,“20%变化”越可怕?
KingbaseES 官方自动清理参数文档说明,自动 ANALYZE 的触发与:
autovacuum_analyze_threshold + autovacuum_analyze_scale_factor × 表规模有关。
默认参数中:
autovacuum_analyze_threshold = 50 autovacuum_analyze_scale_factor = 0.2意味着随着表越来越大:
按比例触发所需的变化行数也越来越大。
如果表已经:
5亿行20% 就是:
1亿行即使业务每天新增:
1000万统计信息也可能在一段时间内无法完全反映最新分布。
注意:
“新增1000万”并不等于一定立刻触发一次你期望的统计刷新。
实际还受:
自动维护调度 数据库负载 表级设置 版本参数影响。
因此增长型大表应该有:
自己的统计新鲜度策略而不是全部依赖默认阈值。
2.2 表总行数增长只是第一层问题
更危险的是:
分布发生改变例如过去:
tenant_id=999999 只有10万订单某次业务迁入以后:
突然增加800万表总行数只增加:
4%看起来变化不大。
但这个租户的选择性已经从:
极高选择性变成:
低选择性这会直接改变:
索引扫描是否还划算所以:
统计新鲜度不能只看“整表变了多少”,还要看关键业务值的分布是否发生跃迁。
2.3 MCV为什么重要?
假设:
tenant_id存在大量普通租户:
每个只有1万~5万行但一个超级租户:
800万如果超级租户进入:
Most Common Values优化器能对它使用更准确的频率。
如果采样没有捕捉到,或者旧统计里的频率已经过期:
优化器就可能用平均分布估算这对热点参数非常危险。
2.4 n_distinct为什么会影响GROUP BY和JOIN?
n_distinct描述:
某列大约有多少不同值它会影响:
GROUP BY组数 Join结果规模 选择率例如:
customer_id从:
1000万个distinct增长到:
6000万个但旧统计仍停留在:
1000万优化器对:
HashAggregate Hash Join内存和结果规模的估算都会受到影响。
2.5 日期直方图特别容易在追加型表里过期
订单、流水、日志这类表:
数据不断向时间轴右侧追加旧统计采集时:
最大日期=2026-05-01现在:
已经到2026-08-01如果查询:
WHEREbiz_date>='2026-07-01'统计信息没有及时更新,就可能很难准确判断:
最近一个月到底有多少数据这就是典型:
Ascending Column / Append-only Distribution问题。
3. 复现过程:ANALYZE前后,同一SQL从21秒降到3秒
3.1 基线SQL
SELECTo.order_id,o.customer_id,o.amountFROMgrowing_order oWHEREo.tenant_id=:tenant_idANDo.status=1ANDo.biz_date>=:start_dateANDo.biz_date<:end_date;执行:
EXPLAIN(ANALYZE,BUFFERS,VERBOSE)SELECT...;热点参数:
tenant_id=999999 status=1 最近90天3.2 采集前计划
示例:
Index Scan estimated rows=18,000 actual rows=8,200,000误差:
455倍如果后面继续 Join:
customer优化器可能认为:
只有1.8万行于是选择:
Nested Loop结果:
Inner loops 数百万P95:
21.6sBuffers Read:
980万这时很多 DBA 第一反应:
索引不行 Join算法不行但真正的第一个问题应该是:
为什么18000变成了820万?3.3 先确认统计是否陈旧
需要记录:
上次ANALYZE时间 表当前大概行数 上次统计时行数 近期INSERT/UPDATE/DELETE 热点参数增长不要只看:
SQL慢就直接执行:
CREATE INDEX3.4 单变量实验:只执行ANALYZE
这是整篇最重要的实验方法。
不要同时:
改索引 改SQL 调work_mem 改Join开关只执行:
ANALYZEgrowing_order;然后:
同一SQL 同一参数 同一测试环境再次运行。
3.5 ANALYZE后计划
示例:
estimated rows=6,900,000 actual rows=8,200,000估算误差从:
455倍降到:
约1.19倍计划:
Index Scan + Nested Loop变成:
Bitmap Scan + Hash JoinP95:
21.6s →3.4sBuffers:
980万 →190万这里已经可以证明:
主要根因不是 SQL 突然写坏,而是旧统计让优化器严重低估热点参数。
3.6 为什么ANALYZE后计划会变?
因为优化器重新获得了:
表规模 热点频率 直方图 distinct NULL比例等数据概览。
然后重新计算:
Index Scan成本 Bitmap Scan成本 Seq Scan成本 Join成本原来:
820万行在优化器世界里只有:
1.8万行现在:
终于接近真实值所以计划自然会改变。
4. 方案实施:普通ANALYZE还不够时,怎么继续提高判断精度
4.1 第一层:建立“统计新鲜度”指标
不能只监控:
CPU 连接数 锁还要监控:
last_analyze last_autoanalyze 表增长量 修改行数 reltuples对于:
关键大表建议定义:
Freshness SLA例如:
热点交易表: 统计延迟不超过6小时 历史归档表: 不超过7天不同表不能一刀切。
4.2 第二层:超大表单独调整auto analyze阈值
示例:
ALTERTABLEgrowing_orderSET(autovacuum_analyze_threshold=50000,autovacuum_analyze_scale_factor=0.02);这是:
方法示例不是通用推荐值。
如果表:
5亿行scale factor:
0.2对应比例变化量非常大。
调整到:
0.02可以更早触发。
但采集频率也会增加。
所以必须平衡:
统计新鲜度 vs ANALYZE资源开销4.3 不要全库统一把scale factor调很低
如果:
10000张表全部设置:
0.01可能导致:
后台ANALYZE频繁执行 CPU/IO增加正确方式:
按增长速度分层例如:
G0: 静态维表 G1: 普通OLTP表 G2: 增长型大表 G3: 热点超大事实表只对:
G2/G3做特殊策略。
4.4 第三层:提高关键列statistics target
KingbaseES 官方 ANALYZE 文档说明:
default_statistics_target控制默认统计信息量。
也可以:
ALTERTABLE...ALTERCOLUMN...SETSTATISTICS...单独提高某列目标。
例如:
ALTERTABLEgrowing_orderALTERCOLUMNtenant_idSETSTATISTICS500;然后:
ANALYZEgrowing_order;目的:
更多MCV 更多直方图桶 更大采样提高热点和长尾估算质量。
4.5 为什么不建议全局statistics target直接1000?
因为统计目标越大:
采样更多 ANALYZE更慢 sys_statistic更大 规划时间也可能略增KingbaseES 官方文档明确指出,提高统计目标是在:
估算精度 ANALYZE时间 统计目录空间之间做权衡。
所以应该:
只提高关键列例如:
tenant_id biz_date status region_id4.6 第四层:多列相关条件使用扩展统计
查询:
WHEREtenant_id=:tANDstatus=1ANDbiz_date>=:d单列统计通常会把:
tenant_id status biz_date选择率近似组合。
如果三个条件高度相关:
估算可能仍然错误KingbaseES 官方优化器统计文档明确指出,单列统计无法表达多列之间的相关性,因此提供:
dependencies ndistinct MCV扩展统计。
4.7 创建扩展统计
CREATESTATISTICSst_order_tenant_status_date(dependencies,ndistinct,mcv)ONtenant_id,status,biz_dateFROMgrowing_order;注意:
CREATE STATISTICS只是建立统计对象。
官方文档明确说明:
真正的数据采集还必须执行 ANALYZE。
所以接着:
ANALYZEgrowing_order;4.8 扩展统计解决什么问题?
例如:
超级租户的status=1比例=95% 普通租户=20%单独看:
tenant_id和:
status都无法完整表达:
两者的关联关系扩展统计可以帮助优化器更好判断:
tenant + status组合选择率。
4.9 第五层:采集后做计划回归
ANALYZE不是:
做完就结束因为官方文档也提醒:
统计变化可能使规划器选择发生变化大多数情况下是改善。
但对:
边界SQL也可能从一个计划切到另一个计划。
所以统计采集要有:
计划回归至少检查:
Top SQL 核心接口 长查询 批处理4.10 统计采集后的验证指标
每条关键 SQL 保存:
Plan Hash / Plan Shape estimated rows actual rows Buffers P95/P99 CPU Temp目标不是:
计划必须不变而是:
新计划是否更符合真实数据4.11 什么时候不要第一时间加Hint?
KingbaseES SQL 调优资料提到,当统计信息误差仍无法解决时,可以考虑使用 Hint 控制执行计划。
但顺序应该是:
统计新鲜度 ↓ statistics target ↓ 扩展统计 ↓ SQL/索引 ↓ 最后才考虑Hint如果一开始:
强制Nested Loop 强制Index Scan你只是把:
错误统计造成的症状固定住。
数据再增长一次:
Hint很可能再次变成坏计划4.12 统计信息不是越“实时”越好
每执行一批:
1000行INSERT就 ANALYZE 一次:
没有必要采样本身有成本。
合理目标应该是:
统计变化足以影响计划时 及时更新而不是:
每秒和真实数据完全同步4.13 超大追加表要结合业务波次
例如:
每天凌晨批量导入3000万最适合:
导入完成 ↓ 建/维护索引 ↓ ANALYZE ↓ 报表放量而不是等:
自动ANALYZE什么时候碰巧触发这和迁移后大批量导入完成需要手工 ANALYZE 的工程原则是一致的。
4.14 分区表应关注“新分区”统计
增长型事实表经常:
按月/日分区新分区:
刚创建 刚装载统计信息可能为空或很弱。
应在:
批量加载完成后明确执行:
ANALYZE新分区/相关对象不要假设旧分区统计可以代表新分区。
4.15 统计信息采样存在天然误差
官方调优指南明确指出:
统计是采样得到的因此即使:
刚刚ANALYZE也不代表:
estimated=actual完全一致。
我们真正关注的是:
误差是否足以改变计划例如:
estimated=700万 actual=820万通常可以接受。
而:
estimated=1.8万 actual=820万就是灾难级偏差。
5. 结果对比:统计采集本身就能让SQL从21.6秒降到3.4秒
E0:旧统计
estimated: 18,000 actual: 8,200,000 Plan: Index Scan + Nested Loop Buffers: 980万 P95: 21.6sE1:普通ANALYZE
estimated: 6,900,000 actual: 8,200,000 Plan: Bitmap Scan + Hash Join Buffers: 190万 P95: 3.4s已经:
6倍+改善。
E2:提高关键列statistics target
tenant_id: 500 biz_date: 500 status: 300重新 ANALYZE。
示例:
estimated: 7,900,000 actual: 8,200,000 P95: 2.8s热点值估算进一步改善。
E3:扩展统计
建立:
tenant_id status biz_date相关性统计。
重新 ANALYZE。
示例:
estimated: 8,100,000 actual: 8,200,000 P95: 2.3sPlan:
稳定5.1 汇总
| 实验 | 统计状态 | Estimated | Actual | 计划 | P95 |
|---|---|---|---|---|---|
| E0 | 过期 | 1.8万 | 820万 | Index+Nested Loop | 21.6s |
| E1 | ANALYZE | 690万 | 820万 | Bitmap+Hash | 3.4s |
| E2 | 高统计目标 | 790万 | 820万 | Bitmap+Hash | 2.8s |
| E3 | 扩展统计 | 810万 | 820万 | 稳定Hash路径 | 2.3s |
以上均为方法演示数据,不是生产实测。
5.2 优化收益不只是Execution Time
Buffers:
980万 →150万说明:
数据库真实读取工作量下降Nested Loop:
数百万Inner loops消失。
CPU:
下降因此这是:
计划质量真正改善而不是:
缓存刚好变热5.3 统计信息还会影响Hash内存
如果优化器估:
Hash Build=1万行实际:
500万那么:
内存预算 Batches 临时文件都会比预想糟糕。
所以统计过期也会间接制造:
Hash Join落盘问题。
这和前面的 Hash Join 文章并不是两个独立主题。
根因链可能是:
统计过期 →低估Build →选Hash →内存不足 →多Batch →Temp爆炸5.4 统计信息也会影响GROUP BY
estimated groups:
1000actual groups:
100万那么:
HashAggregate需要维护的状态量完全不同。
所以统计治理应该覆盖:
Scan Join Aggregate Sort而不是只看索引扫描。
5.5 P99更能暴露热点估算错误
普通参数:
都估得准热点参数:
估错几百倍总体平均:
可能看起来不错真正出问题的是:
少量VIP/大租户所以要按:
参数组看 P95/P99。
6. 风险与复盘:统计信息采集不是“越多越好”,而是要让优化器获得足够准确的世界模型
6.1 风险一:全库ANALYZE造成资源冲击
大型数据库:
几TB 上万张表高峰期直接:
ANALYZE;可能带来明显 IO/CPU。
应该:
按关键表 按波次 按维护窗口执行。
6.2 风险二:statistics target过高
全局:
1000会增加:
采样 ANALYZE时间 统计目录 规划成本优先:
列级提高6.3 风险三:只看last_analyze时间
昨天刚 ANALYZE:
不代表今天就一定准确如果昨晚:
导入5000万热点数据统计已经再次失真。
所以应该同时看:
时间 + 变化量6.4 风险四:自动ANALYZE阈值太低
scale factor:
太低会让超高频写表频繁 ANALYZE。
资源成本可能超过收益。
所以阈值要根据:
增长速度 表大小 查询重要性设计。
6.5 风险五:扩展统计对象太多
所有列组合都建立:
不现实官方文档也指出,列组合数量可能非常庞大,因此扩展统计需要:
人工针对关键相关条件创建只服务:
真正影响计划的列组合6.6 风险六:统计更新后计划变化
ANALYZE 后:
计划改变不是异常。
但需要:
Top SQL回归因为某些临界 SQL 可能改变 Join 顺序或访问路径。
6.7 风险七:出现回归就删除新统计
这也是错误做法。
新统计通常:
更接近真实数据如果某 SQL 因此变差,应先检查:
成本模型 索引 SQL结构 参数敏感而不是第一时间:
恢复错误世界模型推荐统计治理模型
对增长型大表建立:
表规模 + DML增长 + 最近ANALYZE + 关键列分布 + Top SQL估算误差五维监控。
可以定义:
S0: 静态/低变化 S1: 普通业务表 S2: 高增长大表 S3: 高增长+高倾斜核心表S3:
更低auto analyze比例 关键列高statistics target 扩展统计 采集后计划回归这比:
全库统一参数更合理。
推荐诊断顺序
1. 保存慢SQL计划 2. 对比estimated/actual 3. 看表增长和最近ANALYZE 4. 检查热点参数/日期分布 5. 单变量执行ANALYZE 6. 对比计划和P95 7. 必要时提高列statistics target 8. 多列相关则CREATE STATISTICS 9. 再次ANALYZE 10. 固化auto analyze表级策略回退方案
统计治理的回退和普通 SQL 回退不完全一样。
如果调整:
statistics target auto analyze参数 扩展统计后出现回归:
1. 保存新旧计划和参数 2. 不要立即清除所有新统计 3. 若列statistics target过高,恢复原值后重新ANALYZE 4. 若表级autovacuum参数不合适,恢复原storage parameter 5. 若扩展统计被证明有问题,记录证据后DROP并重新ANALYZE 6. 重测普通/热点/长尾参数最重要的是:
统计回退也必须保持单变量,不能同时改SQL、索引、Hint。
否则无法知道真正原因。
最终复盘
统计信息本质上是:
优化器对数据世界的压缩模型它不需要:
100%精确但必须:
足够准确到不会选错成本数量级对于增长型大表,最危险的不是:
表从1亿变3亿本身。
而是:
热点分布 日期分布 distinct 列相关性都已经变化,优化器仍然使用过去的概率模型。
如果只记住一句话:
统计信息过期真正破坏的不是“行数显示”,而是优化器判断索引、扫描、Join、Hash、Sort 和 Aggregate 的整个成本基础;当 estimated 与 actual 跨越几个数量级时,应该先修正统计世界模型,再去改 SQL 和索引。
这也是为什么生产调优时:
EXPLAIN ANALYZE里的:
estimated rows vs actual rows永远是最值得先看的数据之一。
附录 A:最小统计采集
ANALYZEgrowing_order;附录 B:提高列统计目标
ALTERTABLEgrowing_orderALTERCOLUMNtenant_idSETSTATISTICS500;ANALYZEgrowing_order;附录 C:扩展统计
CREATESTATISTICSst_order_tenant_status_date(dependencies,ndistinct,mcv)ONtenant_id,status,biz_dateFROMgrowing_order;ANALYZEgrowing_order;附录 D:最低验收门禁
[ ] 上次ANALYZE时间已记录 [ ] 表增长量已记录 [ ] 热点参数已覆盖 [ ] estimated/actual已比较 [ ] ANALYZE单变量实验已执行 [ ] statistics target有依据 [ ] 扩展统计仅用于关键相关列 [ ] 采集后Top SQL已回归 [ ] P95/P99达到SLA [ ] Buffer/CPU无异常回归 [ ] 自动采集阈值已文档化 [ ] 回退参数已保存转载自:https://blog.csdn.net/u014727709/article/details/163950186
欢迎 👍点赞✍评论⭐收藏,欢迎指正