数据库 Page 内部是什么样:Page Header、Slot 和 Line Pointer

教学项目地址:MiniDB(欢迎参与完善,可以给个star/fork)

上一篇讲了 Heap File。

一张表不是一整块连续的数据,而是由很多 page 组成。每个 page 里存放若干条 tuple。索引通常不直接指向文件偏移,而是指向一个 RecordId:

text 复制代码
RecordId = page_id + slot_id

现在继续往下看:一个 page 里面到底长什么样?

如果把 page 想成一个 8KB 的小房间,tuple 不是随便堆进去的。数据库需要知道:

text 复制代码
这个 page 是谁
这个 page 已经用了多少空间
这个 page 里有多少条 tuple
每条 tuple 在哪里
哪些 tuple 已经删除
哪些 tuple 被重定向到新版本
哪些空间还能复用

这些信息都要放在 page 内部。

最简单的想法:tuple 连续排列

先看一个最直接的设计。

text 复制代码
Page
+-------------------+
| tuple 1           |
| tuple 2           |
| tuple 3           |
| tuple 4           |
+-------------------+

如果只是插入,这样确实可以。

每次插入一条 tuple,就把它 append 到 page 后面:

text 复制代码
+-------------------+
| tuple 1           |
| tuple 2           |
| tuple 3           |
| tuple 4           |
| tuple 5           |
+-------------------+

但只要加入删除和更新,这个设计马上出问题。

假设删除 tuple 2

text 复制代码
+-------------------+
| tuple 1           |
| deleted tuple 2   |
| tuple 3           |
| tuple 4           |
+-------------------+

这块空间怎么办?

如果直接把 tuple 3tuple 4 往前挪:

text 复制代码
+-------------------+
| tuple 1           |
| tuple 3           |
| tuple 4           |
+-------------------+

那么问题来了:如果索引里保存的是 tuple 3 的物理位置,tuple 一移动,索引里的位置就失效了。

数据库不希望每次 page 内部整理空间时,都去更新所有索引。

所以 page 内部不能只是简单的 tuple 数组。

Slot Array:给 tuple 一个稳定入口

更合理的设计是加一层间接引用。

外部不直接指向 tuple 的物理偏移,而是指向 slot。slot 再指向 tuple 真正的位置。
#mermaid-svg-A03XTe2SgPqxwMcS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A03XTe2SgPqxwMcS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A03XTe2SgPqxwMcS .error-icon{fill:#552222;}#mermaid-svg-A03XTe2SgPqxwMcS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A03XTe2SgPqxwMcS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A03XTe2SgPqxwMcS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A03XTe2SgPqxwMcS .marker.cross{stroke:#333333;}#mermaid-svg-A03XTe2SgPqxwMcS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A03XTe2SgPqxwMcS p{margin:0;}#mermaid-svg-A03XTe2SgPqxwMcS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-A03XTe2SgPqxwMcS .cluster-label text{fill:#333;}#mermaid-svg-A03XTe2SgPqxwMcS .cluster-label span{color:#333;}#mermaid-svg-A03XTe2SgPqxwMcS .cluster-label span p{background-color:transparent;}#mermaid-svg-A03XTe2SgPqxwMcS .label text,#mermaid-svg-A03XTe2SgPqxwMcS span{fill:#333;color:#333;}#mermaid-svg-A03XTe2SgPqxwMcS .node rect,#mermaid-svg-A03XTe2SgPqxwMcS .node circle,#mermaid-svg-A03XTe2SgPqxwMcS .node ellipse,#mermaid-svg-A03XTe2SgPqxwMcS .node polygon,#mermaid-svg-A03XTe2SgPqxwMcS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-A03XTe2SgPqxwMcS .rough-node .label text,#mermaid-svg-A03XTe2SgPqxwMcS .node .label text,#mermaid-svg-A03XTe2SgPqxwMcS .image-shape .label,#mermaid-svg-A03XTe2SgPqxwMcS .icon-shape .label{text-anchor:middle;}#mermaid-svg-A03XTe2SgPqxwMcS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-A03XTe2SgPqxwMcS .rough-node .label,#mermaid-svg-A03XTe2SgPqxwMcS .node .label,#mermaid-svg-A03XTe2SgPqxwMcS .image-shape .label,#mermaid-svg-A03XTe2SgPqxwMcS .icon-shape .label{text-align:center;}#mermaid-svg-A03XTe2SgPqxwMcS .node.clickable{cursor:pointer;}#mermaid-svg-A03XTe2SgPqxwMcS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-A03XTe2SgPqxwMcS .arrowheadPath{fill:#333333;}#mermaid-svg-A03XTe2SgPqxwMcS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-A03XTe2SgPqxwMcS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-A03XTe2SgPqxwMcS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A03XTe2SgPqxwMcS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-A03XTe2SgPqxwMcS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A03XTe2SgPqxwMcS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-A03XTe2SgPqxwMcS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-A03XTe2SgPqxwMcS .cluster text{fill:#333;}#mermaid-svg-A03XTe2SgPqxwMcS .cluster span{color:#333;}#mermaid-svg-A03XTe2SgPqxwMcS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-A03XTe2SgPqxwMcS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-A03XTe2SgPqxwMcS rect.text{fill:none;stroke-width:0;}#mermaid-svg-A03XTe2SgPqxwMcS .icon-shape,#mermaid-svg-A03XTe2SgPqxwMcS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A03XTe2SgPqxwMcS .icon-shape p,#mermaid-svg-A03XTe2SgPqxwMcS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-A03XTe2SgPqxwMcS .icon-shape .label rect,#mermaid-svg-A03XTe2SgPqxwMcS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A03XTe2SgPqxwMcS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-A03XTe2SgPqxwMcS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-A03XTe2SgPqxwMcS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RID(page_id=10, slot_id=2)
Slot 2
Tuple data in page

这样,即使 tuple 在 page 内部移动,只要 slot_id 不变,外部引用就不会失效。

page 内部大致变成这样:

text 复制代码
+-----------------------------+
| Page Header                 |
+-----------------------------+
| Slot 0 -> tuple offset      |
| Slot 1 -> tuple offset      |
| Slot 2 -> tuple offset      |
+-----------------------------+
| Free Space                  |
+-----------------------------+
| tuple data                  |
| tuple data                  |
| tuple data                  |
+-----------------------------+

更直观一点:
#mermaid-svg-K6Kfiak1MqnDjcFQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K6Kfiak1MqnDjcFQ .error-icon{fill:#552222;}#mermaid-svg-K6Kfiak1MqnDjcFQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K6Kfiak1MqnDjcFQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .marker.cross{stroke:#333333;}#mermaid-svg-K6Kfiak1MqnDjcFQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K6Kfiak1MqnDjcFQ p{margin:0;}#mermaid-svg-K6Kfiak1MqnDjcFQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster-label text{fill:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster-label span{color:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster-label span p{background-color:transparent;}#mermaid-svg-K6Kfiak1MqnDjcFQ .label text,#mermaid-svg-K6Kfiak1MqnDjcFQ span{fill:#333;color:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .node rect,#mermaid-svg-K6Kfiak1MqnDjcFQ .node circle,#mermaid-svg-K6Kfiak1MqnDjcFQ .node ellipse,#mermaid-svg-K6Kfiak1MqnDjcFQ .node polygon,#mermaid-svg-K6Kfiak1MqnDjcFQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .rough-node .label text,#mermaid-svg-K6Kfiak1MqnDjcFQ .node .label text,#mermaid-svg-K6Kfiak1MqnDjcFQ .image-shape .label,#mermaid-svg-K6Kfiak1MqnDjcFQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-K6Kfiak1MqnDjcFQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .rough-node .label,#mermaid-svg-K6Kfiak1MqnDjcFQ .node .label,#mermaid-svg-K6Kfiak1MqnDjcFQ .image-shape .label,#mermaid-svg-K6Kfiak1MqnDjcFQ .icon-shape .label{text-align:center;}#mermaid-svg-K6Kfiak1MqnDjcFQ .node.clickable{cursor:pointer;}#mermaid-svg-K6Kfiak1MqnDjcFQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .arrowheadPath{fill:#333333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K6Kfiak1MqnDjcFQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K6Kfiak1MqnDjcFQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K6Kfiak1MqnDjcFQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster text{fill:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ .cluster span{color:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K6Kfiak1MqnDjcFQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K6Kfiak1MqnDjcFQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-K6Kfiak1MqnDjcFQ .icon-shape,#mermaid-svg-K6Kfiak1MqnDjcFQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K6Kfiak1MqnDjcFQ .icon-shape p,#mermaid-svg-K6Kfiak1MqnDjcFQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K6Kfiak1MqnDjcFQ .icon-shape .label rect,#mermaid-svg-K6Kfiak1MqnDjcFQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K6Kfiak1MqnDjcFQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K6Kfiak1MqnDjcFQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K6Kfiak1MqnDjcFQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 8KB Page
Page Header
Slot Array / Line Pointers
Free Space
Tuple Data

这种设计的关键是:

text 复制代码
RID 指向 slot
slot 指向 tuple
tuple 可以移动
slot_id 尽量保持稳定

这就是很多数据库 page layout 的基本思路。

MiniDB 也采用了类似 PostgreSQL 风格的 line pointer 设计。page 内部有 page header、line pointer array 和 tuple data。line pointer 不只记录 tuple 的位置,还记录这个 slot 当前是什么状态。

Page Header:page 的元信息

每个 page 都需要一个 header。

Header 不存业务数据,而是存 page 自己的管理信息。比如:

text 复制代码
page_id
page_type
LSN
free space offset
tuple count
flags

不同数据库的 page header 字段会不一样,但目的差不多:

text 复制代码
识别这个 page
判断 page 类型
知道空闲空间在哪里
知道 page 是否被修改到某个 WAL LSN
辅助恢复和一致性检查

可以把 page header 理解成 page 的"身份证 + 状态栏"。
#mermaid-svg-98Hc1XnpTjiTHdXK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-98Hc1XnpTjiTHdXK .error-icon{fill:#552222;}#mermaid-svg-98Hc1XnpTjiTHdXK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-98Hc1XnpTjiTHdXK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-98Hc1XnpTjiTHdXK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-98Hc1XnpTjiTHdXK .marker.cross{stroke:#333333;}#mermaid-svg-98Hc1XnpTjiTHdXK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-98Hc1XnpTjiTHdXK p{margin:0;}#mermaid-svg-98Hc1XnpTjiTHdXK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster-label text{fill:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster-label span{color:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster-label span p{background-color:transparent;}#mermaid-svg-98Hc1XnpTjiTHdXK .label text,#mermaid-svg-98Hc1XnpTjiTHdXK span{fill:#333;color:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK .node rect,#mermaid-svg-98Hc1XnpTjiTHdXK .node circle,#mermaid-svg-98Hc1XnpTjiTHdXK .node ellipse,#mermaid-svg-98Hc1XnpTjiTHdXK .node polygon,#mermaid-svg-98Hc1XnpTjiTHdXK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-98Hc1XnpTjiTHdXK .rough-node .label text,#mermaid-svg-98Hc1XnpTjiTHdXK .node .label text,#mermaid-svg-98Hc1XnpTjiTHdXK .image-shape .label,#mermaid-svg-98Hc1XnpTjiTHdXK .icon-shape .label{text-anchor:middle;}#mermaid-svg-98Hc1XnpTjiTHdXK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-98Hc1XnpTjiTHdXK .rough-node .label,#mermaid-svg-98Hc1XnpTjiTHdXK .node .label,#mermaid-svg-98Hc1XnpTjiTHdXK .image-shape .label,#mermaid-svg-98Hc1XnpTjiTHdXK .icon-shape .label{text-align:center;}#mermaid-svg-98Hc1XnpTjiTHdXK .node.clickable{cursor:pointer;}#mermaid-svg-98Hc1XnpTjiTHdXK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-98Hc1XnpTjiTHdXK .arrowheadPath{fill:#333333;}#mermaid-svg-98Hc1XnpTjiTHdXK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-98Hc1XnpTjiTHdXK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-98Hc1XnpTjiTHdXK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-98Hc1XnpTjiTHdXK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-98Hc1XnpTjiTHdXK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-98Hc1XnpTjiTHdXK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster text{fill:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK .cluster span{color:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-98Hc1XnpTjiTHdXK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-98Hc1XnpTjiTHdXK rect.text{fill:none;stroke-width:0;}#mermaid-svg-98Hc1XnpTjiTHdXK .icon-shape,#mermaid-svg-98Hc1XnpTjiTHdXK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-98Hc1XnpTjiTHdXK .icon-shape p,#mermaid-svg-98Hc1XnpTjiTHdXK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-98Hc1XnpTjiTHdXK .icon-shape .label rect,#mermaid-svg-98Hc1XnpTjiTHdXK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-98Hc1XnpTjiTHdXK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-98Hc1XnpTjiTHdXK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-98Hc1XnpTjiTHdXK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Page Header
page_id
page_type
page_lsn
free space info
tuple / slot count

其中 page_lsn 很重要。

数据库修改 page 时,会先写 WAL。WAL record 有自己的 LSN。page 被修改后,也会记录这个修改对应的 LSN。

恢复时,数据库可以比较:

text 复制代码
page_lsn >= wal_record_lsn

如果 page 已经包含这条 WAL 修改,就不需要重复 redo。

所以 page header 不是单纯为了存储布局服务,它还和 WAL、恢复机制有关。

Line Pointer 不只是 offset

最简单的 slot 只需要记录 tuple 的偏移和长度:

text 复制代码
slot_id -> offset + length

但真实数据库里,slot 还需要状态。

因为 tuple 可能处于不同生命周期:

text 复制代码
正常存在
已经删除
被重定向到新版本
这个 slot 空闲可复用

PostgreSQL 风格的设计里,line pointer 通常有几种状态。MiniDB 也采用了类似思路,比如:

text 复制代码
LP_UNUSED
LP_NORMAL
LP_REDIRECT
LP_DEAD

可以这样理解:

text 复制代码
LP_UNUSED   这个 slot 没有使用
LP_NORMAL   这个 slot 指向一条正常 tuple
LP_REDIRECT 这个 slot 不再直接指向 tuple,而是重定向到另一个 slot
LP_DEAD     这个 slot 对应的 tuple 已经死掉,可以等待清理

用图表示:
#mermaid-svg-7vzSSPckIVlKTY5R{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7vzSSPckIVlKTY5R .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7vzSSPckIVlKTY5R .error-icon{fill:#552222;}#mermaid-svg-7vzSSPckIVlKTY5R .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7vzSSPckIVlKTY5R .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7vzSSPckIVlKTY5R .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7vzSSPckIVlKTY5R .marker.cross{stroke:#333333;}#mermaid-svg-7vzSSPckIVlKTY5R svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7vzSSPckIVlKTY5R p{margin:0;}#mermaid-svg-7vzSSPckIVlKTY5R .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7vzSSPckIVlKTY5R .cluster-label text{fill:#333;}#mermaid-svg-7vzSSPckIVlKTY5R .cluster-label span{color:#333;}#mermaid-svg-7vzSSPckIVlKTY5R .cluster-label span p{background-color:transparent;}#mermaid-svg-7vzSSPckIVlKTY5R .label text,#mermaid-svg-7vzSSPckIVlKTY5R span{fill:#333;color:#333;}#mermaid-svg-7vzSSPckIVlKTY5R .node rect,#mermaid-svg-7vzSSPckIVlKTY5R .node circle,#mermaid-svg-7vzSSPckIVlKTY5R .node ellipse,#mermaid-svg-7vzSSPckIVlKTY5R .node polygon,#mermaid-svg-7vzSSPckIVlKTY5R .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7vzSSPckIVlKTY5R .rough-node .label text,#mermaid-svg-7vzSSPckIVlKTY5R .node .label text,#mermaid-svg-7vzSSPckIVlKTY5R .image-shape .label,#mermaid-svg-7vzSSPckIVlKTY5R .icon-shape .label{text-anchor:middle;}#mermaid-svg-7vzSSPckIVlKTY5R .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7vzSSPckIVlKTY5R .rough-node .label,#mermaid-svg-7vzSSPckIVlKTY5R .node .label,#mermaid-svg-7vzSSPckIVlKTY5R .image-shape .label,#mermaid-svg-7vzSSPckIVlKTY5R .icon-shape .label{text-align:center;}#mermaid-svg-7vzSSPckIVlKTY5R .node.clickable{cursor:pointer;}#mermaid-svg-7vzSSPckIVlKTY5R .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7vzSSPckIVlKTY5R .arrowheadPath{fill:#333333;}#mermaid-svg-7vzSSPckIVlKTY5R .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7vzSSPckIVlKTY5R .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7vzSSPckIVlKTY5R .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7vzSSPckIVlKTY5R .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7vzSSPckIVlKTY5R .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7vzSSPckIVlKTY5R .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7vzSSPckIVlKTY5R .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7vzSSPckIVlKTY5R .cluster text{fill:#333;}#mermaid-svg-7vzSSPckIVlKTY5R .cluster span{color:#333;}#mermaid-svg-7vzSSPckIVlKTY5R div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7vzSSPckIVlKTY5R .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7vzSSPckIVlKTY5R rect.text{fill:none;stroke-width:0;}#mermaid-svg-7vzSSPckIVlKTY5R .icon-shape,#mermaid-svg-7vzSSPckIVlKTY5R .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7vzSSPckIVlKTY5R .icon-shape p,#mermaid-svg-7vzSSPckIVlKTY5R .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7vzSSPckIVlKTY5R .icon-shape .label rect,#mermaid-svg-7vzSSPckIVlKTY5R .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7vzSSPckIVlKTY5R .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7vzSSPckIVlKTY5R .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7vzSSPckIVlKTY5R :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Line Pointer
State
LP_UNUSED

unused slot
LP_NORMAL

points to tuple
LP_REDIRECT

redirects to another slot
LP_DEAD

dead tuple

这个状态设计非常关键。它让 page 不只是一个数据容器,而是能表达 tuple 生命周期。

LP_NORMAL:最普通的 tuple

最简单的情况是 LP_NORMAL

text 复制代码
slot 0 -> tuple offset

图上是这样:
#mermaid-svg-AD3nsAqsDrSat8MZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AD3nsAqsDrSat8MZ .error-icon{fill:#552222;}#mermaid-svg-AD3nsAqsDrSat8MZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AD3nsAqsDrSat8MZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AD3nsAqsDrSat8MZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AD3nsAqsDrSat8MZ .marker.cross{stroke:#333333;}#mermaid-svg-AD3nsAqsDrSat8MZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AD3nsAqsDrSat8MZ p{margin:0;}#mermaid-svg-AD3nsAqsDrSat8MZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster-label text{fill:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster-label span{color:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster-label span p{background-color:transparent;}#mermaid-svg-AD3nsAqsDrSat8MZ .label text,#mermaid-svg-AD3nsAqsDrSat8MZ span{fill:#333;color:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ .node rect,#mermaid-svg-AD3nsAqsDrSat8MZ .node circle,#mermaid-svg-AD3nsAqsDrSat8MZ .node ellipse,#mermaid-svg-AD3nsAqsDrSat8MZ .node polygon,#mermaid-svg-AD3nsAqsDrSat8MZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AD3nsAqsDrSat8MZ .rough-node .label text,#mermaid-svg-AD3nsAqsDrSat8MZ .node .label text,#mermaid-svg-AD3nsAqsDrSat8MZ .image-shape .label,#mermaid-svg-AD3nsAqsDrSat8MZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-AD3nsAqsDrSat8MZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AD3nsAqsDrSat8MZ .rough-node .label,#mermaid-svg-AD3nsAqsDrSat8MZ .node .label,#mermaid-svg-AD3nsAqsDrSat8MZ .image-shape .label,#mermaid-svg-AD3nsAqsDrSat8MZ .icon-shape .label{text-align:center;}#mermaid-svg-AD3nsAqsDrSat8MZ .node.clickable{cursor:pointer;}#mermaid-svg-AD3nsAqsDrSat8MZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AD3nsAqsDrSat8MZ .arrowheadPath{fill:#333333;}#mermaid-svg-AD3nsAqsDrSat8MZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AD3nsAqsDrSat8MZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AD3nsAqsDrSat8MZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AD3nsAqsDrSat8MZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AD3nsAqsDrSat8MZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AD3nsAqsDrSat8MZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster text{fill:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ .cluster span{color:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AD3nsAqsDrSat8MZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AD3nsAqsDrSat8MZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-AD3nsAqsDrSat8MZ .icon-shape,#mermaid-svg-AD3nsAqsDrSat8MZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AD3nsAqsDrSat8MZ .icon-shape p,#mermaid-svg-AD3nsAqsDrSat8MZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AD3nsAqsDrSat8MZ .icon-shape .label rect,#mermaid-svg-AD3nsAqsDrSat8MZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AD3nsAqsDrSat8MZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AD3nsAqsDrSat8MZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AD3nsAqsDrSat8MZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} slot 0

LP_NORMAL
tuple data

这表示 slot 0 指向一条正常 tuple。

查询通过 RID 找到 slot 0,然后顺着 line pointer 找到 tuple data,再交给事务系统判断它对当前 snapshot 是否可见。

注意:LP_NORMAL 只说明这个 slot 指向 tuple。它不代表这条 tuple 对所有事务都可见。

可见性还要看 tuple header 里的 xmin/xmax

text 复制代码
LP_NORMAL 解决物理定位
xmin/xmax 解决事务可见性

这两个概念要分清。

LP_DEAD:删除后不能马上物理清理

删除一条 tuple 时,数据库通常不会立刻把它从 page 里抹掉。

原因前面讲过:可能还有旧事务能看到它。

所以删除通常分阶段:

text 复制代码
第一步:设置 tuple.xmax
第二步:等旧 snapshot 消失
第三步:标记 LP_DEAD 或进入 pruning
第四步:最终空间可复用

大致流程:
#mermaid-svg-MpW7V0RHqRaMkYVQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MpW7V0RHqRaMkYVQ .error-icon{fill:#552222;}#mermaid-svg-MpW7V0RHqRaMkYVQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MpW7V0RHqRaMkYVQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .marker.cross{stroke:#333333;}#mermaid-svg-MpW7V0RHqRaMkYVQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MpW7V0RHqRaMkYVQ p{margin:0;}#mermaid-svg-MpW7V0RHqRaMkYVQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster-label text{fill:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster-label span{color:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster-label span p{background-color:transparent;}#mermaid-svg-MpW7V0RHqRaMkYVQ .label text,#mermaid-svg-MpW7V0RHqRaMkYVQ span{fill:#333;color:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .node rect,#mermaid-svg-MpW7V0RHqRaMkYVQ .node circle,#mermaid-svg-MpW7V0RHqRaMkYVQ .node ellipse,#mermaid-svg-MpW7V0RHqRaMkYVQ .node polygon,#mermaid-svg-MpW7V0RHqRaMkYVQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .rough-node .label text,#mermaid-svg-MpW7V0RHqRaMkYVQ .node .label text,#mermaid-svg-MpW7V0RHqRaMkYVQ .image-shape .label,#mermaid-svg-MpW7V0RHqRaMkYVQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-MpW7V0RHqRaMkYVQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .rough-node .label,#mermaid-svg-MpW7V0RHqRaMkYVQ .node .label,#mermaid-svg-MpW7V0RHqRaMkYVQ .image-shape .label,#mermaid-svg-MpW7V0RHqRaMkYVQ .icon-shape .label{text-align:center;}#mermaid-svg-MpW7V0RHqRaMkYVQ .node.clickable{cursor:pointer;}#mermaid-svg-MpW7V0RHqRaMkYVQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .arrowheadPath{fill:#333333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MpW7V0RHqRaMkYVQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MpW7V0RHqRaMkYVQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MpW7V0RHqRaMkYVQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster text{fill:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ .cluster span{color:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MpW7V0RHqRaMkYVQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MpW7V0RHqRaMkYVQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-MpW7V0RHqRaMkYVQ .icon-shape,#mermaid-svg-MpW7V0RHqRaMkYVQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MpW7V0RHqRaMkYVQ .icon-shape p,#mermaid-svg-MpW7V0RHqRaMkYVQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MpW7V0RHqRaMkYVQ .icon-shape .label rect,#mermaid-svg-MpW7V0RHqRaMkYVQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MpW7V0RHqRaMkYVQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MpW7V0RHqRaMkYVQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MpW7V0RHqRaMkYVQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} yes
no
DELETE tuple
set xmax
Old snapshot may see it?
keep tuple
mark LP_DEAD
prune / vacuum
space reusable

LP_DEAD 的意义是:这个 slot 指向的 tuple 已经不再对任何事务有用,可以等待清理。

但它和 LP_UNUSED 还不一样。

text 复制代码
LP_DEAD   表示曾经有 tuple,现在死了
LP_UNUSED 表示这个 slot 当前未使用

数据库可以在后续 VACUUM 或 page pruning 中,把 dead tuple 的空间回收,把 slot 变成可复用状态。

LP_REDIRECT:HOT Update 的入口

LP_REDIRECT 是理解 HOT update 的关键。

先看普通更新。

一条 tuple 被更新时,数据库通常会生成一个新版本:

text 复制代码
old tuple -> new tuple

如果更新的是非索引列,索引 key 没变,那么其实没必要新增索引 entry。

比如表:

sql 复制代码
CREATE TABLE users (
    id INT,
    name TEXT,
    age INT
);

CREATE INDEX idx_users_id ON users(id);

执行:

sql 复制代码
UPDATE users SET age = 21 WHERE id = 1;

索引列是 id,没有变。索引 entry 仍然是:

text 复制代码
id=1 -> RID(page=10, slot=0)

如果新版本也在同一个 page 上,数据库可以让旧 slot 重定向到新版本 slot。这样索引仍然指向旧 RID,但查询可以沿着 redirect 找到新版本。

这就是 HOT-style update 的基本想法。
#mermaid-svg-jADBH4bWDiw2adIn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jADBH4bWDiw2adIn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jADBH4bWDiw2adIn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jADBH4bWDiw2adIn .error-icon{fill:#552222;}#mermaid-svg-jADBH4bWDiw2adIn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jADBH4bWDiw2adIn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jADBH4bWDiw2adIn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jADBH4bWDiw2adIn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jADBH4bWDiw2adIn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jADBH4bWDiw2adIn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jADBH4bWDiw2adIn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jADBH4bWDiw2adIn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jADBH4bWDiw2adIn .marker.cross{stroke:#333333;}#mermaid-svg-jADBH4bWDiw2adIn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jADBH4bWDiw2adIn p{margin:0;}#mermaid-svg-jADBH4bWDiw2adIn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jADBH4bWDiw2adIn .cluster-label text{fill:#333;}#mermaid-svg-jADBH4bWDiw2adIn .cluster-label span{color:#333;}#mermaid-svg-jADBH4bWDiw2adIn .cluster-label span p{background-color:transparent;}#mermaid-svg-jADBH4bWDiw2adIn .label text,#mermaid-svg-jADBH4bWDiw2adIn span{fill:#333;color:#333;}#mermaid-svg-jADBH4bWDiw2adIn .node rect,#mermaid-svg-jADBH4bWDiw2adIn .node circle,#mermaid-svg-jADBH4bWDiw2adIn .node ellipse,#mermaid-svg-jADBH4bWDiw2adIn .node polygon,#mermaid-svg-jADBH4bWDiw2adIn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jADBH4bWDiw2adIn .rough-node .label text,#mermaid-svg-jADBH4bWDiw2adIn .node .label text,#mermaid-svg-jADBH4bWDiw2adIn .image-shape .label,#mermaid-svg-jADBH4bWDiw2adIn .icon-shape .label{text-anchor:middle;}#mermaid-svg-jADBH4bWDiw2adIn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jADBH4bWDiw2adIn .rough-node .label,#mermaid-svg-jADBH4bWDiw2adIn .node .label,#mermaid-svg-jADBH4bWDiw2adIn .image-shape .label,#mermaid-svg-jADBH4bWDiw2adIn .icon-shape .label{text-align:center;}#mermaid-svg-jADBH4bWDiw2adIn .node.clickable{cursor:pointer;}#mermaid-svg-jADBH4bWDiw2adIn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jADBH4bWDiw2adIn .arrowheadPath{fill:#333333;}#mermaid-svg-jADBH4bWDiw2adIn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jADBH4bWDiw2adIn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jADBH4bWDiw2adIn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jADBH4bWDiw2adIn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jADBH4bWDiw2adIn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jADBH4bWDiw2adIn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jADBH4bWDiw2adIn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jADBH4bWDiw2adIn .cluster text{fill:#333;}#mermaid-svg-jADBH4bWDiw2adIn .cluster span{color:#333;}#mermaid-svg-jADBH4bWDiw2adIn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jADBH4bWDiw2adIn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jADBH4bWDiw2adIn rect.text{fill:none;stroke-width:0;}#mermaid-svg-jADBH4bWDiw2adIn .icon-shape,#mermaid-svg-jADBH4bWDiw2adIn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jADBH4bWDiw2adIn .icon-shape p,#mermaid-svg-jADBH4bWDiw2adIn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jADBH4bWDiw2adIn .icon-shape .label rect,#mermaid-svg-jADBH4bWDiw2adIn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jADBH4bWDiw2adIn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jADBH4bWDiw2adIn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jADBH4bWDiw2adIn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Index entry

id=1
slot 0

LP_REDIRECT
slot 3

LP_NORMAL
new tuple version

这个设计的好处是:

text 复制代码
非索引列更新不需要新增索引 entry
减少 B+Tree 写入
减少索引膨胀
提高更新性能

但它也有前提:

text 复制代码
更新的列不能影响索引 key
新版本最好在同一个 page
版本链需要正确维护
旧 snapshot 仍然可能要看到旧版本
VACUUM 不能过早清理

MiniDB 支持 HOT-style same-page update。它使用类似 LP_REDIRECT 的机制来表达旧 slot 到新版本的关系。这个设计很适合教学,因为它能把 MVCC、page layout 和索引维护三件事串起来。

Tuple Data:真正的行数据

Line pointer 只是入口,真正的数据放在 tuple data 区域。

一条 tuple 通常不只包含业务字段,还会包含 tuple header。

例如一行:

text 复制代码
id = 1
name = Alice
age = 20

真实 tuple 里可能长这样:

text 复制代码
Tuple Header:
  xmin
  xmax
  flags
  next_version

Tuple Body:
  id
  name
  age

简化图:
#mermaid-svg-Gz9l9qqapnEAmXmR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Gz9l9qqapnEAmXmR .error-icon{fill:#552222;}#mermaid-svg-Gz9l9qqapnEAmXmR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Gz9l9qqapnEAmXmR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Gz9l9qqapnEAmXmR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Gz9l9qqapnEAmXmR .marker.cross{stroke:#333333;}#mermaid-svg-Gz9l9qqapnEAmXmR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Gz9l9qqapnEAmXmR p{margin:0;}#mermaid-svg-Gz9l9qqapnEAmXmR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster-label text{fill:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster-label span{color:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster-label span p{background-color:transparent;}#mermaid-svg-Gz9l9qqapnEAmXmR .label text,#mermaid-svg-Gz9l9qqapnEAmXmR span{fill:#333;color:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR .node rect,#mermaid-svg-Gz9l9qqapnEAmXmR .node circle,#mermaid-svg-Gz9l9qqapnEAmXmR .node ellipse,#mermaid-svg-Gz9l9qqapnEAmXmR .node polygon,#mermaid-svg-Gz9l9qqapnEAmXmR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Gz9l9qqapnEAmXmR .rough-node .label text,#mermaid-svg-Gz9l9qqapnEAmXmR .node .label text,#mermaid-svg-Gz9l9qqapnEAmXmR .image-shape .label,#mermaid-svg-Gz9l9qqapnEAmXmR .icon-shape .label{text-anchor:middle;}#mermaid-svg-Gz9l9qqapnEAmXmR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Gz9l9qqapnEAmXmR .rough-node .label,#mermaid-svg-Gz9l9qqapnEAmXmR .node .label,#mermaid-svg-Gz9l9qqapnEAmXmR .image-shape .label,#mermaid-svg-Gz9l9qqapnEAmXmR .icon-shape .label{text-align:center;}#mermaid-svg-Gz9l9qqapnEAmXmR .node.clickable{cursor:pointer;}#mermaid-svg-Gz9l9qqapnEAmXmR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Gz9l9qqapnEAmXmR .arrowheadPath{fill:#333333;}#mermaid-svg-Gz9l9qqapnEAmXmR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Gz9l9qqapnEAmXmR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Gz9l9qqapnEAmXmR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gz9l9qqapnEAmXmR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Gz9l9qqapnEAmXmR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gz9l9qqapnEAmXmR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster text{fill:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR .cluster span{color:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Gz9l9qqapnEAmXmR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Gz9l9qqapnEAmXmR rect.text{fill:none;stroke-width:0;}#mermaid-svg-Gz9l9qqapnEAmXmR .icon-shape,#mermaid-svg-Gz9l9qqapnEAmXmR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gz9l9qqapnEAmXmR .icon-shape p,#mermaid-svg-Gz9l9qqapnEAmXmR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Gz9l9qqapnEAmXmR .icon-shape .label rect,#mermaid-svg-Gz9l9qqapnEAmXmR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gz9l9qqapnEAmXmR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Gz9l9qqapnEAmXmR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Gz9l9qqapnEAmXmR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Tuple
Tuple Header
Tuple Body
xmin
xmax
flags
next version
id
name
age

为什么 tuple 需要 header?

因为数据库要回答这些问题:

text 复制代码
这条 tuple 是哪个事务创建的?
它有没有被删除或更新?
删除它的是哪个事务?
它有没有下一个版本?
当前事务能不能看到它?

这就是 MVCC 需要的信息。

所以一条数据库里的"行",不是只有用户定义的字段。它还带着事务和版本控制信息。

Free Space:page 里最容易被低估的部分

Page 里通常会保留一块 free space。

插入新 tuple 时,从 free space 里分配空间。

slot array 往一个方向增长,tuple data 往另一个方向增长。中间剩下的就是 free space。

text 复制代码
+-----------------------------+
| Page Header                 |
+-----------------------------+
| Slot 0                      |
| Slot 1                      |
| Slot 2                      |
+-----------------------------+
| Free Space                  |
+-----------------------------+
| Tuple 2                     |
| Tuple 1                     |
| Tuple 0                     |
+-----------------------------+

用图看:
#mermaid-svg-VkzOPAgh740gRJYh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VkzOPAgh740gRJYh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VkzOPAgh740gRJYh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VkzOPAgh740gRJYh .error-icon{fill:#552222;}#mermaid-svg-VkzOPAgh740gRJYh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VkzOPAgh740gRJYh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VkzOPAgh740gRJYh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VkzOPAgh740gRJYh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VkzOPAgh740gRJYh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VkzOPAgh740gRJYh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VkzOPAgh740gRJYh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VkzOPAgh740gRJYh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VkzOPAgh740gRJYh .marker.cross{stroke:#333333;}#mermaid-svg-VkzOPAgh740gRJYh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VkzOPAgh740gRJYh p{margin:0;}#mermaid-svg-VkzOPAgh740gRJYh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VkzOPAgh740gRJYh .cluster-label text{fill:#333;}#mermaid-svg-VkzOPAgh740gRJYh .cluster-label span{color:#333;}#mermaid-svg-VkzOPAgh740gRJYh .cluster-label span p{background-color:transparent;}#mermaid-svg-VkzOPAgh740gRJYh .label text,#mermaid-svg-VkzOPAgh740gRJYh span{fill:#333;color:#333;}#mermaid-svg-VkzOPAgh740gRJYh .node rect,#mermaid-svg-VkzOPAgh740gRJYh .node circle,#mermaid-svg-VkzOPAgh740gRJYh .node ellipse,#mermaid-svg-VkzOPAgh740gRJYh .node polygon,#mermaid-svg-VkzOPAgh740gRJYh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VkzOPAgh740gRJYh .rough-node .label text,#mermaid-svg-VkzOPAgh740gRJYh .node .label text,#mermaid-svg-VkzOPAgh740gRJYh .image-shape .label,#mermaid-svg-VkzOPAgh740gRJYh .icon-shape .label{text-anchor:middle;}#mermaid-svg-VkzOPAgh740gRJYh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VkzOPAgh740gRJYh .rough-node .label,#mermaid-svg-VkzOPAgh740gRJYh .node .label,#mermaid-svg-VkzOPAgh740gRJYh .image-shape .label,#mermaid-svg-VkzOPAgh740gRJYh .icon-shape .label{text-align:center;}#mermaid-svg-VkzOPAgh740gRJYh .node.clickable{cursor:pointer;}#mermaid-svg-VkzOPAgh740gRJYh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VkzOPAgh740gRJYh .arrowheadPath{fill:#333333;}#mermaid-svg-VkzOPAgh740gRJYh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VkzOPAgh740gRJYh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VkzOPAgh740gRJYh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VkzOPAgh740gRJYh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VkzOPAgh740gRJYh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VkzOPAgh740gRJYh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VkzOPAgh740gRJYh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VkzOPAgh740gRJYh .cluster text{fill:#333;}#mermaid-svg-VkzOPAgh740gRJYh .cluster span{color:#333;}#mermaid-svg-VkzOPAgh740gRJYh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VkzOPAgh740gRJYh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VkzOPAgh740gRJYh rect.text{fill:none;stroke-width:0;}#mermaid-svg-VkzOPAgh740gRJYh .icon-shape,#mermaid-svg-VkzOPAgh740gRJYh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VkzOPAgh740gRJYh .icon-shape p,#mermaid-svg-VkzOPAgh740gRJYh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VkzOPAgh740gRJYh .icon-shape .label rect,#mermaid-svg-VkzOPAgh740gRJYh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VkzOPAgh740gRJYh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VkzOPAgh740gRJYh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VkzOPAgh740gRJYh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Page Layout
Header
Slot Array grows downward
Free Space
Tuple Data grows upward

为什么 slot 和 tuple 要从两头往中间长?

因为 slot 是固定较小的结构,tuple 是变长数据。它们从两端增长,可以更灵活地利用 page 内空间。

插入时:

text 复制代码
新增一个 slot
从 tuple data 区域分配 tuple 空间
free space 变小

删除时:

text 复制代码
tuple 先变成不可见或 dead
空间之后由 pruning / vacuum 回收

更新时:

text 复制代码
旧版本可能保留
新版本可能插入同页
free space 继续减少

所以 page 内部会出现碎片。数据库需要 compact、prune、vacuum 等机制整理空间。

Page 内部的一次插入

现在把插入流程放到 page 内部看。

假设 page 里还有空间,插入一条 tuple。

步骤大致是:

text 复制代码
1. 检查 free space 是否够
2. 分配一个 slot
3. 在 tuple data 区域写入 tuple
4. slot 记录 tuple offset 和 length
5. 更新 page header
6. 标记 page dirty

#mermaid-svg-8lEFczR3lEWCMv0Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8lEFczR3lEWCMv0Q .error-icon{fill:#552222;}#mermaid-svg-8lEFczR3lEWCMv0Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8lEFczR3lEWCMv0Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8lEFczR3lEWCMv0Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8lEFczR3lEWCMv0Q .marker.cross{stroke:#333333;}#mermaid-svg-8lEFczR3lEWCMv0Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8lEFczR3lEWCMv0Q p{margin:0;}#mermaid-svg-8lEFczR3lEWCMv0Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster-label text{fill:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster-label span{color:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster-label span p{background-color:transparent;}#mermaid-svg-8lEFczR3lEWCMv0Q .label text,#mermaid-svg-8lEFczR3lEWCMv0Q span{fill:#333;color:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q .node rect,#mermaid-svg-8lEFczR3lEWCMv0Q .node circle,#mermaid-svg-8lEFczR3lEWCMv0Q .node ellipse,#mermaid-svg-8lEFczR3lEWCMv0Q .node polygon,#mermaid-svg-8lEFczR3lEWCMv0Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8lEFczR3lEWCMv0Q .rough-node .label text,#mermaid-svg-8lEFczR3lEWCMv0Q .node .label text,#mermaid-svg-8lEFczR3lEWCMv0Q .image-shape .label,#mermaid-svg-8lEFczR3lEWCMv0Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-8lEFczR3lEWCMv0Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8lEFczR3lEWCMv0Q .rough-node .label,#mermaid-svg-8lEFczR3lEWCMv0Q .node .label,#mermaid-svg-8lEFczR3lEWCMv0Q .image-shape .label,#mermaid-svg-8lEFczR3lEWCMv0Q .icon-shape .label{text-align:center;}#mermaid-svg-8lEFczR3lEWCMv0Q .node.clickable{cursor:pointer;}#mermaid-svg-8lEFczR3lEWCMv0Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8lEFczR3lEWCMv0Q .arrowheadPath{fill:#333333;}#mermaid-svg-8lEFczR3lEWCMv0Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8lEFczR3lEWCMv0Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8lEFczR3lEWCMv0Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8lEFczR3lEWCMv0Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8lEFczR3lEWCMv0Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8lEFczR3lEWCMv0Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster text{fill:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q .cluster span{color:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8lEFczR3lEWCMv0Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8lEFczR3lEWCMv0Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-8lEFczR3lEWCMv0Q .icon-shape,#mermaid-svg-8lEFczR3lEWCMv0Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8lEFczR3lEWCMv0Q .icon-shape p,#mermaid-svg-8lEFczR3lEWCMv0Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8lEFczR3lEWCMv0Q .icon-shape .label rect,#mermaid-svg-8lEFczR3lEWCMv0Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8lEFczR3lEWCMv0Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8lEFczR3lEWCMv0Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8lEFczR3lEWCMv0Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} no
yes
Insert tuple
Check free space
Enough?
Need another page
Allocate slot
Write tuple data
Set line pointer
Update page header
Mark page dirty

注意这里还没讲 WAL。

真实执行时,数据库不能随便先改 page。它还要先写 WAL,确保崩溃后能恢复。后面讲 WAL 时会专门展开。

Page 内部的一次删除

删除一条 tuple,不是把 tuple data 清零。

一般流程是:

text 复制代码
1. 找到 RID 对应 slot
2. 找到 tuple
3. 设置 tuple.xmax
4. 更新 tuple header
5. 之后由 GC / VACUUM 判断是否能标记 dead

#mermaid-svg-8qZtjJfo9VRhy193{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8qZtjJfo9VRhy193 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8qZtjJfo9VRhy193 .error-icon{fill:#552222;}#mermaid-svg-8qZtjJfo9VRhy193 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8qZtjJfo9VRhy193 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8qZtjJfo9VRhy193 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8qZtjJfo9VRhy193 .marker.cross{stroke:#333333;}#mermaid-svg-8qZtjJfo9VRhy193 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8qZtjJfo9VRhy193 p{margin:0;}#mermaid-svg-8qZtjJfo9VRhy193 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8qZtjJfo9VRhy193 .cluster-label text{fill:#333;}#mermaid-svg-8qZtjJfo9VRhy193 .cluster-label span{color:#333;}#mermaid-svg-8qZtjJfo9VRhy193 .cluster-label span p{background-color:transparent;}#mermaid-svg-8qZtjJfo9VRhy193 .label text,#mermaid-svg-8qZtjJfo9VRhy193 span{fill:#333;color:#333;}#mermaid-svg-8qZtjJfo9VRhy193 .node rect,#mermaid-svg-8qZtjJfo9VRhy193 .node circle,#mermaid-svg-8qZtjJfo9VRhy193 .node ellipse,#mermaid-svg-8qZtjJfo9VRhy193 .node polygon,#mermaid-svg-8qZtjJfo9VRhy193 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8qZtjJfo9VRhy193 .rough-node .label text,#mermaid-svg-8qZtjJfo9VRhy193 .node .label text,#mermaid-svg-8qZtjJfo9VRhy193 .image-shape .label,#mermaid-svg-8qZtjJfo9VRhy193 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8qZtjJfo9VRhy193 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8qZtjJfo9VRhy193 .rough-node .label,#mermaid-svg-8qZtjJfo9VRhy193 .node .label,#mermaid-svg-8qZtjJfo9VRhy193 .image-shape .label,#mermaid-svg-8qZtjJfo9VRhy193 .icon-shape .label{text-align:center;}#mermaid-svg-8qZtjJfo9VRhy193 .node.clickable{cursor:pointer;}#mermaid-svg-8qZtjJfo9VRhy193 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8qZtjJfo9VRhy193 .arrowheadPath{fill:#333333;}#mermaid-svg-8qZtjJfo9VRhy193 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8qZtjJfo9VRhy193 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8qZtjJfo9VRhy193 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qZtjJfo9VRhy193 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8qZtjJfo9VRhy193 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qZtjJfo9VRhy193 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8qZtjJfo9VRhy193 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8qZtjJfo9VRhy193 .cluster text{fill:#333;}#mermaid-svg-8qZtjJfo9VRhy193 .cluster span{color:#333;}#mermaid-svg-8qZtjJfo9VRhy193 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8qZtjJfo9VRhy193 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8qZtjJfo9VRhy193 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8qZtjJfo9VRhy193 .icon-shape,#mermaid-svg-8qZtjJfo9VRhy193 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qZtjJfo9VRhy193 .icon-shape p,#mermaid-svg-8qZtjJfo9VRhy193 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8qZtjJfo9VRhy193 .icon-shape .label rect,#mermaid-svg-8qZtjJfo9VRhy193 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qZtjJfo9VRhy193 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8qZtjJfo9VRhy193 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8qZtjJfo9VRhy193 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DELETE by RID
Find slot
Find tuple
Set xmax
Visibility changes by MVCC
Later GC / VACUUM
LP_DEAD or reusable

这也是为什么删除之后表文件不会马上变小。

数据库里的删除更像是"逻辑删除 + 延迟物理清理"。

Page 内部的一次更新

更新更复杂。

如果是普通更新:

text 复制代码
1. 旧 tuple 设置 xmax
2. 新 tuple 插入 page 或其他 page
3. 建立版本链
4. 如果索引列变化,更新索引

如果是 HOT-style update:

text 复制代码
1. 旧版本和新版本在同一个 page
2. 索引列不变
3. 旧 slot 可以 redirect 到新版本
4. 索引 entry 不需要改变

简化图:
#mermaid-svg-h1Otq4NQxNxwGLJd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h1Otq4NQxNxwGLJd .error-icon{fill:#552222;}#mermaid-svg-h1Otq4NQxNxwGLJd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h1Otq4NQxNxwGLJd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h1Otq4NQxNxwGLJd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h1Otq4NQxNxwGLJd .marker.cross{stroke:#333333;}#mermaid-svg-h1Otq4NQxNxwGLJd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h1Otq4NQxNxwGLJd p{margin:0;}#mermaid-svg-h1Otq4NQxNxwGLJd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster-label text{fill:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster-label span{color:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster-label span p{background-color:transparent;}#mermaid-svg-h1Otq4NQxNxwGLJd .label text,#mermaid-svg-h1Otq4NQxNxwGLJd span{fill:#333;color:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd .node rect,#mermaid-svg-h1Otq4NQxNxwGLJd .node circle,#mermaid-svg-h1Otq4NQxNxwGLJd .node ellipse,#mermaid-svg-h1Otq4NQxNxwGLJd .node polygon,#mermaid-svg-h1Otq4NQxNxwGLJd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h1Otq4NQxNxwGLJd .rough-node .label text,#mermaid-svg-h1Otq4NQxNxwGLJd .node .label text,#mermaid-svg-h1Otq4NQxNxwGLJd .image-shape .label,#mermaid-svg-h1Otq4NQxNxwGLJd .icon-shape .label{text-anchor:middle;}#mermaid-svg-h1Otq4NQxNxwGLJd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h1Otq4NQxNxwGLJd .rough-node .label,#mermaid-svg-h1Otq4NQxNxwGLJd .node .label,#mermaid-svg-h1Otq4NQxNxwGLJd .image-shape .label,#mermaid-svg-h1Otq4NQxNxwGLJd .icon-shape .label{text-align:center;}#mermaid-svg-h1Otq4NQxNxwGLJd .node.clickable{cursor:pointer;}#mermaid-svg-h1Otq4NQxNxwGLJd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h1Otq4NQxNxwGLJd .arrowheadPath{fill:#333333;}#mermaid-svg-h1Otq4NQxNxwGLJd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h1Otq4NQxNxwGLJd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h1Otq4NQxNxwGLJd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h1Otq4NQxNxwGLJd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h1Otq4NQxNxwGLJd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h1Otq4NQxNxwGLJd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster text{fill:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd .cluster span{color:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h1Otq4NQxNxwGLJd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h1Otq4NQxNxwGLJd rect.text{fill:none;stroke-width:0;}#mermaid-svg-h1Otq4NQxNxwGLJd .icon-shape,#mermaid-svg-h1Otq4NQxNxwGLJd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h1Otq4NQxNxwGLJd .icon-shape p,#mermaid-svg-h1Otq4NQxNxwGLJd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h1Otq4NQxNxwGLJd .icon-shape .label rect,#mermaid-svg-h1Otq4NQxNxwGLJd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h1Otq4NQxNxwGLJd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h1Otq4NQxNxwGLJd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h1Otq4NQxNxwGLJd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} yes
no
UPDATE tuple
Indexed column changed?
Normal update
old tuple xmax
new tuple
new index entry
HOT-style update
LP_REDIRECT
new tuple version

这个地方是数据库存储层里非常典型的工程取舍。

普通更新逻辑更简单,但索引写入更多。

HOT 更新逻辑更复杂,但能减少索引维护成本。

MiniDB 支持 HOT-style update,是它比很多教学数据库更接近真实数据库的地方。

MiniDB 的 Page 设计和 PostgreSQL 的关系

MiniDB 的存储模型明显借鉴了 PostgreSQL 风格。

相似点包括:

text 复制代码
固定大小 page
Page Header
Line Pointer Array
LP_NORMAL / LP_REDIRECT / LP_DEAD
MVCC tuple header
HOT-style update
VACUUM / pruning
Visibility Map / Free Space Map

但 MiniDB 不是 PostgreSQL 的复制版。

它是一个教学和实验用数据库内核,很多地方做了简化。比如 page header 更紧凑,索引恢复策略也和 PostgreSQL 不一样。MiniDB 当前更重视把核心路径跑通,让学习者能在较小代码规模里看到完整数据库的骨架。

这其实是它的价值所在。

PostgreSQL 的实现非常成熟,但也非常复杂。MiniDB 用更小的实现把这些关键概念串起来,适合作为理解现代数据库存储层的入口。

为什么 Page Layout 很重要

Page layout 听起来像底层细节,但它会影响很多上层能力。

它影响索引:

text 复制代码
索引保存 RID
RID 依赖 page_id + slot_id
slot 稳定性影响索引正确性

它影响事务:

text 复制代码
tuple header 保存 xmin / xmax
MVCC 判断依赖这些字段

它影响更新:

text 复制代码
是否支持 HOT
版本链怎么组织
旧版本怎么保留

它影响删除:

text 复制代码
什么时候 LP_DEAD
什么时候空间可复用

它影响恢复:

text 复制代码
page_lsn 记录 page 修改进度
WAL redo 要看 page_lsn

它影响性能:

text 复制代码
page 内碎片多不多
tuple 是否需要移动
free space 是否容易找到

所以 page layout 不是"存储格式小细节",而是数据库很多机制的地基。

小结

一个 page 不是简单的数据块。

它通常包含:

text 复制代码
Page Header:记录 page 元信息
Line Pointer Array:给 tuple 提供稳定入口
Free Space:插入和更新可用空间
Tuple Data:真实行数据和 MVCC header

Line pointer 让 RID 稳定,tuple 可以在 page 内部移动。LP_NORMALLP_REDIRECTLP_DEAD 让 page 能表达普通 tuple、HOT redirect 和 dead tuple。Tuple header 里的 xmin/xmax 让事务系统可以判断版本可见性。

MiniDB 的 page 设计采用 PostgreSQL 风格,但保持了教学项目的相对简洁。理解这一层之后,再看 HeapFile、MVCC、HOT、VACUUM、IndexOnlyScan、WAL,都会更容易。

下一篇会进入 Buffer Pool,讲数据库为什么需要自己管理内存缓存,以及 page 是如何在内存和磁盘之间流动的。

相关推荐
代码地平线1 小时前
C++ 入门篇类和对象·上篇:从本质深剖类与对象与C++基本用法
c语言·开发语言·数据结构·c++·笔记·算法
十五年专注C++开发1 小时前
C++17之类模板实参自动推导CTAD
开发语言·c++·聚合初始化·catd
日取其半万世不竭1 小时前
密码管理工具私有化部署,Vaultwarden 备份恢复怎么做?
数据库·docker·容器
填满你的记忆1 小时前
《为什么 MySQL 不适合做 AI 检索?》
数据库·人工智能·mysql·ai·向量数据库
map1e_zjc1 小时前
Redis入门笔记
数据库·redis·缓存
星马梦缘1 小时前
ACM笔记 学习版本
数据结构·c++·算法
步十人1 小时前
【Redis】高可用集群架构
数据库·redis·架构
霸道流氓气质1 小时前
批量异步处理 + MQ + Redis 进度追踪实战指南
数据库·redis·状态模式
smart19981 小时前
数据备份解决方案,适合金融等关键业务需求
数据库·科技·存储