primary_handler.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package index
  2. import (
  3. "TsDB2/DB/core/index/btree"
  4. "TsDB2/DB/core/index/datapage"
  5. "TsDB2/DB/core/page"
  6. "encoding/binary"
  7. )
  8. // PrimaryHandler — 主键 B+ 树索引 (IndexTypePrimary)
  9. // 存储: storeRow = [pkID(8B LE) + rowData]
  10. type PrimaryHandler struct{}
  11. func init() {
  12. Compose.RegisterHandler(page.IndexTypePrimary, &PrimaryHandler{})
  13. // 主键 B+ 树分裂参数 —— 写死在本文件, 不随索引元数据传递。
  14. // 主键多为自增/递增: 顺序追加时右叶只保留 1 条 (SequentialRight), 避免每次
  15. // 分裂都把整页对半搬运; 非顺序插入仍按 SplitPercent 的比例分裂。
  16. btree.PKSplitCfg = btree.SplitConfig{SplitPercent: 50, SequentialRight: true}
  17. }
  18. func (h *PrimaryHandler) Insert(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, rowData []byte, keyLen int) (uint32, error) {
  19. // 一次分配即得 [pkID(8B BE) + rowData]: 前 8 字节同时作为 B+ 树 key (只读)
  20. storeRowData := make([]byte, 8+len(rowData))
  21. binary.BigEndian.PutUint64(storeRowData[:8], pkID)
  22. copy(storeRowData[8:], rowData)
  23. // 主键 B+ 树始终以 pkID(8B BE) 为 key, 与 Search/Delete/Update 保持一致
  24. return h.insertPrebuilt(handle, entryPage, storeRowData[:8], storeRowData)
  25. }
  26. // insertPrebuilt 写入已构建好的 storeRowData (storeRowData = [pkID(8B BE) + rowData])
  27. func (h *PrimaryHandler) insertPrebuilt(handle page.HandleDb, entryPage *uint32, indexKey []byte, storeRowData []byte) (uint32, error) {
  28. rootPID := *entryPage
  29. rowSize := uint32(len(storeRowData)) + 4 // 4B len prefix + row data
  30. // 尝试复用 DataPage: 只在路由目标叶子内查找
  31. if rootPID != 0 {
  32. if dpID, found := btree.FindLastLeafDP(handle, rootPID, rowSize, indexKey); found {
  33. dp, err := datapage.Load(handle, dpID)
  34. if err == nil {
  35. // 用 InsertRowSorted 而不是 AppendRow: 主键由调用方显式给出时
  36. // (自定义主键 / BaseModel.ID) 插入顺序与 pkID 顺序无关, 而
  37. // FindRowByPK 依赖"页内行按 pkID 升序"提前终止 —— 追加小号会让
  38. // 行在页里却查不到 (详见 datapage.InsertRowSorted 的说明)。
  39. if dp.InsertRowSorted(storeRowData) {
  40. dp.Save()
  41. newRoot, _, dup, err := btree.BTreePKInsert(rootPID, indexKey, dpID, handle)
  42. if err != nil {
  43. return 0, err
  44. }
  45. if !dup {
  46. *entryPage = newRoot
  47. }
  48. return newRoot, nil
  49. }
  50. }
  51. }
  52. }
  53. // 无可用空间或树为空: 创建新 DataPage
  54. dp := datapage.New(handle)
  55. dp.AppendRow(storeRowData)
  56. dp.Save()
  57. newRoot, _, _, err := btree.BTreePKInsert(rootPID, indexKey, dp.PageID(), handle)
  58. if err != nil {
  59. return 0, err
  60. }
  61. *entryPage = newRoot
  62. return newRoot, nil
  63. }
  64. func (h *PrimaryHandler) Search(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int, resolve RowResolver) ([]byte, bool) {
  65. // 主键索引自己就是行数据所在, 不需要回表 (resolve 仅对二级索引有意义)
  66. if entryPage == 0 || len(indexKey) < 8 {
  67. return nil, false
  68. }
  69. // 主键索引的键就是 pkID (见 Insert: 叶键 = storeRow[:8]), 所以这里必须按 pkID 定位。
  70. // 旧实现是 dp.FindRowByIndexKey(indexKey, 9) —— 即"拿 key 去和第 1 个字段的值比",
  71. // 只有当第 1 列的值恒等于 pkID (ORM 的 _id/_ts) 时才凑巧成立; 主键索引声明在别的
  72. // 列上时, SearchByIndex(pk_.., pkKey(id)) 会查不到行 (数据在, 但 pkID 不可达),
  73. // 而且它按偏移 9 匹配的是"页内第一行满足者", 大表上还会路由到别的页而漏行。
  74. // 统一按 pkID 走, 与 SearchByPK/DeleteByPK/Update/ResolveRow 同一套语义。
  75. return h.SearchByPK(handle, entryPage, binary.BigEndian.Uint64(indexKey[:8]))
  76. }
  77. // IsRowStore 主键索引直接承载行数据
  78. func (h *PrimaryHandler) IsRowStore() bool { return true }
  79. // ResolveRow 按 pkID 读取整行。
  80. // 主键 B+ 树的 DataPage 按 pkID 有序, 用 cache (各页 pkID 区间) 二分即可定位;
  81. // cache 未命中时退回 B+ 树路由 (内含全叶扫描兜底), 保证不会漏行。
  82. func (h *PrimaryHandler) ResolveRow(handle page.HandleDb, entryPage uint32, pkID uint64, cache *[]DPRange) ([]byte, bool) {
  83. pkKeyBE := make([]byte, 8)
  84. binary.BigEndian.PutUint64(pkKeyBE, pkID)
  85. if cache != nil {
  86. if *cache == nil {
  87. var ranges []DPRange
  88. for _, pid := range btree.GetDataPageIDs(handle, entryPage) {
  89. dp, err := datapage.Load(handle, pid)
  90. if err != nil || dp.Count() == 0 {
  91. continue
  92. }
  93. ranges = append(ranges, DPRange{PageID: pid, MinPK: dp.MinPK(), MaxPK: dp.MaxPK()})
  94. }
  95. *cache = ranges
  96. }
  97. ranges := *cache
  98. // 二分: 找到最后一个 MinPK <= pkID 的页 (各页区间有序且不重叠)
  99. pos := -1
  100. lo, hi := 0, len(ranges)-1
  101. for lo <= hi {
  102. mid := (lo + hi) / 2
  103. if ranges[mid].MinPK <= pkID {
  104. pos = mid
  105. lo = mid + 1
  106. } else {
  107. hi = mid - 1
  108. }
  109. }
  110. if pos >= 0 && pkID <= ranges[pos].MaxPK {
  111. if dp, err := datapage.Load(handle, ranges[pos].PageID); err == nil {
  112. if row, ok := dp.FindRowByPK(pkKeyBE); ok {
  113. return row, true
  114. }
  115. }
  116. }
  117. }
  118. return h.SearchByPK(handle, entryPage, pkID)
  119. }
  120. func (h *PrimaryHandler) SearchByPK(handle page.HandleDb, entryPage uint32, pkID uint64) ([]byte, bool) {
  121. rootPID := entryPage
  122. if rootPID == 0 {
  123. return nil, false
  124. }
  125. pkKeyBE := make([]byte, 8)
  126. binary.BigEndian.PutUint64(pkKeyBE, pkID)
  127. dpID, found := btree.BTreePKSearch(rootPID, pkKeyBE, handle)
  128. if found {
  129. dp, err := datapage.Load(handle, dpID)
  130. if err == nil {
  131. if rowData, ok := dp.FindRowByPK(pkKeyBE); ok {
  132. return rowData, true
  133. }
  134. }
  135. }
  136. // 路由失败或未找到, 扫描所有叶子
  137. return btree.ScanAllLeavesForPK(rootPID, handle, pkKeyBE)
  138. }
  139. func (h *PrimaryHandler) Delete(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, keyLen int) error {
  140. rootPID := *entryPage
  141. if rootPID == 0 {
  142. return page.ErrNotFound
  143. }
  144. dpID, found := btree.BTreePKSearch(rootPID, indexKey, handle)
  145. if found {
  146. dp, err := datapage.Load(handle, dpID)
  147. if err == nil {
  148. deleted := false
  149. if dp.DeleteByIndexKey(indexKey, 8) {
  150. deleted = true
  151. } else if dp.DeleteByPK(indexKey) {
  152. deleted = true
  153. }
  154. if deleted {
  155. // 摘除本行在 B+ 树叶里的条目 (pk -> dpID)。必须「每次删除都做」,
  156. // 不能只在 DataPage 变空时做: 旧实现只在 AllDeleted() 时删除
  157. // 「触发变空的那一个键」, 同一页里先被删掉的行会留下陈旧叶条目。
  158. // 该 DataPage 随后被回收复用 (例如改作 WAL 页) 时, 这些条目就指向了
  159. // 非数据页, 空闲链上也挂着仍被索引引用的页
  160. // (analyzer 报 not-data-page / freeLive, 属数据损坏前兆)。
  161. newRoot, _, derr := btree.BTreePKDelete(rootPID, indexKey, handle)
  162. if derr != nil && derr != page.ErrNotFound {
  163. return derr
  164. }
  165. if newRoot != rootPID {
  166. *entryPage = newRoot
  167. }
  168. if dp.AllDeleted() {
  169. if handle.FreePage != nil {
  170. handle.FreePage(dpID)
  171. }
  172. } else {
  173. dp.Save()
  174. }
  175. return nil
  176. }
  177. }
  178. }
  179. // 索引键路由失败, 扫描所有叶子
  180. return btree.ScanAndDelete(rootPID, handle, indexKey, indexKey)
  181. }
  182. func (h *PrimaryHandler) DeleteByPK(handle page.HandleDb, entryPage *uint32, pkID uint64) error {
  183. rootPID := *entryPage
  184. if rootPID == 0 {
  185. return page.ErrNotFound
  186. }
  187. pkKeyBE := make([]byte, 8)
  188. binary.BigEndian.PutUint64(pkKeyBE, pkID)
  189. dpID, found := btree.BTreePKSearch(rootPID, pkKeyBE, handle)
  190. if found {
  191. dp, err := datapage.Load(handle, dpID)
  192. if err == nil {
  193. if dp.DeleteByPK(pkKeyBE) {
  194. // 同 Delete: 每次成功删除都要摘掉本行的叶条目, 否则 DataPage 回收复用后
  195. // 会留下指向非数据页的陈旧条目 (详见 Delete 处的说明)。
  196. newRoot, _, derr := btree.BTreePKDelete(rootPID, pkKeyBE, handle)
  197. if derr != nil && derr != page.ErrNotFound {
  198. return derr
  199. }
  200. if newRoot != rootPID {
  201. *entryPage = newRoot
  202. }
  203. if dp.AllDeleted() {
  204. if handle.FreePage != nil {
  205. handle.FreePage(dpID)
  206. }
  207. } else {
  208. dp.Save()
  209. }
  210. return nil
  211. }
  212. }
  213. }
  214. // 路由失败, 扫描所有叶子
  215. return btree.ScanAndDeleteByPK(rootPID, handle, pkKeyBE)
  216. }
  217. // Update 更新主键索引中 pkID 对应的行。
  218. //
  219. // schema 演进 (AddField) 后, 旧行的 schemaVer 与行长都会变长, "原地覆盖"不再可行,
  220. // 因此采用三级策略, 保证更新要么真正生效, 要么返回明确错误:
  221. //
  222. // 1. 原地更新 — 新行长 <= 旧行长 (最常见, 零成本)
  223. // 2. 页内重建 — 页内腾挪并顺带回收墓碑, 支持行变长而无需换页
  224. // 3. 换页 — 页内确实装不下时, 把行搬到新 DataPage 并改写 B+ 树叶条目
  225. func (h *PrimaryHandler) Update(handle page.HandleDb, entryPage *uint32, oldKey, newKey, rowData []byte, pkID uint64, keyLen int) error {
  226. rootPID := *entryPage
  227. if rootPID == 0 {
  228. return page.ErrNotFound
  229. }
  230. pkKeyBE := make([]byte, 8)
  231. binary.BigEndian.PutUint64(pkKeyBE, pkID)
  232. // 一次分配构造 [pkID(8B BE) + rowData], 取代 pkBuf+append 的二次分配
  233. storeRowData := make([]byte, 8+len(rowData))
  234. copy(storeRowData, pkKeyBE)
  235. copy(storeRowData[8:], rowData)
  236. // 1/2 级: B+ 树路由定位 DataPage
  237. dpID, found := btree.BTreePKSearch(rootPID, pkKeyBE, handle)
  238. if found {
  239. if dp, err := datapage.Load(handle, dpID); err == nil {
  240. if err := dp.UpdateByPK(pkKeyBE, storeRowData); err == nil {
  241. return nil
  242. }
  243. if err := dp.RebuildWith(pkKeyBE, storeRowData); err == nil {
  244. return nil
  245. }
  246. // 页内装不下 → 直接换页, 不再做无意义的全树扫描
  247. return h.relocateRow(handle, entryPage, pkKeyBE, storeRowData, dpID)
  248. }
  249. }
  250. // 路由失败: 回退扫描所有叶子 (内部同样含 1/2 级兜底)
  251. if err := btree.ScanAndUpdate(rootPID, handle, pkKeyBE, storeRowData); err == nil {
  252. return nil
  253. }
  254. // 3 级: 目标行位于"已满且需变长"的页上, 定位后换页
  255. if oldDPID, ok := btree.FindDataPageIDByPK(rootPID, handle, pkKeyBE); ok {
  256. return h.relocateRow(handle, entryPage, pkKeyBE, storeRowData, oldDPID)
  257. }
  258. return page.ErrNotFound
  259. }
  260. // relocateRow 把 pkID 对应的行搬到新的 DataPage 承载。
  261. //
  262. // 步骤 (无事务, 顺序上优先保证"索引始终可达该行"):
  263. // 1. 新建 DataPage 并写入新行 (此时尚未被索引引用)
  264. // 2. 摘除该键的旧叶条目, 再插入指向新页的条目
  265. // 3. 旧页软删该行; 若旧页已无活行则直接回收
  266. func (h *PrimaryHandler) relocateRow(handle page.HandleDb, entryPage *uint32, pkKeyBE, storeRowData []byte, oldDPID uint32) error {
  267. newDP := datapage.New(handle)
  268. newDP.AppendRow(storeRowData)
  269. newDP.Save()
  270. newDPID := newDP.PageID()
  271. // BTreePKInsert 遇到已存在的键会判为重复而不更新指向, 故必须先摘除旧条目
  272. newRoot, _, err := btree.BTreePKDelete(*entryPage, pkKeyBE, handle)
  273. if err != nil {
  274. if handle.FreePage != nil {
  275. handle.FreePage(newDPID)
  276. }
  277. return err
  278. }
  279. newRoot2, _, _, err := btree.BTreePKInsert(newRoot, pkKeyBE, newDPID, handle)
  280. if err != nil {
  281. *entryPage = newRoot
  282. if handle.FreePage != nil {
  283. handle.FreePage(newDPID)
  284. }
  285. return err
  286. }
  287. *entryPage = newRoot2
  288. // 旧页收尾: 只软删这一行, 其它行仍由各自的叶条目引用
  289. if oldDP, lerr := datapage.Load(handle, oldDPID); lerr == nil {
  290. if oldDP.DeleteByPK(pkKeyBE) {
  291. if oldDP.AllDeleted() {
  292. if handle.FreePage != nil {
  293. handle.FreePage(oldDPID)
  294. }
  295. } else {
  296. oldDP.Save()
  297. }
  298. }
  299. }
  300. return nil
  301. }
  302. func (h *PrimaryHandler) GetDataPageIDs(handle page.HandleDb, entryPage uint32) []uint32 {
  303. return btree.GetDataPageIDs(handle, entryPage)
  304. }
  305. func (h *PrimaryHandler) ReadDataPageRows(handle page.HandleDb, dpID uint32) [][]byte {
  306. dp, err := datapage.Load(handle, dpID)
  307. if err != nil {
  308. return nil
  309. }
  310. return dp.GetAllRows()
  311. }
  312. func (h *PrimaryHandler) RangeScan(handle page.HandleDb, entryPage uint32,
  313. startKey, endKey []byte, startInclusive, endInclusive bool, reverse bool,
  314. callback func(row []byte) bool) error {
  315. return btree.BTreePKRangeScan(entryPage, handle, startKey, endKey, startInclusive, endInclusive, reverse, callback)
  316. }