BasePage.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package page
  2. import (
  3. "errors"
  4. "unsafe"
  5. )
  6. type PageType byte
  7. const (
  8. TableIndex PageType = 1 // 表页
  9. DataIndex PageType = 2 // 数据页
  10. HeapIndex PageType = 3 // 堆页
  11. IndexMetaIndex PageType = 4 // 索引元数据页(全局)
  12. IndexDataIndex PageType = 5 // 索引数据页(B+树节点)
  13. DataOverflowPage PageType = 6 // 数据页溢出页 (已废弃: schema 续块改用 SchemaExtIndex)
  14. WALIndex PageType = 7 // WAL 日志页
  15. SchemaExtIndex PageType = 8 // 表结构扩展页(全库共享, 存各表 schema 续块)
  16. BlobIndex PageType = 9 // Blob 链页 (v10 新增: 一页 = 一个 blob chunk, 链由 PageHdr.NextPage 串)
  17. )
  18. // 操作类的回调函数
  19. type HandleDb struct {
  20. NewPage func() uint32
  21. // FreePage 立即回收页: 直写盘 + 把页挂上空闲链表。
  22. // 只适合"收尾类"路径 (DropTable 的整表回收), 那条路径依赖空闲链立刻变化。
  23. FreePage func(pageID uint32)
  24. // DeferFreePage 延迟回收页: 只登记, 等 Commit(提交成功) / Close 后再真正还链。
  25. //
  26. // 为什么需要它: 在"先 WAL 后回写"的提交顺序下, 修改中途直写盘会造出
  27. // "页已清零、WAL 尚未提交"的中间态 —— 崩溃或 Rollback 之后, 原页在磁盘上
  28. // 已经变成空闲链表节点(内容全空), 而堆链/索引仍然指向它 → 数据丢失。
  29. // 事务中途也不能回退 header.FreePageStart/FreePageCount (直写盘绕过 BufferPool)。
  30. DeferFreePage func(pageID uint32)
  31. SavePage func(pageID uint32, buf []byte) error
  32. ReadPage func(pageID uint32, buf []byte) error
  33. // FreeChain 把一条已经用 PageHdr.NextPage 串好的整链 (head…tail) 一次性挂到
  34. // 空闲链表尾, O(1) 完成 —— 调用方已持有链首/链尾/链长, 不需要逐页遍历。
  35. //
  36. // 前提: 链内每页的 next 都写在 PageHdr.NextPage (与空闲链同一字段), 且
  37. // tail.NextPage == 0。用于 DropTable 的堆链、WAL 提交/恢复后的整条 WAL 链,
  38. // 以及哈希索引的桶目录链/桶溢出链 (桶页 next 已统一到 PageHdr.NextPage)。
  39. FreeChain func(head, tail, count uint32) error
  40. // DeferFreeChain 延迟整链回收: 时序约定与 DeferFreePage 完全一致 —— 只登记,
  41. // 等 Commit(提交成功) / Close 之后才真正还链, 避免事务中途直写盘造出
  42. // "页已清零、WAL 尚未提交"的中间态。
  43. //
  44. // 与 DeferFreePage 的区别: 一条链只登记一次 (回收方已持有 head/tail/count),
  45. // 提交后一次 FreeChain 还链, 不需要逐页登记、逐页改写空闲链。
  46. // nil 时调用方应退回逐页 DeferFreePage。
  47. DeferFreeChain func(head, tail, count uint32)
  48. // ValidPage 校验页号是否落在「已分配区间」内, 返回 false 的页号一律不得还链或被引用。
  49. // 供给方的标准实现: 2 <= pageID && pageID < header.NextFreePage
  50. // (0 是头页, 1 是建库预留的表页, 都不可回收; >= NextFreePage 的页从未分配过)。
  51. //
  52. // 为什么必须有: 页枚举路径 (btree.CollectAllPageIDs / btree.GetDataPageIDs / 链遍历)
  53. // 只按偏移解析页头, 不看页类型 —— 一旦索引元数据的 EntryPage 指向一张被回收复用过的页
  54. // (DataPage / 堆页 / WAL 页), 行数据会被当成 B+ 条目解析: IndexHead.Count 落在行键
  55. // Min/Max 上就是个大整数, btPID 于是从任意位置截出 4 字节, 吐出「246 万」这种垃圾页号。
  56. // 这种页号一旦交给 FreePage:
  57. // 1) 空闲链头指向文件里不存在的位置 → 下一次 NewPage 拿到它, 所有写入跑到几十 GB 偏移;
  58. // 2) 每次还链 = 读+写整页 + 写 4KB 头页, 上千个垃圾页号能让 DROP TABLE 假死到分钟级。
  59. // nil 表示不做校验 (仅老 demo 手搓的 HandleDb)。
  60. ValidPage func(pageID uint32) bool
  61. }
  62. // ======================== 索引共享类型 ========================
  63. // IndexPageSize 索引页大小 (字节)
  64. const IndexPageSize = 16384
  65. // IndexType 索引类型
  66. type IndexType byte
  67. const (
  68. IndexTypeTime IndexType = 1 // 时序索引
  69. IndexTypePrimary IndexType = 2 // 主键索引
  70. IndexTypeStr IndexType = 3 // 已废弃,合并到 IndexTypeBTree (保留仅为向后兼容旧数据库文件)
  71. IndexTypeCompose IndexType = 4 // 复合索引
  72. IndexTypeBTree IndexType = 5 // B+树二级索引 (叶子存 PKID)
  73. IndexTypeHash IndexType = 6 // Hash 桶式二级索引 (等值查找 O(1))
  74. )
  75. // IndexHead B+ 树节点头 / 索引页头 (从页缓冲区 unsafe 解析)
  76. //
  77. // 注意字段之间有编译器填充: Hdr(20B) 之后 IndexType 占 1 字节, 而 EntryLen 是
  78. // uint16 需 2 字节对齐, 于是偏移 21 处垫 1 字节 —— 结构体实际 32 字节, 不是各
  79. // 字段简单相加的 29。尾部 _ [3]byte 是显式填充, 让整体对齐到 4 字节。
  80. type IndexHead struct {
  81. Hdr PageHdr // off 0 20 bytes
  82. IndexType IndexType // off 20 1 byte
  83. EntryLen uint16 // off 22 2 bytes (off 21 是编译器填充)
  84. Count uint32 // off 24 4 bytes
  85. IsLeaf byte // off 28 1 byte
  86. _ [3]byte // off 29 显式填充
  87. }
  88. // IndexHeadSize IndexHead 结构体字节大小
  89. const IndexHeadSize = int(unsafe.Sizeof(IndexHead{}))
  90. // BTreeFillFactor B+ 树分裂填充因子 (百分比 50~100)
  91. var BTreeFillFactor = 100
  92. // 索引共享错误
  93. var (
  94. ErrNotFound = errors.New("key not found")
  95. ErrRowTooLarge = errors.New("row too large, need heap overflow")
  96. ErrDuplicateKey = errors.New("duplicate key")
  97. )