06 - Column-Store Databases
Database Workload
目前市面上有常见的三种数据库
On-Line Transaction Processing (OLTP)
联机事务处理
特点:高并发、低延迟、每次操作数据量小、事务性强(ACID)。
典型场景:用户注册、下单、发评论、转账等。
性能要求:快速响应,支持大量并发写入。
技术重点:行式存储(row-oriented)、索引优化、事务日志、锁机制。
Tuple 有几个字段时,查询某一个字段时,不管其他的字段是否需要,都会被读取加载到内存中,即使它们是没有用处的数据,这也就是 OLTP 不适合用于分析的原因。
事实上,除非批量导出,否则分析时几乎不会出现 SELECT * 。
On-Line Analytical Processing (OLAP)
联机分析处理
特点:读多写少、查询复杂、涉及大量数据扫描和聚合。
典型场景:商业智能(BI)、报表、用户行为分析。
性能要求:高效处理大规模数据聚合,支持复杂查询。
技术重点:列式存储(column-oriented)、数据压缩、向量化执行、MPP 架构。
对于 OLAP 来说,虽然 read 很快,但是相对的 write 就慢,每次 write 都需要将表按字段分离,再进行写入。
同时, OLAP 做点查(point queries) 很慢,因为需要从多个列查询结果,然后进行拼接。
Hybrid Transaction + Analytical Processing
混合事务/分析处理 目标:在同一个系统中同时高效支持 OLTP 和 OLAP。
问题:由于需要兼容两种需求,因此两种需求都不能做得很好,导致市场的选择会出现问题。
代表系统:SingleStore、TiDB、Oracle In-Memory、SQL Server Hekaton。
相对来说
OLTP 大多数操作是写入操作,各种操作也很简单。(实际读取更多,相对 OLAP 写入更多)
OLAP 大多数操作是读取操作,且查询非常复杂。(实际几乎不写入,只会定期批量导入)
HTAP 处于 OLTP 和 OLAP 的中间。
一些特殊的情况,例如在 PostgreSQL 中,它虽然是 OLTP ,但是如果存储了大文本数据,它会单独设置一个 Page ,而不会将数据存到 Slot 中。
Decomposition Storage Model (DSM)
OLAP 的 Tuple 有两种存储方式
Fixed-length Offsets
所有列按相同行序存储,第 i 个元素天然对齐一个 Tuple
要求每个字段大小固定
可能有额外的存储开销
Database compression
目标:
- 生成固定长度的值
- 推迟解压数据的时间(不解压就能够知道里面包含了什么)
- 压缩是无损的
实现方法:
- Block-level (按 Page 压缩)
- Tuple-level (按 Tuple 压缩)
- Attribute-level (对单个属性压缩)
- Colunm-level (对列压缩)
对于 MySQL(InnoDB) 来说,它使用的 Block-level 压缩,对 Page 进行压缩存储。
在每个数据页的页头(header) 中,有一个叫 “mod log”(修改日志) 的区域。它类似于预写日志(WAL),用于记录对这个页所做的修改,但这些修改尚未应用到压缩数据本身。
当我需要访问一个 Page 时, buffer pool 会像往常一样从磁盘读取它,以压缩的形式读入内存中, 如果需要 Write ,直接定位到它然后覆盖即可,不需要解压,在 mod log 中写一条记录,对这个 Tuple 的这个字段,用新值覆盖,当 mod log 写满时,我才需要解压整个页,应用所有修改,然后清空 mod log;
如果需要 Read ,如果刚刚被 Write 过,直接读取 mod log 进行了,但如果需要查看 Tuple 内部的实际值,就必须解压整个页,然后合并 mod log。这是一种常见模式:系统会有一个快速写入区(delta store / mod log)
当 mod log 满了需要同步的时候,会产生阻塞
对于 OLAP 来说,更常见的是 Colunm-level ,因为一个列里有许多属性,它们都是重复,可以增加压缩的效率。
Encoding
| 压缩技术 (Compression Scheme) | 适用场景 | 核心原理 | 优点 | 缺点 / 挑战 | 典型系统 / 备注 |
|---|---|---|---|---|---|
| 行程编码 (RLE, Run-Length Encoding) | 列中存在大量连续重复值(如布尔标志、状态字段) | 将连续相同值编码为 (值, 起始偏移, 重复次数) 三元组 | 极高压缩比(当重复值集中时);支持直接在压缩域聚合(如 COUNT) | 需数据排序才能高效;无序数据压缩后反而更大 | ORC、Parquet;常与 Delta 编码组合使用 |
| 位打包 (Bit Packing) | 数值远小于其数据类型上限(如 age 用 32 位 int 存 0–127) | 仅存储有效低位,丢弃高位无意义的 0 | 显著节省空间(如 32 位 → 8 位);保持固定长度,利于向量化 | 插入超大值需补丁表(Patch Table);增加读取逻辑复杂度 | Amazon Redshift(MOSTLY8 编码) |
| 位图编码 (Bitmap Encoding) | 低基数(Low-Cardinality) 分类列(如 gender, is_active) | 为每个唯一值维护一个位图,第 i 位 = 1 表示第 i 行是该值 | 查询极快(位运算如 AND/OR/POPCNT);压缩率高(1 行 = 1 位) | 高基数列(如 user_id)会导致空间爆炸 | ClickHouse、Doris;高基数时需 Roaring Bitmaps |
| Delta 编码 (Delta Encoding) | 平缓变化的有序数值(如时间戳、自增 ID、温度) | 存储与前一个值(或基准值)的差值(delta),而非绝对值 | 差值通常很小,可用更少位表示;适合进一步压缩(如 RLE) | 要求数据有序;随机访问需从头累加 | Parquet、ORC;常用于时间序列数据 |
| 字典编码 (Dictionary Encoding) | 重复字符串或枚举值(如 country, product_name) | 将唯一值映射为固定长度整数 ID,列中存 ID,另存字典(值 ↔ ID) | 高压缩率;整数比较快;支持顺序保留(order-preserving) 范围查询 | 唯一值过多时字典变大;插入新值可能需重建 | 最常用;ORC、Parquet、ClickHouse、Doris |
关键设计原则:
- 组合使用:多种压缩技术可叠加(如 Dictionary + RLE、Delta + RLE),效果是乘法级的。
- 顺序保留:字典按值排序,使压缩数据仍能高效支持
WHERE name > 'Alice'等范围查询。- OLAP 友好:依赖“数据不可变 + 批量写入”假设,避免频繁更新压缩结构。
Embedded Tuple Ids
每列独立存储 + 附加 Tuple ID + 外部索引对齐
不能使用