TablePage.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. package core
  2. import (
  3. "TsDB2/DB/core/index"
  4. "TsDB2/DB/core/index/btree"
  5. "TsDB2/DB/core/page"
  6. "TsDB2/DB/core/types"
  7. "TsDB2/DB/core/util"
  8. "encoding/binary"
  9. "fmt"
  10. "unsafe"
  11. )
  12. type TablePage struct {
  13. hdr page.PageHdr
  14. tables []*Table
  15. // schemaExt 全库共享的 schema 扩展页分配器 (链头记在表页第 1 页 PageHdr 之后的 4 字节)
  16. schemaExt *SchemaExtAllocator
  17. handle page.HandleDb
  18. hdrSize uint32 // hdr 的大小
  19. tableInfoSize uint32 // 表头大小
  20. lastPageID uint32 // 最后一页
  21. }
  22. // NewTablePage 创建表页管理器
  23. func NewTablePage(handle page.HandleDb) *TablePage {
  24. return &TablePage{
  25. handle: handle,
  26. hdrSize: uint32(unsafe.Sizeof(page.PageHdr{})),
  27. tableInfoSize: uint32(unsafe.Sizeof(TableInfo{})),
  28. }
  29. }
  30. // ensureSchemaExt 惰性创建共享扩展页分配器
  31. func (t *TablePage) ensureSchemaExt() *SchemaExtAllocator {
  32. if t.schemaExt == nil {
  33. t.schemaExt = newSchemaExtAllocator(t.handle, 0,
  34. func() []*Table { return t.tables },
  35. t.saveSchemaExtHead)
  36. }
  37. return t.schemaExt
  38. }
  39. // saveSchemaExtHead 把共享扩展页链头写回表页第 1 页 PageHdr 之后的 4 字节
  40. func (t *TablePage) saveSchemaExtHead(pageID uint32) error {
  41. if t.hdr.PageID == 0 {
  42. return nil
  43. }
  44. buf := make([]byte, PageSize)
  45. if err := t.handle.ReadPage(t.hdr.PageID, buf); err != nil {
  46. return err
  47. }
  48. binary.BigEndian.PutUint32(buf[t.hdrSize:t.hdrSize+schemaExtHeadSize], pageID)
  49. return t.handle.SavePage(t.hdr.PageID, buf)
  50. }
  51. // Tables 获取所有表
  52. func (t *TablePage) Tables() []*Table {
  53. return t.tables
  54. }
  55. // LastPageID 获取最后页ID
  56. func (t *TablePage) LastPageID() uint32 {
  57. return t.lastPageID
  58. }
  59. // Init 初始化表页:从磁盘读取所有表 + 所有字段(支持跨页存储)
  60. func (t *TablePage) Init(pageID uint32) {
  61. t.tables = make([]*Table, 0)
  62. t.hdrSize = uint32(unsafe.Sizeof(page.PageHdr{}))
  63. t.tableInfoSize = uint32(unsafe.Sizeof(TableInfo{}))
  64. t.hdr.PageID = pageID
  65. t.hdr.Type = page.TableIndex
  66. t.hdr.NextPage = 0
  67. t.lastPageID = 0
  68. if pageID == 0 {
  69. return
  70. }
  71. // 表页第 1 页 PageHdr 之后的 4 字节 = 共享 schema 扩展页链头
  72. first := make([]byte, PageSize)
  73. extPID := uint32(0)
  74. if err := t.handle.ReadPage(pageID, first); err == nil {
  75. extPID = binary.BigEndian.Uint32(first[t.hdrSize : t.hdrSize+schemaExtHeadSize])
  76. }
  77. t.ensureSchemaExt()
  78. t.schemaExt.firstPID = extPID
  79. for pageID != 0 {
  80. t.lastPageID = pageID
  81. pageBuf := make([]byte, PageSize)
  82. if err := t.handle.ReadPage(pageID, pageBuf); err != nil {
  83. break
  84. }
  85. pageHdr := page.HdrOf(pageBuf)
  86. // 页类型 / 自述页号校验: 表页链同样可能走到"尚未初始化的新页" (或被回收复用的页),
  87. // 对不上就说明这不是表页, 必须立刻停止解析 —— 否则页里的旧内容会被当成 TableInfo,
  88. // 解析出的垃圾 FirstIndexID/FirstData 会一路传到页回收路径上。
  89. if pageHdr.Type != page.TableIndex || pageHdr.PageID != pageID {
  90. break
  91. }
  92. offset := t.hdrSize + schemaExtHeadSize
  93. for offset+t.tableInfoSize <= pageHdr.UseSize {
  94. info := (*TableInfo)(unsafe.Pointer(&pageBuf[offset]))
  95. if util.IsHighBitSet(info.Flag) {
  96. offset += t.tableInfoSize + uint32(info.FieldAllocateSize)
  97. continue
  98. }
  99. activeVer, masterFields, versions := readSchema(
  100. t.handle, pageID, uint16(offset+t.tableInfoSize), info.FieldAllocateSize,
  101. info.ExtPage, info.ExtOff)
  102. indexMate := &IndexMetaPage{
  103. handle: t.handle,
  104. }
  105. indexMate.Init(info.FirstIndexID)
  106. t.tables = append(t.tables, &Table{
  107. info: info,
  108. Field: masterFields,
  109. handle: t.handle,
  110. indexMeta: indexMate,
  111. tablePageID: pageID,
  112. tableStartOffset: offset,
  113. schemaExt: t.schemaExt,
  114. activeVersion: activeVer,
  115. versions: versions,
  116. })
  117. offset += t.tableInfoSize + uint32(info.FieldAllocateSize)
  118. }
  119. pageID = pageHdr.NextPage
  120. }
  121. }
  122. // ===========================================================================
  123. // 表页槽位管理
  124. // ===========================================================================
  125. //
  126. // 表页链上的记录布局 (所有页统一):
  127. //
  128. // [PageHdr 20B][SchemaExtPageID 4B(仅第 1 页用)][TableInfo 104B + 内联 schema 块]...
  129. //
  130. // 每条记录占 tableInfoSize + FieldAllocateSize 字节, 长度由记录自身描述。于是
  131. // Drop 只要把 Flag 置高位, 这条记录就变成"可复用空洞", 遍历方按长度自然跳过它。
  132. //
  133. // Create 的落点优先级:
  134. // 1. 尾页尾部追加 最省事, 也不打乱页内记录顺序
  135. // 2. 复用某个空洞 (first-fit) 空洞够大时按需求切分, 余量补一条墓碑记录
  136. // 3. 压缩整条表页链后重试 碎片太多时把空洞合并回尾部, 顺带回收空页
  137. // 4. 追加新表页 真放不下才让链变长
  138. //
  139. // 有了这一层, "批量建表 → 删表 → 再建表" 的循环不会让表页链无限膨胀。
  140. // tableSlot 一段可写入表记录的槽位
  141. type tableSlot struct {
  142. pageID uint32 // 所在页
  143. offset uint32 // TableInfo 起始偏移
  144. capacity uint32 // 槽位容量 (>= 需求); 复用空洞时可能大于需求
  145. bump bool // 位于尾页尾部 → 需要推进 UseSize
  146. }
  147. // allocSlot 为 required 字节的表记录挑选落点
  148. func (t *TablePage) allocSlot(required uint32) (tableSlot, error) {
  149. // 全库第一张表: 先把链头页建出来
  150. if t.hdr.PageID == 0 {
  151. pid := t.handle.NewPage()
  152. t.InitHeader(pid)
  153. t.hdr.PageID = pid
  154. t.lastPageID = pid
  155. }
  156. // 1) 尾页尾部直接追加
  157. if s, ok, err := t.tailSlot(required); err != nil {
  158. return tableSlot{}, err
  159. } else if ok {
  160. return s, nil
  161. }
  162. // 2) 复用 Drop 留下的空洞
  163. if s, ok, err := t.findFreeSlot(required); err != nil {
  164. return tableSlot{}, err
  165. } else if ok {
  166. return s, nil
  167. }
  168. // 3) 单块空洞都装不下 → 压缩整条链, 把碎片合并回尾部再试
  169. if t.hasHoles() {
  170. if err := t.Compact(); err != nil {
  171. return tableSlot{}, err
  172. }
  173. if s, ok, err := t.tailSlot(required); err != nil {
  174. return tableSlot{}, err
  175. } else if ok {
  176. return s, nil
  177. }
  178. }
  179. // 4) 真放不下: 追加新表页
  180. newPID, err := t.appendPage()
  181. if err != nil {
  182. return tableSlot{}, err
  183. }
  184. return tableSlot{
  185. pageID: newPID,
  186. offset: t.hdrSize + schemaExtHeadSize,
  187. capacity: required,
  188. bump: true,
  189. }, nil
  190. }
  191. // tailSlot 尾页尾部是否还剩 required 字节
  192. func (t *TablePage) tailSlot(required uint32) (tableSlot, bool, error) {
  193. if t.lastPageID == 0 {
  194. return tableSlot{}, false, nil
  195. }
  196. buf := make([]byte, PageSize)
  197. if err := t.handle.ReadPage(t.lastPageID, buf); err != nil {
  198. return tableSlot{}, false, err
  199. }
  200. off := page.HdrOf(buf).UseSize
  201. if off < t.hdrSize+schemaExtHeadSize {
  202. off = t.hdrSize + schemaExtHeadSize
  203. }
  204. if off+required > PageSize {
  205. return tableSlot{}, false, nil
  206. }
  207. return tableSlot{pageID: t.lastPageID, offset: off, capacity: required, bump: true}, true, nil
  208. }
  209. // findFreeSlot 在整条表页链上找第一个容量足够的空洞 (first-fit)
  210. func (t *TablePage) findFreeSlot(required uint32) (tableSlot, bool, error) {
  211. for pid := t.hdr.PageID; pid != 0; {
  212. buf := make([]byte, PageSize)
  213. if err := t.handle.ReadPage(pid, buf); err != nil {
  214. return tableSlot{}, false, err
  215. }
  216. hdr := page.HdrOf(buf)
  217. for off := t.hdrSize + schemaExtHeadSize; off+t.tableInfoSize <= hdr.UseSize; {
  218. info := (*TableInfo)(unsafe.Pointer(&buf[off]))
  219. size := t.tableInfoSize + uint32(info.FieldAllocateSize)
  220. if off+size > hdr.UseSize {
  221. break
  222. }
  223. if util.IsHighBitSet(info.Flag) && size >= required {
  224. return tableSlot{pageID: pid, offset: off, capacity: size}, true, nil
  225. }
  226. off += size
  227. }
  228. pid = hdr.NextPage
  229. }
  230. return tableSlot{}, false, nil
  231. }
  232. // hasHoles 表页链上是否还存在 Drop 留下的空洞
  233. func (t *TablePage) hasHoles() bool {
  234. for pid := t.hdr.PageID; pid != 0; {
  235. buf := make([]byte, PageSize)
  236. if err := t.handle.ReadPage(pid, buf); err != nil {
  237. return false
  238. }
  239. hdr := page.HdrOf(buf)
  240. for off := t.hdrSize + schemaExtHeadSize; off+t.tableInfoSize <= hdr.UseSize; {
  241. info := (*TableInfo)(unsafe.Pointer(&buf[off]))
  242. if util.IsHighBitSet(info.Flag) {
  243. return true
  244. }
  245. off += t.tableInfoSize + uint32(info.FieldAllocateSize)
  246. }
  247. pid = hdr.NextPage
  248. }
  249. return false
  250. }
  251. // appendPage 在链尾追加一张空表页, 并挂到前驱的 NextPage 上
  252. func (t *TablePage) appendPage() (uint32, error) {
  253. pid := t.handle.NewPage()
  254. t.InitHeader(pid)
  255. if t.lastPageID != 0 {
  256. buf := make([]byte, PageSize)
  257. if err := t.handle.ReadPage(t.lastPageID, buf); err != nil {
  258. return 0, err
  259. }
  260. page.HdrOf(buf).NextPage = pid
  261. if err := t.handle.SavePage(t.lastPageID, buf); err != nil {
  262. return 0, err
  263. }
  264. }
  265. t.lastPageID = pid
  266. return pid, nil
  267. }
  268. // checkFieldCount 校验列数能否落盘 (建表与加列共用同一条闸门)。
  269. //
  270. // 列数上限见 MaxFieldsPerTable: FieldCount 与字段索引都是 1 字节, 超了会回绕 / 截断。
  271. // 错误里带上"本次多少列、上限多少列", 只说"字段数量非法"用户不知道要砍到几列。
  272. func checkFieldCount(n int) error {
  273. if n <= 0 {
  274. return fmt.Errorf("%w: 一张表至少要有 1 列", ErrInvalidFieldCount)
  275. }
  276. if n > MaxFieldsPerTable {
  277. return fmt.Errorf("%w: 一张表最多 %d 列, 本次 %d 列 "+
  278. "(列数与字段索引都是 1 字节, 超过会截断成错位 schema)",
  279. ErrInvalidFieldCount, MaxFieldsPerTable, n)
  280. }
  281. return nil
  282. }
  283. // ===========================================================================
  284. // ✅ Create 完整正确(字段数量全是 byte,255上限)
  285. // ===========================================================================
  286. func (t *TablePage) Create(name string, fields []types.FieldInfo) (*Table, error) {
  287. if len(name) > TableNameSize {
  288. return nil, fmt.Errorf("%w: 表名 %q 有 %d 字节, 上限 %d",
  289. ErrTableNameTooLong, name, len(name), TableNameSize)
  290. }
  291. for _, tb := range t.tables {
  292. if util.BytesToString(tb.info.Name[:]) == name && !util.IsHighBitSet(tb.info.Flag) {
  293. return nil, ErrTableExists
  294. }
  295. }
  296. if err := checkFieldCount(len(fields)); err != nil {
  297. return nil, err
  298. }
  299. // 建表即校验列级参数与行宽。
  300. //
  301. // 行宽是 schema 属性而不是行数据属性: 一张建出来就超上限的表, 之后每一行都会写入
  302. // 失败, 而且是在插数据时才暴露。必须在建表这一步拒绝, 并把"哪几列占了多少"报出来。
  303. // 校验放在 core 而不是只留在 ORM/migrate: schema 也能从 SQL 或低层 API 直接建。
  304. if err := validateFieldsForSchema(fields); err != nil {
  305. return nil, err
  306. }
  307. // 过完上面两道闸门后 len(fields) ≤ MaxFieldsPerTable(254), 转 byte 不会回绕。
  308. fieldCount := byte(len(fields))
  309. // 内联块按 schema 实际序列化长度精确分配 (不再 ×2 预分配, 也不加 256B 下限)。
  310. // 超出 MaxInlineSchemaSize 的部分截断, 由 writeSchema 写进共享扩展页。
  311. masterIndices := make([]byte, fieldCount)
  312. for i := range masterIndices {
  313. masterIndices[i] = byte(i)
  314. }
  315. initialVersions := []SchemaVersion{{Version: 1, FieldIndices: masterIndices}}
  316. probe := &Table{Field: fields, activeVersion: 1, versions: initialVersions}
  317. allocateSize := len(probe.serializeSchema())
  318. if allocateSize > MaxInlineSchemaSize {
  319. allocateSize = MaxInlineSchemaSize
  320. }
  321. if allocateSize <= 0 || uint32(allocateSize) > PageSize {
  322. return nil, ErrFieldAllocateTooLarge
  323. }
  324. // 这个表总共需要多少空间
  325. required := t.tableInfoSize + uint32(allocateSize)
  326. // 挑选槽位: 尾页尾部 → 复用 Drop 空洞 → 压缩后重试 → 新页
  327. slot, err := t.allocSlot(required)
  328. if err != nil {
  329. return nil, err
  330. }
  331. pageBuf := make([]byte, PageSize)
  332. if err := t.handle.ReadPage(slot.pageID, pageBuf); err != nil {
  333. return nil, err
  334. }
  335. pageHdr := page.HdrOf(pageBuf)
  336. offset := slot.offset
  337. info := (*TableInfo)(unsafe.Pointer(&pageBuf[offset]))
  338. *info = TableInfo{} // 复用空洞时必须整体清零, 否则会残留被删表的字段
  339. copy(info.Name[:], name)
  340. info.Flag = 0
  341. info.FieldCount = fieldCount
  342. info.ActiveFieldCount = fieldCount // 初始时实际字段数等于总字段数
  343. info.RowSize = types.CalcRowSize(fields)
  344. info.FieldAllocateSize = uint16(allocateSize)
  345. info.TotalRows = 0
  346. info.ExtPage = 0
  347. info.ExtOff = 0
  348. // 创建数据页
  349. dataPID := t.handle.NewPage()
  350. info.FirstData = dataPID
  351. info.LastData = dataPID
  352. dataBuf := make([]byte, PageSize)
  353. dHdr := page.HdrOf(dataBuf)
  354. dHdr.Type = page.DataIndex
  355. dHdr.PageID = dataPID
  356. dHdr.UseSize = t.hdrSize
  357. t.handle.SavePage(dataPID, dataBuf)
  358. // 创建堆页
  359. heapPID := t.handle.NewPage()
  360. info.FirstHeap = heapPID
  361. info.LastHeap = heapPID
  362. info.HeapPageCount = 1 // 建表即开一张堆页, 堆链长度从 1 起算
  363. heapBuf := make([]byte, PageSize)
  364. hHdr := page.HdrOf(heapBuf)
  365. hHdr.Type = page.HeapIndex
  366. hHdr.PageID = heapPID
  367. hHdr.UseSize = t.hdrSize
  368. t.handle.SavePage(heapPID, heapBuf)
  369. indexMate := &IndexMetaPage{
  370. handle: t.handle,
  371. }
  372. // 创建索引元数据。
  373. // 注意这里是 InitEmpty 而不是 Init: indexPID 刚从空闲链摘出来, 盘上还留着它上一世
  374. // 的内容 (常见是 WAL 页残留)。Init 会从盘上回读本页内容来重建 metas, 于是把整页旧
  375. // 内容当成 IndexMeta 数组解析出几十上百条垃圾元数据 (实测 91 条, EntryPage=16777216);
  376. // 之后即便再用 InitHeader 把页写干净, 也清不掉内存里那批垃圾 meta —— 最终由
  377. // DropTable 拿着垃圾 EntryPage 去枚举页, FreePage 落到从未分配过的页号上,
  378. // 写到几十 GB 偏移 (DROP 假死 52s、稀疏文件暴涨到 40GB)。
  379. indexPID := t.handle.NewPage()
  380. indexMate.InitEmpty(indexPID)
  381. info.FirstIndexID = indexPID
  382. info.LastIndexID = indexPID
  383. // 槽位大于需求(复用空洞): 余量按 TableInfo 拆成墓碑记录留给下次建表;
  384. // 连一个 TableInfo 都放不下时并入本表内联块 —— 由 FieldAllocateSize 描述的容量
  385. // 把遍历步进撑到槽位末尾 (writeSchema 会清零整块, 不会留下脏字节)。
  386. if rem := slot.capacity - required; rem >= t.tableInfoSize {
  387. free := (*TableInfo)(unsafe.Pointer(&pageBuf[offset+required]))
  388. *free = TableInfo{}
  389. free.Flag = 0x80
  390. free.FieldAllocateSize = uint16(rem - t.tableInfoSize)
  391. } else if rem > 0 {
  392. info.FieldAllocateSize = uint16(slot.capacity - t.tableInfoSize)
  393. }
  394. if slot.bump {
  395. pageHdr.UseSize = offset + slot.capacity
  396. }
  397. if err := t.handle.SavePage(slot.pageID, pageBuf); err != nil {
  398. return nil, err
  399. }
  400. // schema 定义统一由 writeSchema 落盘 (格式: [activeVer][masterCount][master fields][verCount][ver defs...])
  401. // 初始 schema 一般能完整放进内联块; 超过 MaxInlineSchemaSize 时余下部分自动进共享扩展页
  402. newTable := Table{
  403. info: info,
  404. Field: fields,
  405. handle: t.handle,
  406. indexMeta: indexMate,
  407. tablePageID: slot.pageID,
  408. tableStartOffset: offset,
  409. schemaExt: t.ensureSchemaExt(),
  410. activeVersion: 1,
  411. versions: initialVersions,
  412. }
  413. if err := newTable.writeSchema(); err != nil {
  414. return nil, err
  415. }
  416. t.tables = append(t.tables, &newTable)
  417. return &newTable, nil
  418. }
  419. // ===========================================================================
  420. // ✅ Compact / Stats —— 表页链压缩与布局统计
  421. // ===========================================================================
  422. // Compact 压缩整条表页链: 丢弃 Drop 留下的空洞, 把活表记录按原顺序紧排到链首的
  423. // 若干页, 多余页回收。
  424. //
  425. // 安全性: 记录以"自描述长度"顺序排列, 页内偏移不对外暴露 —— 外部一律通过
  426. // Table{tablePageID, tableStartOffset} 定位, 所以搬移记录后只要同步更新内存里的
  427. // Table 引用即可, 不会破坏任何索引或 schema。
  428. func (t *TablePage) Compact() error {
  429. if t.hdr.PageID == 0 {
  430. return nil
  431. }
  432. // 1. 顺序收集链上所有页 ID 与所有活记录的原始字节
  433. var pageIDs []uint32
  434. var live [][]byte
  435. for pid := t.hdr.PageID; pid != 0; {
  436. buf := make([]byte, PageSize)
  437. if err := t.handle.ReadPage(pid, buf); err != nil {
  438. return err
  439. }
  440. hdr := page.HdrOf(buf)
  441. pageIDs = append(pageIDs, pid)
  442. for off := t.hdrSize + schemaExtHeadSize; off+t.tableInfoSize <= hdr.UseSize; {
  443. info := (*TableInfo)(unsafe.Pointer(&buf[off]))
  444. size := t.tableInfoSize + uint32(info.FieldAllocateSize)
  445. if off+size > hdr.UseSize {
  446. break
  447. }
  448. if !util.IsHighBitSet(info.Flag) {
  449. data := make([]byte, size)
  450. copy(data, buf[off:off+size])
  451. live = append(live, data)
  452. }
  453. off += size
  454. }
  455. pid = hdr.NextPage
  456. }
  457. if len(pageIDs) == 0 {
  458. return nil
  459. }
  460. // 2. 紧排。活记录总字节 <= 原占用, 所以一定放得进现有的页数
  461. first := t.hdrSize + schemaExtHeadSize
  462. next := 0
  463. keep := 0
  464. for pageIdx := 0; pageIdx < len(pageIDs); pageIdx++ {
  465. if pageIdx > 0 && next >= len(live) {
  466. break // 后面的页已经没有活记录, 直接回收
  467. }
  468. pid := pageIDs[pageIdx]
  469. buf := make([]byte, PageSize)
  470. if err := t.handle.ReadPage(pid, buf); err != nil {
  471. return err
  472. }
  473. hdr := page.HdrOf(buf)
  474. // 只清记录区: [hdrSize, hdrSize+schemaExtHeadSize) 保留共享扩展页链头
  475. for i := first; i < PageSize; i++ {
  476. buf[i] = 0
  477. }
  478. off := first
  479. for next < len(live) && off+uint32(len(live[next])) <= PageSize {
  480. copy(buf[off:], live[next])
  481. off += uint32(len(live[next]))
  482. next++
  483. }
  484. hdr.UseSize = off
  485. // 3. 同步内存 Table 的落点 (活表名唯一)
  486. for o := first; o+t.tableInfoSize <= hdr.UseSize; {
  487. info := (*TableInfo)(unsafe.Pointer(&buf[o]))
  488. if tb := t.findTableByName(util.BytesToString(info.Name[:])); tb != nil {
  489. tb.info = info
  490. tb.tablePageID = pid
  491. tb.tableStartOffset = o
  492. }
  493. o += t.tableInfoSize + uint32(info.FieldAllocateSize)
  494. }
  495. if err := t.handle.SavePage(pid, buf); err != nil {
  496. return err
  497. }
  498. keep++
  499. }
  500. // 4. 重连链尾, 回收多余页
  501. for i := 0; i < keep; i++ {
  502. buf := make([]byte, PageSize)
  503. if err := t.handle.ReadPage(pageIDs[i], buf); err != nil {
  504. return err
  505. }
  506. nx := uint32(0)
  507. if i+1 < keep {
  508. nx = pageIDs[i+1]
  509. }
  510. page.HdrOf(buf).NextPage = nx
  511. if err := t.handle.SavePage(pageIDs[i], buf); err != nil {
  512. return err
  513. }
  514. }
  515. t.lastPageID = pageIDs[keep-1]
  516. if t.handle.FreePage != nil {
  517. for _, pid := range pageIDs[keep:] {
  518. t.handle.FreePage(pid)
  519. }
  520. }
  521. return nil
  522. }
  523. // Stats 表页链布局统计: 页数 / 活表数 / 空洞数 / 可回收字节数 (诊断与测试用)
  524. func (t *TablePage) Stats() (pages, live, dead int, reclaimable uint32) {
  525. for pid := t.hdr.PageID; pid != 0; {
  526. buf := make([]byte, PageSize)
  527. if err := t.handle.ReadPage(pid, buf); err != nil {
  528. break
  529. }
  530. hdr := page.HdrOf(buf)
  531. pages++
  532. for off := t.hdrSize + schemaExtHeadSize; off+t.tableInfoSize <= hdr.UseSize; {
  533. info := (*TableInfo)(unsafe.Pointer(&buf[off]))
  534. size := t.tableInfoSize + uint32(info.FieldAllocateSize)
  535. if util.IsHighBitSet(info.Flag) {
  536. dead++
  537. reclaimable += size
  538. } else {
  539. live++
  540. }
  541. off += size
  542. }
  543. pid = hdr.NextPage
  544. }
  545. return
  546. }
  547. // findTableByName 按表名找内存中的 Table (活表名唯一)
  548. func (t *TablePage) findTableByName(name string) *Table {
  549. for _, tb := range t.tables {
  550. if tb != nil && util.BytesToString(tb.info.Name[:]) == name {
  551. return tb
  552. }
  553. }
  554. return nil
  555. }
  556. // ===========================================================================
  557. // ✅ Drop 删除表并回收所有页
  558. // ===========================================================================
  559. func (t *TablePage) Drop(name string) error {
  560. pageID := t.hdr.PageID
  561. for pageID != 0 {
  562. buf := make([]byte, PageSize)
  563. if err := t.handle.ReadPage(pageID, buf); err != nil {
  564. return err
  565. }
  566. hdr := page.HdrOf(buf)
  567. offset := t.hdrSize + schemaExtHeadSize
  568. for offset+t.tableInfoSize <= hdr.UseSize {
  569. info := (*TableInfo)(unsafe.Pointer(&buf[offset]))
  570. if util.BytesToString(info.Name[:]) == name && !util.IsHighBitSet(info.Flag) {
  571. // 找到该表在内存中的 Table 对象,获取其索引元数据
  572. var tableEntry *Table
  573. for _, tb := range t.tables {
  574. if util.BytesToString(tb.info.Name[:]) == name && !util.IsHighBitSet(tb.info.Flag) {
  575. tableEntry = tb
  576. break
  577. }
  578. }
  579. // 回收该表所有页 (用该表自己的 indexMeta.metas,不遍历共享的 IndexMeta 页链)
  580. t.recycleTablePages(info, tableEntry)
  581. // 软删除标记
  582. info.Flag = util.SetHighBit(info.Flag)
  583. t.handle.SavePage(pageID, buf)
  584. for i := range t.tables {
  585. if util.BytesToString(t.tables[i].info.Name[:]) == name {
  586. t.tables = append(t.tables[:i], t.tables[i+1:]...)
  587. break
  588. }
  589. }
  590. return nil
  591. }
  592. offset += t.tableInfoSize + uint32(info.FieldAllocateSize)
  593. }
  594. pageID = hdr.NextPage
  595. }
  596. return ErrTableNotFound
  597. }
  598. // recycleTablePages 回收表的所有页 (B+树页、DataPage页、Heap页)
  599. // 注意: IndexMeta 页是所有表共享的,不能释放!
  600. func (t *TablePage) recycleTablePages(info *TableInfo, tableEntry *Table) {
  601. if t.handle.FreePage == nil {
  602. return
  603. }
  604. var pagesToFree []uint32
  605. // 1. 用该表自己的 indexMeta.metas 收集索引页和 DataPage
  606. // 不遍历共享的 IndexMeta 页链,避免误释放其他表的索引页
  607. //
  608. // 必须按页布局分流, 与 Deleter.releaseOldTree 同一套约定:
  609. // B+ 树节点结构统一, 按内部条目下降即可枚举全部节点页; 哈希 / 复合 / 时序
  610. // 的页根本不是 B+ 节点 (元数据页+目录页+桶页链, 或扁平条目链), 拿 CollectAllPageIDs
  611. // 去遍历不会报错, 只会「少收」—— 实测哈希索引 20 页里只回收了元数据页 1 页,
  612. // 目录页与 18 个桶页全部泄漏; 复合索引 3 页只回收 1 页。文件只涨不缩。
  613. if tableEntry != nil && tableEntry.indexMeta != nil {
  614. for i := range tableEntry.indexMeta.metas {
  615. m := &tableEntry.indexMeta.metas[i]
  616. if m.Flag&0x80 != 0 || m.EntryPage == 0 {
  617. continue
  618. }
  619. if isBTreePageIndex(m) {
  620. // 收集 B+ 树所有节点页 (内部节点+叶子节点)
  621. btPages := btree.CollectAllPageIDs(t.handle, m.EntryPage)
  622. pagesToFree = append(pagesToFree, btPages...)
  623. // 只有承载行数据的索引 (主键/时序) 叶子节点存 DataPage ID
  624. // 二级索引叶子存的是 PKID(8B),不是 DataPage ID!
  625. if indexStoresRows(m) {
  626. dpIDs := btree.GetDataPageIDs(t.handle, m.EntryPage)
  627. pagesToFree = append(pagesToFree, dpIDs...)
  628. }
  629. continue
  630. }
  631. h, ok := index.Compose.GetHandler(m.IndexType)
  632. if !ok {
  633. continue
  634. }
  635. // 优先整链回收: 哈希索引的桶页 next 早已统一到 PageHdr.NextPage, 且每条
  636. // 桶链的 (head, tail, count) 就持久化在目录槽里 —— 每条链一次 FreeChain,
  637. // O(1) 拼进空闲链, 完全不必读链内任何一页。
  638. if cc, ok := h.(index.ChainCollector); ok && t.handle.FreeChain != nil {
  639. for _, ch := range cc.CollectChains(t.handle, m.EntryPage) {
  640. if ch.Head == 0 || ch.Count == 0 {
  641. continue
  642. }
  643. if ch.Head == ch.Tail {
  644. t.handle.FreePage(ch.Head) // 单页链 (例如元数据页)
  645. continue
  646. }
  647. _ = t.handle.FreeChain(ch.Head, ch.Tail, ch.Count)
  648. }
  649. continue
  650. }
  651. if pc, ok := h.(index.PageCollector); ok {
  652. pagesToFree = append(pagesToFree, pc.CollectPageIDs(t.handle, m.EntryPage)...)
  653. continue
  654. }
  655. // 兜底: 该类型没有页枚举能力时, 至少把数据页收干净 ——
  656. // 数据页是行真正的落点, 漏掉它等于漏掉整张表的数据。
  657. if indexStoresRows(m) {
  658. pagesToFree = append(pagesToFree, h.GetDataPageIDs(t.handle, m.EntryPage)...)
  659. }
  660. }
  661. }
  662. // 1.5) 表自身的数据页链与索引元数据页链。
  663. // CreateTable 每建一张表都会固定分配三类页: Data(FirstData/LastData)、
  664. // Heap(FirstHeap/LastHeap)、IndexMeta(FirstIndexID/LastIndexID)。
  665. // 上面只收了"索引自己的树页/桶页", 这里还必须把 Data 链与 IndexMeta 链整条收掉,
  666. // 否则 DROP 之后这两条链的页既不被任何表引用、也不在空闲链上 —— 成为永久孤儿页
  667. // (analyzer 报的 Data / IdxMeta 孤儿页正是它们)。
  668. //
  669. // IndexMeta 链是「本表独有」的: 每张表的 TableInfo 各自持有 FirstIndexID..LastIndexID,
  670. // 建表时各自 NewPage, 并不共享。旧注释"IndexMeta 页是全库共享的不能释放"不成立。
  671. // 链内 next 用 PageHdr.NextPage (与 loadMetas 遍历一致), 逐页收集后统一去重释放。
  672. for _, head := range []uint32{info.FirstData, info.FirstIndexID} {
  673. // 成环保护: 链一旦自环 (某页被复用后又把 next 指回自己), 这个循环永远转不完,
  674. // DROP TABLE 会直接假死。宁可少收几页, 也不能挂在一条坏链上。
  675. walked := make(map[uint32]bool)
  676. for pid := head; pid != 0; {
  677. if walked[pid] {
  678. break
  679. }
  680. walked[pid] = true
  681. buf := make([]byte, PageSize)
  682. if err := t.handle.ReadPage(pid, buf); err != nil {
  683. break
  684. }
  685. next := page.HdrOf(buf).NextPage
  686. pagesToFree = append(pagesToFree, pid)
  687. pid = next
  688. }
  689. }
  690. // 2. 堆链 (溢出页): 首尾已知 (FirstHeap/LastHeap), 链内 next 与空闲链同用
  691. // PageHdr.NextPage —— 整条一次性拼进空闲链, 不遍历、不逐页写, O(1)。
  692. //
  693. // 为什么以前必须逐页走: 那时的还链入口只有 FreePage(单页), 所以要先把
  694. // 每个页号读出来。现在 FreeChain 只要 (链首, 链尾, 链长): 前两个 TableInfo
  695. // 里就有, 链长由 heapAllocBlock/heapDropPage 一直维护在 HeapPageCount。
  696. //
  697. // 退化: 缺 FreeChain 能力 (老 demo 手搓的 HandleDb) 或链长未知时, 退回逐页遍历。
  698. heapSpliced := false
  699. if info.FirstHeap != 0 && info.LastHeap != 0 && info.HeapPageCount > 0 && t.handle.FreeChain != nil {
  700. if err := t.handle.FreeChain(info.FirstHeap, info.LastHeap, info.HeapPageCount); err == nil {
  701. heapSpliced = true
  702. }
  703. }
  704. if !heapSpliced {
  705. walked := make(map[uint32]bool)
  706. heapID := info.FirstHeap
  707. for heapID != 0 {
  708. // 同 1.5 的成环保护: 坏链不能把 DROP 拖成死循环。
  709. if walked[heapID] {
  710. break
  711. }
  712. walked[heapID] = true
  713. buf := make([]byte, PageSize)
  714. if err := t.handle.ReadPage(heapID, buf); err != nil {
  715. break
  716. }
  717. hdr := page.HdrOf(buf)
  718. nextHeap := hdr.NextPage
  719. pagesToFree = append(pagesToFree, heapID)
  720. heapID = nextHeap
  721. }
  722. }
  723. // 3. schema 续块不再单独回收: 它位于全库共享的扩展页里, 表被删除后
  724. // 那一段自然成为垃圾, 由 SchemaExtAllocator 在满页压缩重排时统一回收。
  725. // 4. 统一释放所有页。去重并跳过 0: 多个枚举器拼在一起后, 同一个页 ID 出现两次
  726. // 会让空闲链形成自环 (NewPage 之后再也走不出去), 而 0 是保留页号, 直接归还
  727. // 会把空闲链头指向非法页。
  728. seen := make(map[uint32]bool, len(pagesToFree))
  729. for _, pid := range pagesToFree {
  730. if pid == 0 || seen[pid] {
  731. continue
  732. }
  733. // 合法性兜底 (最后一道闸): 页枚举 (CollectAllPageIDs / GetDataPageIDs / 链遍历)
  734. // 只按偏移解析页头, 不看页类型。meta.EntryPage 一旦指向被复用过的页, 行数据会被
  735. // 当成 B+ 条目解析, 截出「从未分配过」的垃圾页号 (实测 2463488)。
  736. // 这种页号绝不能还链: 空闲链头被指向文件里不存在的位置, 下一次 NewPage 拿到它,
  737. // 所有写入都跑到几十 GB 偏移处 (DROP TABLE 因此假死 52s、稀疏文件涨到 40GB)。
  738. // 上游已在 btLoadNode / GetDataPageIDs 里按页类型校验, 这里只处理漏网之鱼。
  739. if t.handle.ValidPage != nil && !t.handle.ValidPage(pid) {
  740. continue
  741. }
  742. seen[pid] = true
  743. t.handle.FreePage(pid)
  744. }
  745. if heapSpliced {
  746. // 堆链已整条交还空闲链, 表记录里的堆指针一并清空, 别留下悬垂引用。
  747. // info 指向 Drop 的 buf 内部, 随后的 SavePage 会把这三项一起落盘。
  748. info.FirstHeap = 0
  749. info.LastHeap = 0
  750. info.HeapPageCount = 0
  751. }
  752. // Data 链 / IndexMeta 链已回收, 同样清空表记录里的指针, 避免留下指向空闲页的悬垂引用。
  753. // (表随后被标记删除, 这四项只影响这条墓碑记录的持久化内容。)
  754. info.FirstData = 0
  755. info.LastData = 0
  756. info.FirstIndexID = 0
  757. info.LastIndexID = 0
  758. }
  759. func (t *TablePage) Find(name string) (*Table, bool) {
  760. for i := range t.tables {
  761. tb := t.tables[i]
  762. if util.BytesToString(tb.info.Name[:]) == name && !util.IsHighBitSet(tb.info.Flag) {
  763. return tb, true
  764. }
  765. }
  766. return nil, false
  767. }
  768. // ReloadInfos 从磁盘重新读回各表的 TableInfo, 原地覆盖 table.info 指向的内存结构。
  769. //
  770. // 供事务 Rollback 使用。事务回滚只是把被改的页从 BufferPool 里丢掉, 而 Table.info 是
  771. // 独立于页缓存的内存副本(不随缓存回收而消失), 不重载的话 TotalRows / FirstData 等字段
  772. // 仍停留在事务中的值 —— 例如回滚后 TotalRows 偏大, 下一次 InsertRow 会按错误的行号继续,
  773. // 并把错误值随 saveTableInfo 写回页里, 造成真正的数据不一致。
  774. //
  775. // only 为 nil 表示全部重载; 否则只重载 tablePageID 落在其中的表。
  776. // 原地赋值(而非替换指针), 保证外部持有的 *Table / *TableInfo 引用继续有效。
  777. func (t *TablePage) ReloadInfos(only map[uint32]struct{}) {
  778. for _, tb := range t.tables {
  779. if tb == nil || tb.info == nil {
  780. continue
  781. }
  782. if only != nil {
  783. if _, ok := only[tb.tablePageID]; !ok {
  784. continue
  785. }
  786. }
  787. buf := make([]byte, PageSize)
  788. if err := t.handle.ReadPage(tb.tablePageID, buf); err != nil {
  789. continue
  790. }
  791. end := tb.tableStartOffset + t.tableInfoSize
  792. if end > uint32(len(buf)) {
  793. continue
  794. }
  795. disk := (*TableInfo)(unsafe.Pointer(&buf[tb.tableStartOffset]))
  796. *tb.info = *disk
  797. // 索引元数据 (IndexMetaPage.metas) 同样是独立于页缓存的内存副本, 必须一起重载:
  798. // 否则 EntryPage 仍指向事务中新建的 B+ 树根 (或被删空时写成的 0),
  799. // 而该根页已随缓存丢弃 → 回滚后 SearchByIndex / 范围扫描全部走空。
  800. if tb.indexMeta != nil {
  801. tb.indexMeta.Reload(tb.info.FirstIndexID)
  802. }
  803. }
  804. }
  805. // InitHeader 初始化头部信息
  806. // 注意: UseSize 额外让出 schemaExtHeadSize 字节 —— 所有表页都在 PageHdr 之后统一预留
  807. // 4 字节(仅第 1 页用来存共享扩展页链头), 这样表偏移遍历逻辑无需区分第几页。
  808. func (t *TablePage) InitHeader(pageID uint32) ([]byte, *page.PageHdr) {
  809. newBuf := make([]byte, PageSize)
  810. newHdr := page.HdrOf(newBuf)
  811. newHdr.Type = page.TableIndex
  812. newHdr.PageID = pageID
  813. newHdr.UseSize = uint32(unsafe.Sizeof(*newHdr)) + schemaExtHeadSize
  814. t.handle.SavePage(pageID, newBuf)
  815. return newBuf, newHdr
  816. }