hdr.go 6.3 KB

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