列式存储即规范化
摘要
本文将列式存储重新定义为数据库规范化的极端形式,展示了把属性拆分为位置对齐的数组如何与基于隐式序数主键连接的规范化表如出一辙。
暂无内容
查看缓存全文
缓存时间: 2026/04/22 13:13
# 列式存储就是规范化
来源:https://buttondown.com/jaffray/archive/columnar-storage-is-normalization
我以前一直没意识到,把行式数据转成列式数据,并不是数据库领域里什么全新的概念,它仍然属于关系抽象的一部分——或者说,可以被看作是关系抽象。
举个例子,我们有这样一份数据:
```json
data = [
{ "name": "Smudge", "colour": "black" },
{ "name": "Sissel", "colour": "grey" },
{ "name": "Hamlet", "colour": "black" }
]
```
这相当于关系数据库里的一张表。假设它真的存在数据库里,每次访问任意字段都得做磁盘 IO。这种表示法有一些优点:
- **加行方便**:只要拼出一行 `{ "name": "Petee", "colour": "black" }`,追加到列表末尾即可;磁盘上可能只需改几个页。即使这一行有几十列,也依旧如此。
- **查行迅速**:因为同一行的所有列在物理上紧邻,把整行捞出来很快。
反过来,如果想统计宠物颜色的直方图,就得把大量无关数据(比如名字)一起读进来——效率很低。
这就是**行式存储**。换成**列式存储**,数据长这样:
```json
data = {
"name": [
"Smudge",
"Sissel",
"Hamlet"
],
"colour": [
"black",
"grey",
"black"
],
}
```
优缺点正好反过来:只关心 `colour` 时,可以只读这一列,名字一字节都不用碰;但改数据或按行查询就麻烦——要到处跳。如果想拿第二行,就得去每个列数组的第二个位置把字段拼回来。
一种理解是:这只是**编码层**的事,位于数据模型之下;SQL 引擎逻辑上区分不了两种格式,只能通过查询性能感知差异。
另一种理解是:**列式化就是一种极端的数据库规范化**。
不再是一张“宽表”对应一堆向量,而是把每列拆成一张小表,主键再加一个属性:
非规范化表:
```
+----+------+-----+
| id | name | age |
+----+------+-----+
| 12 | Bob | 30 |
| 93 | Tom | 35 |
| 27 | Kim | 28 |
+----+------+-----+
```
规范化后:
### Name 表
```
+----+------+
| id | name |
+----+------+
| 12 | Bob |
| 93 | Tom |
| 27 | Kim |
+----+------+
```
### Age 表
```
+----+-----+
| id | age |
+----+-----+
| 12 | 30 |
| 93 | 35 |
| 27 | 28 |
+----+-----+
```
用 `id` 做 join 就能拼回原表。
在列式存储里,可以把“主键”看成数据在数组里的**下标**。
原数据:
```json
data = {
"name": [
"Smudge",
"Sissel",
"Hamlet"
],
"colour": [
"black",
"grey",
"black"
],
}
```
可以视为:
```
+----+--------+
| id | name |
+----+--------+
| 0 | Smudge |
| 1 | Sissel |
| 2 | Hamlet |
+----+--------+
+----+--------+
| id | colour |
+----+--------+
| 0 | black |
| 1 | grey |
| 2 | black |
+----+--------+
```
但 `id` 其实只是数组下标,可以隐去:
```
+--------+
| name |
+--------+
| Smudge |
| Sissel |
| Hamlet |
+--------+
+--------+
| colour |
+--------+
| black |
| grey |
| black |
+--------+
```
这种视角的价值在于:它把传统的投影、join 等查询操作,与数据格式的转换统一了起来。大多数时候,你确实应该把数据格式当成对查询逻辑透明的实现细节;但心里要明白——**“从列存里拼回一行”不仅看起来像 join,它本身就是 join。**
相似文章
@BenjDicken:分片就是:1)数据库可扩展性的基石 2)架构层面超有趣 想设计数据……
Ben Dicken 强调,分片是构建可扩展数据库和设计数据密集型应用的关键。
对 APL 等数组语言的有原则性重新思考
本文提出了一种有原则性的方法来重新思考 APL 等数组语言,通过将变量建模为输入维度的函数,旨在相较于传统方法提高可读性和错误检查能力。
@PlanetScale:数据库太慢?本地挂载 NVMe 存储带来无上限 IOPS,把数据中心级性能搬进云端……
PlanetScale 推出基于本地 NVMe 存储的高性能云托管方案,为 Vitess 与 Postgres 提供无上限 IOPS 与横向扩展能力。
PriorLabs/TabPFN
PriorLabs 推出了 TabPFN,这是一种专为表格数据设计的基座模型。
稀疏 Cholesky 消元树
本文推导了面向右侧的稀疏 Cholesky 算法的列消元树,解释了它如何在不进行稠密分解的情况下预测填充元素和任务依赖关系。