GBase 8a 列存压缩机制详解:编码策略、压缩算法与性能影响
GBase 8a 的存储引擎 Express 是纯列存架构,压缩不是可选功能,而是数据写入的标准流程。理解列存压缩的工作原理,能帮助 DBA 和开发人员在建表时做出更好的数据类型选择,在查询优化时理解 I/O 代价的构成,在容量规划时给出更准确的磁盘估算。本文从列存的基本原理出发,逐一介绍 GBase 8a 支持的编码策略和压缩算法,分析不同数据特征下的最优选择,并给出实际测量压缩效果的方法。
一、列存与行存的压缩差异
理解 GBase 8a 的压缩机制,首先要理解列存储与行存储在数据组织方式上的根本差异,因为压缩效率的高低直接源于此。
行存储(如 MySQL InnoDB)把一行数据的所有字段连续存放在磁盘上。这种布局对 OLTP 场景友好——读取一整行只需一次顺序 I/O,但压缩效果有限,因为同一行中不同字段的数据类型、值域和重复性各不相同,很难找到统一的压缩规律。
列存储把同一列的所有值连续存放在一起。这个看似简单的改变带来了巨大的压缩优势:同一列的所有值共享相同的数据类型和值域,相邻的值往往高度相似甚至完全相同(比如 dept_id 列中大量相同的部门编号,或者 status 列中只有 0/1/2 三个值),压缩算法可以针对这种同质性做深度优化。
GBase 8a 的实测压缩比通常在 3:1 到 10:1 之间,对于重复度极高的枚举型列(如省份、状态码),压缩比甚至可以超过 20:1。这意味着同样的原始数据,在 GBase 8a 中占用的磁盘空间只有 MySQL 的十分之一甚至更少,这是 MPP 分析型数据库能处理 TB 级数据的重要基础之一。
压缩带来的另一个隐性收益是查询性能的提升,这一点常常被忽视。因为磁盘读取是按数据块进行的,压缩后同样大小的数据块能装下更多的行,意味着一次磁盘 I/O 可以读取到更多有效数据。在磁盘 I/O 是查询瓶颈的场景下,压缩率越高,查询速度往往也越快。当然,解压缩本身需要消耗 CPU,在 CPU 已经成为瓶颈的场景下,压缩率并非越高越好,需要在 I/O 节省和 CPU 消耗之间找到平衡点。
二、列存的两层压缩体系
GBase 8a 的 Express 引擎对每列数据采用两层压缩:第一层是编码(Encoding),第二层是通用压缩算法。两层叠加后才是最终存储在磁盘上的数据形态。
这种两层设计的原因在于:编码是针对特定数据模式的"智能变换",能把原始值转换成更规律、更小的表示形式,为后续的通用压缩算法提供更好的输入;通用压缩算法(如 LZ4、ZSTD)则负责在编码输出的基础上做进一步的字节级压缩。两者结合,比单独使用任何一种效果都好。
第一层:编码策略
**字典编码(Dictionary Encoding)**是最常用的编码方式,适合低基数列——即唯一值数量远小于总行数的列。字典编码的原理是:先把列中所有出现过的唯一值建立一个字典(如 {0: "active", 1: "inactive", 2: "pending"}),然后把每行的值替换为对应的字典索引(一个很小的整数)。字典索引通常只需要 1~2 个字节,而原始字符串可能需要 10 多个字节,压缩效果立竿见影。
status、province、dept_id、gender 这类列是字典编码的理想对象。GBase 8a 的 Express 引擎会自动检测列的基数,对符合条件的列自动应用字典编码,不需要手动配置。
**行程编码(Run-Length Encoding,RLE)**专为有大量连续重复值的列设计。比如按时间顺序排列的数据中,order_date 列往往有大量连续相同的日期(同一天的订单聚集在一起);按部门排列的数据中,dept_id 可能有几千行连续相同。RLE 把连续重复的值记录为"(值, 重复次数)"的形式,大量连续重复时压缩率极高。
**差值编码(Delta Encoding)**适合单调递增或变化幅度较小的数值列,如自增 ID、时间戳序列。差值编码存储相邻两个值的差而非原始值。如果 order_id 从 1 连续递增到 10 亿,差值始终是 1,整列只需存储起始值和"差值=1"这条信息,压缩率接近极限。
第二层:通用压缩算法
在编码之后,GBase 8a 会对数据块进一步应用通用压缩算法。常见选项是 LZ4 和 ZSTD:LZ4 的压缩和解压速度极快,CPU 开销极小,但压缩率中等;ZSTD 的压缩率更高,CPU 开销相对较大,但在现代多核 CPU 上通常仍在可接受范围内。对于 I/O 密集型场景(磁盘慢、网络慢),优先选 ZSTD;对于 CPU 密集型场景(大量并发查询),优先选 LZ4。
三、压缩策略配置
GBase 8a 允许在建表或 ANALYZE 时指定压缩策略,也可以通过参数设置全局默认值。
-- 建表时指定压缩策略(大多数情况下让引擎自动选择即可)
CREATE TABLE orders (
order_id BIGINT,
customer_id INT,
amount DECIMAL(18,2),
status TINYINT,
order_date DATE
) ENGINE=EXPRESS DEFAULT CHARSET=utf8
DISTRIBUTED BY HASH(customer_id);
-- 查看某张表各列实际使用的编码和压缩算法
SELECT
column_name,
column_type,
extra
FROM information_schema.columns
WHERE table_schema = 'sales_db'
AND table_name = 'orders';
# gnode 的 gbase.cnf:全局默认压缩级别
# 0 = 不压缩(调试用,生产不建议)
# 1 = 快速压缩(LZ4)
# 2 = 标准压缩(默认)
# 3 = 高压缩(ZSTD,CPU 开销更大)
gbase_default_compress_level = 2
对于 I/O 明显是瓶颈(iostat 中 await 很高、util 接近 100%)的节点,可以尝试调高压缩级别,用更多 CPU 换取更少的磁盘读写量。反之,如果 CPU 是瓶颈(top 中 CPU 持续 100%),应降低压缩级别或使用更快的 LZ4。
四、影响压缩效果的设计决策
理解了压缩原理,就能在日常建表和查询设计中做出更有意识的选择,而不是把压缩当作黑盒。
数据类型的影响是最直接的。用 TINYINT(1 字节)存 0~255 范围的枚举值,远比用 VARCHAR(20) 存 "active"/"inactive" 压缩效果好——不仅字典编码的字典更小,差值编码也更高效。用 DATE 存日期而不是 VARCHAR(10) 存 "2024-06-01",列存引擎可以用专门的日期编码,压缩率和查询时的日期计算性能都更好。用数值类型存 ID,而不是字符串类型,是同样的道理。
写入顺序的影响往往被忽略。列存引擎的 RLE 编码对连续相同值的效果极好,这意味着如果数据按 dept_id 或 order_date 排序写入,同一部门或同一日期的数据聚集在一起,RLE 的压缩率会显著高于随机顺序写入的情况。在 gload 的配置文件中,如果源数据已经按某列有序,可以在配置中声明排序列,让引擎利用这个信息优化编码。
列的基数选择在分布键设计时已经讨论过,但它同样影响压缩效率。用 customer_id(千万级唯一值)做分布键后,该列在每个 gnode 上的局部分布仍然有较高基数,字典编码的收益有限;但 dept_id(百级唯一值)的压缩率就会高得多。这不意味着要为了压缩率去选择低基数列作为分布键,而是说在做容量规划时,高基数分布键列的压缩率要按偏低估算,低基数列按偏高估算,这样整体容量估算会更准确。
五、实测压缩效果
在生产环境中,最准确的压缩率数据来自实际测量,而非公式估算。
-- 方法一:通过系统表查看各表的数据量(已压缩)
SELECT
table_name,
ROUND(SUM(data_size) / 1073741824, 3) AS compressed_gb,
ROUND(SUM(raw_data_size) / 1073741824, 3) AS raw_gb,
ROUND(SUM(raw_data_size) / SUM(data_size), 2) AS compress_ratio
FROM gclusterdb.segment_info
WHERE table_name = 'orders'
GROUP BY table_name;
-- 方法二:分列查看压缩情况(哪些列压缩率高,哪些列压缩收益低)
SELECT
column_name,
ROUND(compressed_size / 1048576, 2) AS compressed_mb,
ROUND(uncompressed_size / 1048576, 2) AS uncompressed_mb,
ROUND(uncompressed_size / compressed_size, 2) AS ratio
FROM gclusterdb.column_storage_info
WHERE table_name = 'orders'
AND schema_name = 'sales_db'
ORDER BY ratio DESC;
通过分列查看,可以发现哪些列的压缩率远低于预期,进而检查这些列的数据类型是否合理(比如用 VARCHAR 存了应该是 INT 的值),或者数据分布是否真的是高基数、高随机性的(这种情况下低压缩率是正常的,无需优化)。
对于即将上线的新系统,建议在上线前用部分生产数据做压缩率摸底测试,把实测的压缩比代入容量规划公式,这比用经验值("按 5:1 算")要准确得多。
六、常见误区
误区一:压缩率越高查询越快。 压缩率提升减少了 I/O 量,在 I/O 是瓶颈时确实能加速查询。但如果 CPU 是瓶颈,更高的压缩率意味着更多的解压 CPU 开销,反而可能导致查询变慢。需要结合实际的资源瓶颈位置来判断。
误区二:用字符串存数值不影响压缩。 字符串 "12345678" 需要 8 个字节,而 INT 类型的 12345678 只需要 4 个字节,且 INT 类型可以使用差值编码进一步压缩,字符串类型不行。用错数据类型不仅浪费存储,还会让特定的编码优化失效。
误区三:压缩是 DBA 的事,与开发无关。 开发人员选择的数据类型和写入顺序直接影响压缩效率。用 TINYINT 还是 VARCHAR 存 status,压缩率可能相差 5 倍,查询时的解压开销也相差同等倍数。把压缩效率的考量纳入代码审查和表设计评审,是提升整体系统效率的有效手段。
评论
热门帖子
- 12025-12-01浏览数:183150
- 22023-05-09浏览数:25914
- 42023-09-25浏览数:19556
- 52020-05-11浏览数:18170