GBase 8a
性能调优
文章
精选

GBase 8a 执行计划跑偏了?统计信息管理是关键

发表于2026-03-23 10:21:3242次浏览4个评论

GBase 8a 执行计划跑偏了?统计信息管理是关键

有时候明明写了很简单的 SQL,执行计划却完全不符合预期,扫描了大量不必要的数据。这类问题十有八九是统计信息不准确导致优化器做出了错误判断。本文从统计信息的基本原理出发,结合实际运维场景,系统介绍如何管理统计信息、读懂执行计划、以及在优化器判断失误时如何手动干预。

一、什么是统计信息?

GBase 8a 的查询优化器在生成执行计划时,需要估算每个操作涉及的数据量,例如:

  • 这张表大概有多少行?
  • 某个过滤条件能过滤掉多少数据?
  • 两张表 JOIN 后结果集大概多大?

这些估算的依据就是统计信息。统计信息不准确,优化器就会做出错误的判断,导致执行计划跑偏。

GBase 8a 的统计信息主要包含以下几类:

  • 表级统计:行数、数据页数、平均行长度等
  • 列级统计:列的最大值、最小值、NULL 比例、唯一值数量(基数)等
  • 块级统计:粗糙索引记录的每个数据块的 min/max 范围

优化器综合利用这三类信息来估算各个执行路径的代价,从中选出代价最小的方案作为最终执行计划。当统计信息过期或失真时,优化器的代价估算就会出现偏差,轻则选错 JOIN 顺序,重则把本可以快速完成的范围扫描退化成全表扫描。

二、查看统计信息

在排查执行计划问题之前,先看清楚当前统计信息的状态,是定位问题的第一步。

-- 查看表的基本统计信息(行数、数据大小、更新时间等)
SHOW TABLE STATUS FROM analytics_db LIKE 'orders';

-- 查看列的统计信息
SELECT * FROM information_schema.COLUMN_STATISTICS 
WHERE table_name = 'orders';

-- 查看索引统计信息(Cardinality 基数是重要指标)
SHOW INDEX FROM orders;

-- 查看统计信息最后更新时间
SELECT 
    table_name,
    table_rows,
    avg_row_length,
    data_length,
    update_time
FROM information_schema.TABLES
WHERE table_schema = 'analytics_db' AND table_name = 'orders';

重点关注 SHOW INDEX 输出中的 Cardinality(基数)字段,它表示该列估算的唯一值数量。如果 Cardinality 和实际差距很大,优化器对过滤效果的估算就会严重失真,这时需要立刻执行 ANALYZE 更新统计信息。

三、读懂 EXPLAIN 执行计划

更新统计信息之前,应该先用 EXPLAIN 看清楚当前的执行计划,明确问题出在哪里。

-- 基本执行计划
EXPLAIN SELECT SUM(amount) FROM orders 
WHERE create_time >= '2024-01-01' AND region = '华东';

-- 查看分区裁剪情况
EXPLAIN PARTITIONS SELECT SUM(amount) FROM orders 
WHERE create_time >= '2024-01-01';

-- 查看更详细的执行信息
EXPLAIN EXTENDED SELECT SUM(amount) FROM orders 
WHERE create_time >= '2024-01-01';

EXPLAIN 输出中需要重点关注的字段:

字段含义需要警惕的情况
type访问类型出现 ALL 表示全表扫描
rows预估扫描行数数字远大于实际结果集,说明过滤效率差
Extra额外操作出现 Using filesort 或 Using temporary
partitions涉及的分区本应裁剪却扫描了所有分区
key实际使用的索引为 NULL 表示没有用到任何索引

type 字段从好到差的顺序为:system > const > eq_ref > ref > range > index > ALL。生产环境中的分析查询出现 ALL 是需要立刻排查的信号。

四、手动更新统计信息

统计信息不会实时更新,GBase 8a 在后台会周期性地自动收集,但在数据变化较大的场景下,等待自动更新往往来不及,需要手动触发。

-- 更新单张表的统计信息
ANALYZE TABLE orders;

-- 同时更新多张表
ANALYZE TABLE orders, users, products;

-- 分区表只更新指定分区(比全表 ANALYZE 更快)
ALTER TABLE orders ANALYZE PARTITION p2024;

以下情况需要手动触发 ANALYZE:

  1. 大批量数据导入或删除后
  2. 查询执行计划突然变差,性能明显下降
  3. 新建表并导入数据后首次查询前
  4. 分区表新增或删除分区后
  5. SHOW INDEX 中 Cardinality 与实际数据量差距超过 50%

定期自动 ANALYZE 脚本示例,通过 crontab 在每天低峰期自动执行:

#!/bin/bash
# 每天凌晨3点自动更新核心表的统计信息

TABLES="orders users products regions"
for TABLE in $TABLES; do
    gccli -u gbase -p'密码' analytics_db \
        -e "ANALYZE TABLE $TABLE;" \
        >> /var/log/gbase_analyze.log 2>&1
    echo "[$(date)] ANALYZE $TABLE 完成"
done
# 加入 crontab
0 3 * * * /opt/scripts/gbase_analyze.sh

 

五、常见执行计划问题与解决方法

问题1:分区裁剪失效

分区裁剪失效通常是因为 WHERE 条件中对分区键做了函数运算,导致优化器无法静态推断分区范围,只能扫描全部分区。这是最常见的踩坑点之一,在实际项目迁移中非常高发。

-- 错误:YEAR() 函数导致分区裁剪失效,扫描全部分区
SELECT * FROM orders WHERE YEAR(create_time) = 2024;

-- 正确:直接范围条件,优化器能静态推断只扫描 p2024
SELECT * FROM orders 
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

问题2:JOIN 顺序选错

当两张表数据量差异悬殊,但统计信息不准导致优化器误判了小表和大表的角色,会选出错误的 JOIN 驱动顺序,造成大量不必要的数据扫描。遇到这类问题,先执行 ANALYZE 更新统计信息,再用 EXPLAIN 确认执行计划是否恢复正常。

-- 通过 EXPLAIN 确认 JOIN 顺序(输出中第一行是驱动表)
EXPLAIN 
SELECT o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.user_id
WHERE o.create_time >= '2024-01-01';

问题3:大表 JOIN 触发全量 shuffle

MPP 架构下,两张大表按非分片键做 JOIN,会触发全量跨节点数据传输,网络开销极大。优化器在统计信息准确的情况下会自动选择广播小表,但如果统计信息失真导致高估了小表的大小,就可能放弃广播策略,退化成全量 shuffle。

先更新统计信息让优化器重新评估,如果仍然不理想,再考虑用 Hint 强制广播。

-- 先更新统计信息
ANALYZE TABLE users;

-- 若仍不理想,强制广播小表
SELECT /*+ BROADCAST(u) */
    o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.user_id;

 

六、用 Hint 强制指定执行计划

当统计信息不准确或优化器判断持续失误时,可以用 Hint 手动干预执行计划。Hint 写在 SELECT 关键字后的注释块中,不影响 SQL 语义,也不会被不支持 Hint 的工具报错。

GBase 8a 常用的 Hint 类型如下:

-- 强制指定 JOIN 驱动顺序(orders 作为外层驱动表)
SELECT /*+ STRAIGHT_JOIN */ 
    o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.user_id
WHERE o.create_time >= '2024-01-01';

-- 强制小表广播,避免大表跨节点 shuffle
SELECT /*+ BROADCAST(u) */
    o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.user_id;

-- 强制指定查询并行度为4个线程
SELECT /*+ PARALLEL(4) */
    region, SUM(amount)
FROM orders
GROUP BY region;

-- 组合使用多个 Hint
SELECT /*+ BROADCAST(u) PARALLEL(8) */
    o.region, u.username, SUM(o.amount)
FROM orders o
JOIN users u ON o.user_id = u.user_id
GROUP BY o.region, u.username;

Hint 的使用原则:先排查统计信息,再考虑 SQL 改写,最后才用 Hint。Hint 是在统计信息无法及时更新的紧急情况下的临时手段,不应作为长期解决方案。一旦问题根因解决,应及时移除 Hint,否则后续数据分布变化后 Hint 反而可能成为新的性能瓶颈。

 

七、建立统计信息管理规范

一个稳定运行的生产环境,需要把统计信息管理纳入日常运维规范,而不是出了问题再临时处理。建议从以下几个方面建立规范:

1. 数据导入后必须 ANALYZE

任何批量数据导入(LOAD DATA、INSERT SELECT)完成后,将 ANALYZE TABLE 作为导入流程的最后一步,写入数据导入脚本中强制执行,避免遗漏。

2. 定期自动 ANALYZE

对高频写入的核心表,通过 crontab 每天低峰期自动执行 ANALYZE,确保统计信息不会因为持续写入而严重滞后。建议每天凌晨2~4点执行,避开业务高峰。

3. 监控 Cardinality 偏差

定期查询 information_schema.TABLES,对比 table_rows 与实际 COUNT(*) 的差距,差距超过 30% 即触发告警并自动执行 ANALYZE。

4. 执行计划变更监控

对核心查询定期跑 EXPLAIN,对比历史执行计划是否有变化。一旦 typerange 退化到 ALL,或 rows 估算值突然暴增,立刻介入排查统计信息是否失效。

八、注意事项

  1. ANALYZE TABLE 会消耗资源,大表分析耗时较长,建议在业务低峰期执行,避免影响在线查询
  2. 统计信息有一定滞后性,高频写入的表建议每天自动执行一次 ANALYZE,不能完全依赖自动收集
  3. Hint 是最后手段,不要滥用 Hint 绕过优化器,滥用 Hint 会使 SQL 维护成本大幅上升
  4. 执行计划不稳定时优先检查统计信息,而不是急着修改 SQL 或增加硬件投入
  5. 分区表统计信息按分区单独维护,新增分区后记得及时 ANALYZE,否则新分区数据的统计信息为空,优化器无法做出正确判断
  6. 不要在业务高峰期对大表执行 ANALYZE,ANALYZE 期间会对表加读锁,影响并发查询性能

评论

登录后才可以发表评论
用户头像
GBase用户28017发表于 1个月前
优秀。
GBase用户21143发表于 1个月前
感谢分享
经纬发表于 1个月前
GBase 8a 执行计划跑偏?统计信息管理全攻略(运维实战版)
GBase 8a 作为列存 MPP 数据库,优化器100% 依赖统计信息生成执行计划 —— 统计信息不准,优化器就会误判数据分布、行数、过滤率,最终导致全表扫描、数据重分布倾斜、JOIN 顺序错误,简单 SQL 也会跑崩。
这篇是纯生产实战,从原理→问题定位→手动管理→强制纠正,一站式解决执行计划跑偏问题。
一、先搞懂:GBase 8a 统计信息是什么?
GBase 8a 的统计信息是优化器的 “导航地图”,核心存储 3 类关键数据:
表基础信息:总行数、数据块数、磁盘占用
列维度信息:唯一值个数(NDV)、最小值 / 最大值、空值数量
数据分布信息:列数据倾斜度、各节点数据分布
优化器靠这些信息决定:
走索引还是全表扫描
JOIN 用小表广播还是大表 Hash 重分布
过滤条件能筛掉多少数据,是否下推
统计信息不准 = 导航地图画错 → 执行计划必然跑偏。
二、最常见的 3 种 “执行计划跑偏” 场景(全是统计信息背锅)
1. 明明有过滤条件,却强制全表扫描
现象:WHERE id=100 只查 1 行,优化器却扫描全表
原因:统计信息显示该列无索引、唯一值极少、过滤率为 0,优化器认为索引不如全表扫快
2. JOIN 顺序完全错误,大表先 JOIN
现象:小表 JOIN 大表,优化器把大表放前面,产生海量中间数据
原因:统计信息行数不准,把小表识别成大表、大表识别成小表
3. 数据倾斜严重,查询卡在单个节点
现象:部分节点 CPU 100%,其他节点空闲,查询极慢
原因:统计信息未记录数据倾斜,优化器未做倾斜优化
三、核心:GBase 8a 统计信息手动管理(运维必用命令)
GBase 8a 不会自动实时更新统计信息,大批量导入、删除、更新数据后,必须手动收集,这是避免执行计划跑偏的核心。
1. 查看统计信息(判断是否准确)
sql
-- 1. 查看表的统计信息
SHOW TABLE STATUS LIKE '表名';

-- 2. 查看列的详细统计信息(最关键)
SHOW COLUMNS STATISTICS FOR 表名;

-- 3. 查看统计信息最后更新时间
SELECT TABLE_NAME, LAST_ANALYZE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='表名';
判断标准:
行数和实际COUNT(*)偏差超过10% → 不准
唯一值(NDV)明显不符合业务 → 不准
最后更新时间是几天前 / 几周前 → 不准
2. 手动收集统计信息(解决跑偏的第一步)
GBase 8a 专用命令,轻量级、不锁表、业务期可执行:
sql
-- 全量收集表+所有列统计信息(推荐)
ANALYZE TABLE 表名 COMPUTE STATISTICS;

-- 快速采样收集(大表专用,速度快,精度够用)
ANALYZE TABLE 表名 ESTIMATE STATISTICS SAMPLE 20 PERCENT;

-- 只收集单列统计信息(针对过滤/JOIN列精准优化)
ANALYZE TABLE 表名 COMPUTE STATISTICS FOR COLUMNS 列名1,列名2;
3. 删除过期统计信息(极端情况用)
统计信息严重失真时,先删除再重新收集:
sql
DELETE STATISTICS ON 表名;
四、实战:执行计划跑偏了?3 步快速修复
第 1 步:查看执行计划,确认问题
sql
-- 查看执行计划
EXPLAIN
SELECT * FROM 表名 WHERE 条件;

-- 重点看:
-- 1. 是否全表扫描(type: ALL)
-- 2. 预估行数(rows)和实际是否偏差极大
-- 3. JOIN顺序是否合理
第 2 步:更新统计信息(90% 问题直接解决)
sql
-- 直接执行,无需停业务
ANALYZE TABLE 表名 COMPUTE STATISTICS;
执行完成后,新查询会自动使用新统计信息,正在运行的查询不受影响。
第 3 步:手动干预(优化器仍判断失误时)
GBase 8a 支持SQL HINT强制指定执行计划,绕开错误统计信息:
sql
-- 1. 强制使用索引
SELECT /*+ INDEX(表名 索引名) */ * FROM 表名 WHERE 条件;

-- 2. 强制JOIN顺序
SELECT /*+ LEADING(小表 大表) */ * FROM 小表 JOIN 大表 ON 条件;

-- 3. 强制小表广播(解决JOIN慢)
SELECT /*+ BROADCAST(小表) */ * FROM 小表 JOIN 大表 ON 条件;
五、生产运维最佳实践(避免统计信息导致问题)
数据变更后必收集
大批量INSERT/DELETE/UPDATE、数据导入完成后,立即执行ANALYZE TABLE。
定期自动收集
核心大表:每天凌晨低峰期执行一次采样统计
小表:每周执行一次全量统计
优先收集关键列
只针对WHERE、JOIN、GROUP BY列收集,节省资源。
不盲目全表收集
TB 级大表用ESTIMATE STATISTICS采样,避免长时间占用 IO。
流泪猫猫头发表于 1小时前
学习了。