GBase 8a 宽表查询里的压缩和行存列取舍
我最近看 GBase 8a 存储优化资料时,对"列存天然快"这句话更谨慎了一些。GBase 8a 的优势确实很大程度来自 MPP、列存、压缩、智能索引和并行执行,但真正落到现场时,并不是所有宽表查询都会自然变快。尤其是 select *、返回列很多、命中记录很离散的场景,列存的 I/O 优势可能被大量离散读取抵消。
这类问题和单纯 SQL 慢不一样。SQL 本身可能不复杂,过滤条件也正常,但业务就是要一次拉很多列做明细展示。此时只盯分布键、并行度、连接顺序,容易漏掉存储形态本身。我的理解是,GBase 8a 的列存压缩和行列混存要一起看:前者解决存储和扫描效率,后者解决特定宽行返回场景的离散 I/O。
列存压缩不是越高越合适
GBase 8a 支持实例级、表级、列级压缩策略,资料里也提到列级优先级高于表级,表级高于实例级。这个设计很适合做精细化治理,但现场如果只追求压缩比,很容易把热表查询和加载性能拖慢。
压缩策略要结合访问模式,而不是只看磁盘节省多少。比如历史归档表很少更新,查询也多是按日期过滤后做聚合,高压缩比更有价值;但高频加载的近实时明细表,如果压缩和解压成本明显影响入库窗口,就要更谨慎。
| 数据类型 | 访问特点 | 压缩倾向 | 备注 |
|---|---|---|---|
| 热明细表 | 持续加载,频繁查询最近数据 | 均衡或高性能压缩 | 优先保证写入窗口和响应 |
| 冷历史表 | 基本不改,偶尔查询 | 高压缩比 | 节省空间和 I/O 更重要 |
| 高基数字符列 | 唯一值多,长度不稳定 | 单独评估 | 压缩收益可能不稳定 |
| 状态码/枚举列 | 重复值高 | 适合压缩 | 列存优势明显 |
| 宽文本字段 | 查询频率低 | 可列级策略 | 避免拖累常用列 |
示例写法可以按实例、表、列三个粒度考虑。不同版本压缩算法名称和取值要以当前环境为准,下面只展示治理思路。
sql
-- 表级指定压缩策略示例
CREATE TABLE dwd.t_user_action_202605 (
user_id bigint,
action_time datetime,
action_type varchar(32),
province varchar(32),
remark varchar(512)
) COMPRESS(5,5);
-- 对特殊列单独指定压缩,示例语法需结合当前版本验证
CREATE TABLE dwd.t_user_profile (
user_id bigint COMPRESS(5),
city_code varchar(16) COMPRESS(5),
profile_txt varchar(2048) COMPRESS(3)
);
我在现场更喜欢先拿一份真实数据压测,而不是凭感觉设置。压缩比、加载耗时、查询耗时、CPU 使用率这几个指标要一起看。
sql
-- 压测表:默认策略
CREATE TABLE test.t_action_default AS
SELECT * FROM dwd.t_user_action_202605 WHERE 1=0;
-- 压测表:调整策略
CREATE TABLE test.t_action_compress AS
SELECT * FROM dwd.t_user_action_202605 WHERE 1=0;
实际压测时导入同一批数据,再对比存储占用和典型 SQL 耗时。压缩策略一旦用于 TB 级历史表,后面再改会牵涉重建和数据搬迁,前期多测几轮更划算。
select 星号是宽表最常见的坑
GBase 8a 的列存对"只访问少量列"的分析查询非常友好,因为没涉及的列不需要参与读取。但如果业务大量使用 select *,列存需要把很多列都读出来,再进行物化,优势就会下降。资料里对这个限制也有说明:访问列数较多、记录又很离散时,会造成大量离散 I/O。
我会先把宽表 SQL 分成两类:
| 查询类型 | 示例 | 优化重点 |
|---|---|---|
| 聚合分析 | select city, count(*) ... group by city |
分布键、过滤列、智能索引、列裁剪 |
| 明细拉取 | select * from t where user_id in (...) order by time |
返回列控制、行存列、分页策略 |
明细拉取不要盲目套聚合查询的优化方法。它的问题常常不是计算,而是把很多分散行的很多列拼出来。
sql
-- 不建议作为默认接口写法
SELECT *
FROM dwd.t_user_action
WHERE user_id = 1008611
ORDER BY action_time DESC
LIMIT 100;
-- 更可控的写法:先明确投影列
SELECT
user_id,
action_time,
action_type,
province,
channel
FROM dwd.t_user_action
WHERE user_id = 1008611
ORDER BY action_time DESC
LIMIT 100;
如果业务确实需要大量列,比如客服明细页、监管导出、交易流水回放,就要评估行列混存能力,而不是要求开发永远不查宽列。
行存列适合解决特定明细查询
GBase 8a 支持行列混存,通过冗余行存储来提升典型 SELECT * 场景性能。我的理解是,它不是替代列存,而是在列存表上给某些高频明细访问准备一条更适合物化的路径。资料里提到,行存列以更小粒度 Data Page 读取数据,减少无效 I/O;也提到 gbase_hybrid_store 可以控制查询时是否使用行存数据。
我会在下面几类场景考虑行存列:
| 场景 | 是否适合 | 原因 |
|---|---|---|
| 单表明细查询,返回列多 | 适合评估 | 行存列可减少离散物化成本 |
| 聚合统计为主 | 不适合优先 | 列存本身更有优势 |
| join/group 复杂计算 | 谨慎 | 行存列不用于所有运算阶段 |
| 高频 order by 明细页 | 适合评估 | 资料示例更接近这类物化场景 |
| 超宽字段全部冗余 | 谨慎 | 冗余空间和维护成本高 |
参数上我一般会先保持自动判断,再用会话级强制对比验证。
sql
-- 自动判断是否使用行存列
SET gbase_hybrid_store = 1;
-- 排查或压测时强制不使用
SET gbase_hybrid_store = 0;
-- 压测时强制使用,有则使用
SET gbase_hybrid_store = 2;
还有两个参数需要关注:行存列页大小 gbase_hybrid_store_page_size,以及平均每个 DC 返回记录数上限 _gbase_hybrid_store_limit。这些参数不要直接生产调整,先在测试环境用真实 SQL 对比。
sql
SHOW VARIABLES LIKE 'gbase_hybrid_store%';
SHOW VARIABLES LIKE '_gbase_hybrid_store_limit';
行存列不是越多越好
行存列本质上有冗余,冗余就会带来空间和维护成本。INSERT、快速 UPDATE、DELETE、LOAD 等动作会维护冗余数据,资料里提到这部分对用户透明,但透明不等于没有代价。宽表上如果随便把所有列都放进行存列,可能把空间、省 I/O、维护成本之间的平衡打破。
我更倾向于按"接口画像"建行存列,而不是按表字段全量冗余。
text
接口A:客服查询最近100条行为
常用列:user_id, action_time, action_type, channel, province, device_type
排序列:action_time
过滤列:user_id
接口B:监管导出全字段
频率:每月一次
处理方式:不为它单独设计行存列,走离线导出窗口
这种分析之后,行存列只覆盖接口 A 的高频列,接口 B 不因为偶发需求拖累整个表。
| 决策项 | 建议 |
|---|---|
| 行存列字段 | 选高频投影列,不盲目全列 |
| 宽文本字段 | 低频时不纳入 |
| 多个接口 | 可以拆多个行存列,但要控制数量 |
| 建立时机 | 先压测,再对大表实施 |
| 删除调整 | 旧定义不合适时按规范重建 |
资料里还提到行存列不支持直接修改定义,只能先删除再重建;原始列定义也有一些限制。这个在变更评审里要提前说明,不要等业务上线后频繁改字段。
压缩和行存列要一起算账
一个典型例子:用户行为明细表 300 列,日增 2 亿行。大部分报表只看 10 到 20 列,列存压缩效果很好;但客服系统经常按用户查最近行为,需要返回 80 列。如果只按报表视角设计,客服查询会慢;如果只按客服视角全量行存冗余,存储成本又会上去。
我会用下面这张表做方案比较。
| 方案 | 优点 | 缺点 | 适用情况 |
|---|---|---|---|
| 纯列存 + 默认压缩 | 存储省,聚合快 | 宽行明细物化慢 | 报表分析为主 |
| 纯列存 + 调整压缩 | 可平衡加载和查询 | 不能根治离散宽查 | 热数据压力明显 |
| 列存 + 精简投影 | 改造成本低 | 需要应用配合 | 接口可改字段 |
| 列存 + 行存列 | 明细宽查改善明显 | 有冗余和维护成本 | 高频单表明细 |
| 拆宽表 | 模型更清晰 | 改造成本大 | 访问模式分层明显 |
真正落到现场时,我会先推动应用去掉无意义的 select *,再用行存列解决剩下的刚性宽查。顺序不能反。否则行存列会变成替开发兜底的万能选项,后面很难管理。
小结
GBase 8a 的列存、压缩、智能索引确实适合分析型负载,但宽表明细查询要单独看。我的经验是,压缩策略先按冷热和列特征分层,行存列按高频接口画像设计,gbase_hybrid_store 用来压测和排查,不要在没有对比数据时直接拍脑袋。
列存解决的是大规模分析效率,行存列解决的是特定明细物化效率。两者不是谁替代谁,而是要按查询形态取舍。
参考资料
text
南大通用GBase 8a MPP Cluster性能优化之存储优化 https://www.gbase.cn/community/post/4439
数据分布式存储 https://www.gbase.cn/docs/gbase-8a/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/product-overview/product-enterprise-enhance-feature/product-data-distributed-storage
南大通用GBase 8a MPP Cluster产品技术简介(一) https://www.gbase.cn/community/post/4209
南大通用GBase 8a不同压缩算法对表和列的数据压缩的使用方法 https://www.gbase8.cn/5087