blobwalk.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package main
  2. // ===== Blob 链走查 (v10 Type=9) =====
  3. //
  4. // v10 起 Blob 列行内是 8B BlobRef [TotalLen BE 4][FirstPageID BE 4], 链页由
  5. // PageHdr.NextPage 串联 (DB/core/page/base_page.go BlobIndex)。行是链的唯一主人:
  6. // 引擎删行 / DropTable 都靠行内引用还链 (table_page.go releaseBlobRefsInDataPage)。
  7. //
  8. // 早期 analyzer 的可达根不含 blob 链 —— 任何带 blob 数据的库, 全部活链页都会被
  9. // 报成"既不属于任何表/索引, 也不在空闲链上"的孤儿 (DockerManager 实测 44 页的主因)。
  10. // 这里把行内引用抽出来走链整链认领, 与引擎 DropTable 的回收口径同源。
  11. import (
  12. "encoding/binary"
  13. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  14. )
  15. // blobRefsOfRow 从一行 (不含 pkID 前缀的 rowData) 抽出所有非空 blob 链首页。
  16. // 行布局解析与引擎 rowBlobRefs (DB/core/blob_read.go) 逐条对照:
  17. // 版本不符 / 偏移表不可信时返回 nil (与引擎"跳过该行"同义, 不让坏行毁掉整批)。
  18. func blobRefsOfRow(t *TInfo, rowData []byte) []uint32 {
  19. if len(rowData) < 1 {
  20. return nil
  21. }
  22. var sv *VInfo
  23. for i := range t.Versions {
  24. if t.Versions[i].Ver == rowData[0] {
  25. sv = &t.Versions[i]
  26. break
  27. }
  28. }
  29. if sv == nil || len(sv.Indices) == 0 {
  30. return nil
  31. }
  32. n := len(sv.Indices)
  33. col0Idx := sv.Indices[0]
  34. if col0Idx >= len(t.MasterFields) || t.MasterFields[col0Idx].Sz <= 0 {
  35. return nil
  36. }
  37. col0Width := t.MasterFields[col0Idx].Sz
  38. // v9 行布局: [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列内容...][NULL 位图]
  39. tblBase := 1 + col0Width
  40. header := tblBase + 2*n
  41. if header > len(rowData) {
  42. return nil
  43. }
  44. // 表项 0 必须等于变长内容区起点 (被零填充/冲掉都会在这里暴露)。
  45. if int(binary.BigEndian.Uint16(rowData[tblBase:tblBase+2])) != header {
  46. return nil
  47. }
  48. bitmapLen := (n + 7) / 8
  49. ends := make([]int, n)
  50. prev := header
  51. for i := 0; i < n; i++ {
  52. off := int(binary.BigEndian.Uint16(rowData[tblBase+2*i:tblBase+2*i+2])) & rowVarOffMask
  53. if off < prev || off > len(rowData) {
  54. return nil
  55. }
  56. ends[i] = off
  57. prev = off
  58. }
  59. if prev+bitmapLen > len(rowData) {
  60. return nil
  61. }
  62. bitmap := rowData[prev : prev+bitmapLen]
  63. var out []uint32
  64. for i, idx := range sv.Indices {
  65. if idx >= len(t.MasterFields) {
  66. return nil
  67. }
  68. f := t.MasterFields[idx]
  69. if f.Dropped || f.Type != byte(types.FieldType_Blob) {
  70. continue
  71. }
  72. if bitmap[i>>3]&(1<<uint(i&7)) != 0 {
  73. continue // NULL 列不占内容字节
  74. }
  75. start := 1
  76. if i > 0 {
  77. start = ends[i-1]
  78. }
  79. end := tblBase
  80. if i > 0 {
  81. end = ends[i]
  82. }
  83. if end-start < 8 || start+8 > len(rowData) {
  84. continue
  85. }
  86. if ref := binary.BigEndian.Uint32(rowData[start+4 : start+8]); ref != 0 {
  87. out = append(out, ref)
  88. }
  89. }
  90. return out
  91. }
  92. // hasBlobColumn 该表是否有未删除的 blob 列 (没有就跳过链走查)。
  93. func hasBlobColumn(t *TInfo) bool {
  94. for i := range t.MasterFields {
  95. f := &t.MasterFields[i]
  96. if !f.Dropped && f.Type == byte(types.FieldType_Blob) {
  97. return true
  98. }
  99. }
  100. return false
  101. }
  102. // walkTableBlobChains 扫一张表数据页里每行的 blob 引用, 把链页 (Type=9) 整链认领。
  103. // 返回 (链数, 链页数)。
  104. //
  105. // ⭐ 数据页集合来自【行存储 B+ 树叶条目】的 dpID 引用 —— btree 行存储的数据页
  106. // 由叶条目 (页号+槽位) 引用, FirstData 链是空的 (不再维护); 走 FirstData 链
  107. // 会扫不到任何行, 活链全被误报孤儿。
  108. //
  109. // 数据页迭代复用 eachRow (indexcheck.go): 它自带 PG_DATA 类型校验, 时序表的
  110. // TimeDataPage (布局不同) 会被整体跳过 —— 时序表 blob 列的链页因此仍会被报孤儿,
  111. // 与修复前一致, 不产生新的误报 (时序表是否允许 blob 列, 引擎未禁止, 属于已知缺口)。
  112. func walkTableBlobChains(w *walker, t *TInfo) (chains, pages int) {
  113. if !hasBlobColumn(t) {
  114. return 0, 0
  115. }
  116. // 行存储 = 主键 B+ 树 (时序表的 TimeDataPage 布局不同, 维持已知缺口)。
  117. var rowStore *IInfo
  118. for i := range t.Idxs {
  119. if isRowStore(t.Idxs[i].Type) && isBTreeIdx(t.Idxs[i].Type) {
  120. rowStore = &t.Idxs[i]
  121. break
  122. }
  123. }
  124. if rowStore == nil {
  125. return 0, 0
  126. }
  127. dps := map[uint32]bool{}
  128. pb := newPageBuf()
  129. queue := []uint32{rowStore.EP}
  130. seen := map[uint32]bool{}
  131. for len(queue) > 0 {
  132. pid := queue[0]
  133. queue = queue[1:]
  134. if pid == 0 || seen[pid] {
  135. continue
  136. }
  137. seen[pid] = true
  138. if pid < 2 || int(pid) > w.total {
  139. continue
  140. }
  141. b, err := pb.page(pid)
  142. if err != nil || b[0] != PG_IDATA {
  143. continue
  144. }
  145. cn := int(le32(b[IX_CNT : IX_CNT+4]))
  146. el := int(le16(b[IX_ELEN : IX_ELEN+2]))
  147. if el <= 4 {
  148. el = 12
  149. }
  150. if b[IX_LEAF] == 1 {
  151. refOff := rowStoreRefOff(rowStore.Type, el)
  152. if refOff >= 0 && refOff+4 <= el {
  153. collect := func(nb []byte) {
  154. n := int(le32(nb[IX_CNT : IX_CNT+4]))
  155. for i := 0; i < n; i++ {
  156. o := IDX_HDR + i*el
  157. if o+el > len(nb) {
  158. break
  159. }
  160. dps[be32(nb[o+refOff:o+refOff+4])] = true
  161. }
  162. }
  163. collect(b)
  164. // 叶同层链: 与 indexPages 一致顺链扫完
  165. for nx := le32(b[PH_NEXT : PH_NEXT+4]); nx != 0; {
  166. if seen[nx] || nx < 2 || int(nx) > w.total {
  167. break
  168. }
  169. seen[nx] = true
  170. nb, err := pb.page(nx)
  171. if err != nil || nb[0] != PG_IDATA {
  172. break
  173. }
  174. collect(nb)
  175. nx = le32(nb[PH_NEXT : PH_NEXT+4])
  176. }
  177. }
  178. continue
  179. }
  180. for i := 0; i < cn; i++ {
  181. o := IDX_HDR + i*el
  182. if o+el > len(b) {
  183. break
  184. }
  185. child := be32(b[o+el-4 : o+el])
  186. if child != 0 {
  187. queue = append(queue, child)
  188. }
  189. }
  190. }
  191. for dp := range dps {
  192. if dp < 2 || int(dp) > w.total {
  193. continue
  194. }
  195. b, err := pb.page(dp)
  196. if err != nil || b[0] != PG_DATA {
  197. continue
  198. }
  199. eachRow(b, func(_ uint64, off, ln int) {
  200. for _, first := range blobRefsOfRow(t, b[off:off+ln]) {
  201. chains++
  202. pages += w.chain(first, "blob", t.Name)
  203. }
  204. })
  205. }
  206. return chains, pages
  207. }