44.4 存储引擎:压缩与编码

一句话

压缩省的是 “IO 与存储”,花的是 “CPU 与延迟”;而真正让 OLAP 快的往往不是通用压缩,而是列式编码(字典、RLE、位打包、Delta)——它把 “同类型、相似值” 变成 “极小的整数序列”,压缩比常比 gzip 高一个量级。

两类手段

类别 手段 典型收益 代价
通用压缩 zlib/gzip、LZ4、ZSTD、lzma 2–10× CPU;LZ4 快但压缩率低,ZSTD 折中
列式编码 字典编码、RLE、位打包、Delta、Frame-of-Reference 10–100×(低基数列) 只适用于列存/同类型数据

实测:同一份 JSON 数据的三种通用压缩(本机)

2 万条记录、约 1.3MB JSON:

算法 压缩后 压缩比 压缩耗时 解压耗时
zlib 约 1/6 ~6× 快 最快
bz2 更小 ~8× 慢 较慢
lzma 最小 ~9× 最慢 慢

(具体数字见本节实测输出;趋势是 压缩率越高、CPU 越贵。)

关键判断:压缩只在 “IO 是瓶颈” 时划算。如果查询是 CPU 密集(复杂聚合),压缩反而拖慢;如果瓶颈在磁盘/网络(大扫描、跨机房),压缩几乎总是赚的。

-- 列存里更有效的是编码,而不是压缩算法
-- 低基数列(city):字典编码 → 300 个字符串变成 300 个整数 + 字典
-- 有序列(ts):Delta 编码 → 相邻差值,通常只有几十字节/千行
-- 重复列(status):RLE → (值, 连续次数) 对

编码方式对照

编码 适用 压缩效果
字典(Dictionary) 低基数枚举(城市、状态) 极好
RLE 连续重复(排序后的列) 极好
位打包(Bit-packing) 小整数范围 好
Delta 单调递增(时间戳、ID) 极好
FOR(Frame of Reference) 整数在一个窄区间 好

反模式

反模式 后果
对已压缩数据(图片/视频)再压缩 几乎不省,白花 CPU
用 lzma 压缩热数据 解压慢,查询延迟上升
行存上用字典编码 一行内类型混杂,字典收益低
压缩块太大(如 1MB) 随机读要解压整块,读放大
压缩块太小(如 4KB) 压缩率下降,元数据开销上升
不监控 “压缩/解压 CPU 占比” 以为省了 IO,其实卡在 CPU

三条铁律

🔑 压缩是 “IO 换 CPU”;先确认瓶颈在 IO。

🔑 列存靠编码,不靠压缩算法。

🔑 压缩块大小 = 读放大与压缩率的平衡点(通常 64KB–1MB)。

自检

Q1 为什么图片/视频再压缩几乎没收益?

▸ 答案:因为它们已经是高熵压缩格式(JPEG/H.264 去掉了空间与时间冗余),再套通用压缩只能去掉极少量结构冗余,压缩比通常 <5%,而 CPU 成本照付。压缩的前提是 “数据里有冗余”。

Q2 字典编码为什么对低基数列特别有效?

▸ 答案:因为它把 “重复出现的长字符串” 换成 “短整数下标”:300 个城市名 → 300 条字典 + 每行 2 字节整数。压缩比取决于 “基数/行数”,基数越低越接近 “位宽下限”,而且比较/分组变成整数比较,查询也更快。

Q3 压缩块为什么不能太大或太小?

▸ 答案:太大 → 读一行也要解压整块,读放大;太小 → 压缩窗口短、压缩率下降,且每块有元数据/索引开销。列存常用 64KB–1MB,并结合 “块内 min/max 索引” 做谓词下推跳过整块。

Q4 什么时候 “不压缩” 反而更好?

▸ 答案:当 CPU 是瓶颈(复杂聚合、实时写入)或数据本身不可压缩(已压缩、加密、随机 ID)时。另外,热数据频繁读写时,压缩/解压的 CPU 开销可能超过 IO 节省。

Q5 Delta 编码对时间戳为什么有效?

▸ 答案:因为有序时间戳的相邻差值很小且稳定(如每秒一条 → 差值恒为 1),用 “基准 + 小整数差值” 表示,位宽从 64 位降到几位。乱序时间戳则效果差——所以列存常先按时间排序再编码。

Q6 ZSTD 与 LZ4 怎么选?

▸ 答案:LZ4 优先延迟(解压极快,适合热数据/实时查询),ZSTD 优先压缩率(可调级别,适合冷数据/归档)。经验做法:热层 LZ4、温层 ZSTD、冷层高压缩比算法,按访问频率分层。

Q7 压缩会不会影响 “随机点查”?

▸ 答案:会。点查要定位到块、解压整块才能取一行,块越大越亏。所以点查型负载(OLTP)通常不压缩或只压大字段;分析型负载(OLAP)按列块压缩,配合 min/max 索引跳块,反而更快。

关联