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 索引跳块,反而更快。
关联
- 前置:44.1 存储引擎:B+ 树与 LSM 树
- 并列:44.5 存储引擎:读写放大与空间放大、44.16 OLAP 与列式存储
- 下游:10.1 块存储 / 文件存储 / 对象存储、44.21 数据库选型方法