| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- package page
- import "unsafe"
- // =============================================================================
- // PageHdr —— 全库所有页的统一头部, 固定 20 字节, 位于每页偏移 0。
- //
- // 布局 (字段顺序经对齐验证, 无隐式填充):
- //
- // off 0 Type 1B 页类型 (见 PageType)
- // off 1 Flags 1B 页状态位 (保留, 见 SetFlag/HasFlag)
- // off 2 RefCount 2B 引用计数 (仅堆页使用, 归零 ⟺ 可回收)
- // off 4 PageID 4B 当前页 ID
- // off 8 PrePage 4B 前驱页 (双向链用, 0 表示无)
- // off 12 NextPage 4B 后继页 —— 堆链/索引链/空闲链/WAL 链共用此字段
- // off 16 UseSize 4B 已用字节数 (含页头)
- //
- // 字段顺序不是随意的, 它同时被三条既有约定锁住:
- // - NextPage 必须落在偏移 12: 空闲链表与 WAL 链都用它当链指针 (见 const.go 的
- // Version 注释、HeadPage.go 的 recyclePage)。
- // - UseSize 必须落在偏移 16: DataPage 与公共页头共用这一个 UseSize 字段
- // (见 page.DataPageHdr)。
- // - 整个头部必须恰好 20 字节: HeapInfo.Data 的长度是 PageSize-PageHdrSize,
- // 页头一旦膨胀, 全库页内偏移集体平移。
- //
- // 把两个 1 字节字段 (Type/Flags) 排在最前、2 字节的 RefCount 紧随其后, 前 4 字节
- // 刚好填满, 后面的 uint32 全部自然对齐。若把 RefCount 直接跟在 Type 后面, 编译器
- // 会为 2 字节对齐插入 padding, 页头就涨到 24 字节 —— 下面的编译期断言拦的就是这个。
- // =============================================================================
- type PageHdr struct {
- Type PageType // off 0 页类型
- Flags byte // off 1 页状态位 (保留)
- RefCount uint16 // off 2 引用计数 (仅堆页使用)
- PageID uint32 // off 4 当前页 ID
- PrePage uint32 // off 8 前一页 (0 表示开始)
- NextPage uint32 // off 12 下一页 (0 表示结束); 堆链/索引链/空闲链/WAL 链共用
- UseSize uint32 // off 16 已用字节数 (含页头)
- }
- // PageHdrSize 页头长度 (字节)
- const PageHdrSize = uint32(unsafe.Sizeof(PageHdr{}))
- // 编译期布局断言 —— 任一条被破坏都直接编译失败, 而不是运行时静默错位。
- // 改动 PageHdr 字段时这里会先报错, 提醒你同步检查上面三条约定。
- var (
- _ [20]byte = [unsafe.Sizeof(PageHdr{})]byte{} // 页头必须 20 字节
- _ [12]byte = [unsafe.Offsetof(PageHdr{}.NextPage)]byte{} // NextPage 必须偏移 12
- _ [16]byte = [unsafe.Offsetof(PageHdr{}.UseSize)]byte{} // UseSize 必须偏移 16
- )
- // HdrOf 把页缓冲区按 PageHdr 解析 (页头固定在偏移 0)。
- //
- // 这是全库唯一的页头解析入口 —— 请不要再散写 (*PageHdr)(unsafe.Pointer(&buf[0])),
- // 那样页头布局一变就要满库去搜。
- func HdrOf(buf []byte) *PageHdr {
- return (*PageHdr)(unsafe.Pointer(&buf[0]))
- }
- // Init 把页头重置为"新建空页": 写入类型/页号, 其余字段清零, UseSize 指向页头之后。
- func (h *PageHdr) Init(pageType PageType, pageID uint32) {
- h.Type = pageType
- h.Flags = 0
- h.RefCount = 0
- h.PageID = pageID
- h.PrePage = 0
- h.NextPage = 0
- h.UseSize = PageHdrSize
- }
- // ---------------------------------------------------------------------------
- // 引用计数
- //
- // 语义: RefCount 记录"有多少个活引用指向本页", 归零表示页内数据块已全部失效,
- // 可整页摘链回收。目前仅堆页使用; 其他页类型忽略该字段 (恒为 0)。
- //
- // 为什么只有堆页需要 —— 判据是"页内还有没有活数据, 能不能不靠外挂计数推出来":
- //
- // - 堆页推不出来, 所以必须计数。堆页的引用是"反向且分散"的: 指针 (6 字节
- // pageID+off) 写在行数据里, 而行本身散落在各个 DataPage 中, 没有任何单一结构
- // 能汇总"这张堆页还被谁指着"。堆页内部也没有活块清单 —— 块是 append-only 的
- // 字节流, 删行只是把空间变成垃圾, 不在页内留任何标记 (见 DB/core/Table.go
- // 的 heapUnref)。
- //
- // - 其他页都能推出来, 加了反而是"第二份真相"。两份真相一旦不同步就是静默错误:
- // 计数偏高 → 页永不回收(泄漏), 偏低 → 页提前回收(丢数据), 且都不会崩。
- // DataPage 每条记录带软删位, AllDeleted() 一扫即知; B+ 树节点由父节点在
- // split/merge 时显式处理且只有唯一父; 表页/元数据页由 HeaderPage 直接持有;
- // 模式扩展页/WAL 页是 append-only、整链一次性回收。
- //
- // 容量: RefCount 是 uint16 (上限 65535), 而堆页块数的物理上限只有 3272 ——
- // 数据区 16364 字节 (PageSize - PageHdrSize) 除以最小块 5 字节 (长度前缀
- // dynamicBlockHdr 4 + 最小载荷 1), 余量约 20 倍, 不存在溢出可能。该上限的前提是
- // "1 引用 = 1 块, 且块不共享" (DB/core/Table.go 的 heapAllocBlock 每次都开新块)。
- // 若将来做块级去重/多行共用一个堆块, 前提即破, 上限变成"全库行数", uint16 会
- // 静默回绕到 0 → 页被误判为无引用而提前回收。
- // ---------------------------------------------------------------------------
- // AddRef 引用计数 +1 (本页新增一个引用)。
- func (h *PageHdr) AddRef() { h.RefCount++ }
- // DecRef 引用计数 -1, 返回计数是否已归零。
- //
- // 归零是唯一触发"摘链回收"的信号, 所以判定只在这一刻做 —— 调用方拿到 true
- // 才去摘链。已为 0 时不再下降 (仍返回 true), 避免 uint16 下溢成 65535。
- func (h *PageHdr) DecRef() bool {
- if h.RefCount > 0 {
- h.RefCount--
- }
- return h.RefCount == 0
- }
- // HasRef 是否仍有活引用。
- func (h *PageHdr) HasRef() bool { return h.RefCount > 0 }
- // ---------------------------------------------------------------------------
- // 页状态位
- //
- // Flags 会随页落盘, 某一位一旦写进磁盘其语义就固定下来, 所以这里只提供存取
- // 方法、不预设任何位。新增位时在下面登记成 PageFlag* 常量。
- // ---------------------------------------------------------------------------
- // SetFlag 置位。
- func (h *PageHdr) SetFlag(f byte) { h.Flags |= f }
- // ClearFlag 清位。
- func (h *PageHdr) ClearFlag(f byte) { h.Flags &^= f }
- // HasFlag 判断某位是否置位。
- func (h *PageHdr) HasFlag(f byte) bool { return h.Flags&f != 0 }
|