index_meta_page.go 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201
  1. package core
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "slices"
  7. "unsafe"
  8. "git.ycl.pub/yl32940/TsDB2/DB/core/index"
  9. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  10. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  11. "git.ycl.pub/yl32940/TsDB2/DB/core/util"
  12. )
  13. // 🔥 最终对接版:只存索引定义 + 指向你的 IndexPage
  14. type IndexMeta struct {
  15. IndexName [IndexNameSize]byte
  16. IndexType page.IndexType
  17. FieldCnt byte // 列的数量
  18. FieldIDs [16]byte // 列
  19. EntryPage uint32 // ✅ 你的 IndexPage 首页ID
  20. PrevMetaID uint32 // 上一个
  21. NextMetaID uint32 // 下一个
  22. Flag byte // 标志位 (见 IndexFlag* 常量)
  23. }
  24. // ===== IndexMeta.Flag 位布局 =====
  25. //
  26. // bit7 | bit0
  27. // 已退位(软删除) | 唯一约束
  28. //
  29. // 位 7 的语义由 util.IsHighBitSet 统一判定 (Deleted); 位 0 是本轮新增的唯一约束标记。
  30. const (
  31. // IndexFlagUnique 该索引是唯一索引: 同一索引键只允许存在一行。
  32. //
  33. // 【NULL 免除】复合唯一索引只要有一个分量是 NULL, extractIndexKey 就返回 nil,
  34. // 该行根本不进这个索引 —— 索引里没有条目, 唯一性自然无从违反。因此
  35. // "唯一但可空"的列可以存任意多行 NULL, 与 SQL 的 UNIQUE 语义一致。
  36. IndexFlagUnique byte = 1 << 0
  37. )
  38. // resolveChunk 批量回表的单批行数: 每批"游标走查 + 物化"完成后才触发上层回调,
  39. // 回调里对行存储索引的 Update/Delete 不会踩坏正在推进的游标。
  40. const resolveChunk = 512
  41. // 编译期断言: 钉死 IndexMeta 的盘上大小与字段偏移。
  42. // analyzer/main.go 里那套硬编码偏移 (META_SZ / im[...]) 必须与之一致,
  43. // 因此改布局时这里会先编译失败, 而不是让分析工具静默读错。
  44. //
  45. // 布局 (IndexNameSize=64) = 100 字节:
  46. //
  47. // IndexName[64] + IndexType(1) + FieldCnt(1) + FieldIDs[16]
  48. // + pad(2) + EntryPage(4) + PrevMetaID(4) + NextMetaID(4) + Flag(1) + pad(3)
  49. var (
  50. _ [unsafe.Sizeof(IndexMeta{}) - 100]struct{}
  51. _ [100 - unsafe.Sizeof(IndexMeta{})]struct{}
  52. _ [64]byte = [unsafe.Offsetof(IndexMeta{}.IndexType)]byte{}
  53. _ [65]byte = [unsafe.Offsetof(IndexMeta{}.FieldCnt)]byte{}
  54. _ [66]byte = [unsafe.Offsetof(IndexMeta{}.FieldIDs)]byte{}
  55. _ [84]byte = [unsafe.Offsetof(IndexMeta{}.EntryPage)]byte{}
  56. _ [96]byte = [unsafe.Offsetof(IndexMeta{}.Flag)]byte{}
  57. )
  58. type IndexMetaPage struct {
  59. hdr page.PageHdr
  60. metas []IndexMeta
  61. handle page.HandleDb
  62. hdrSize uint32
  63. metaSize uint32
  64. lastPageID uint32
  65. rowCache []index.DPRange // 行存储索引回表定位缓存 (由处理器复用, 变更时失效)
  66. // active 活跃索引缓存 (元信息 + 已查表的处理器), 惰性构建。
  67. // 热路径 (InsertRow/DeleteByKey/UpdateByKey/ResolveByPK) 每行都要遍历索引,
  68. // 缓存后省去每行每索引的 Flag 判断与 handler 查表; 索引增删/重载时失效。
  69. active []resolvedIndex
  70. activeResolved bool
  71. rowIdx int // active 中行存储索引的下标, -1 表示无
  72. }
  73. // resolvedIndex 已解析的活跃索引: 元信息 + 已查表的处理器
  74. type resolvedIndex struct {
  75. meta *IndexMeta
  76. handler index.IndexHandler
  77. rowStore index.RowStoreHandler // 非 nil = 承载行数据 (主键/时序), 断言结果随缓存一份
  78. }
  79. // 初始化
  80. func (im *IndexMetaPage) Init(pageID uint32) {
  81. im.hdrSize = uint32(unsafe.Sizeof(page.PageHdr{}))
  82. im.metaSize = uint32(unsafe.Sizeof(IndexMeta{}))
  83. im.hdr.PageID = pageID
  84. im.hdr.Type = page.IndexMetaIndex
  85. im.hdr.NextPage = 0
  86. im.lastPageID = pageID
  87. im.metas = im.loadMetas(pageID)
  88. im.invalidateIndexCache()
  89. }
  90. // InitEmpty 把本页初始化成"空的索引元数据页", 专供「刚 NewPage 出来的新页」使用。
  91. //
  92. // 为什么不能直接用 Init —— 调用顺序错了就是 DROP TABLE 假死 52s 的根源:
  93. // - Init 是"打开一张已有页": 它用 loadMetas 从盘上回读本页内容来重建 metas。
  94. // 而新页是从空闲链摘出来的, 回收链 (WAL 回收 / Drop 回收) 都只做 O(1) 拼接、
  95. // 不擦除页内容, 盘上还留着它上一世的东西 —— 典型是 WAL 页 (Type=7、
  96. // UseSize=16384、NextPage 还指向旧 WAL 链)。此时 Init 会把整页旧内容当作
  97. // IndexMeta 数组解析, 还顺着旧链多解析好几页: 实测建 G 表时解出 91 条垃圾元数据
  98. // (IndexType=66/71/76…, EntryPage=16777216)。随后即便用 InitHeader 覆盖成干净页,
  99. // 内存里的垃圾 meta 也不会消失, 最终被 DropTable 拿去枚举页 → FreePage 落到从未
  100. // 分配过的页号上 → 写到几十 GB 偏移, DROP 假死。
  101. // - InitEmpty 不读盘: 先写一张只含页头的干净页覆盖旧内容, 再把内存状态置空。
  102. func (im *IndexMetaPage) InitEmpty(pageID uint32) {
  103. im.hdrSize = uint32(unsafe.Sizeof(page.PageHdr{}))
  104. im.metaSize = uint32(unsafe.Sizeof(IndexMeta{}))
  105. im.hdr.Type = page.IndexMetaIndex
  106. im.hdr.PageID = pageID
  107. im.hdr.NextPage = 0
  108. im.hdr.UseSize = im.hdrSize
  109. im.lastPageID = pageID
  110. im.InitHeader(pageID) // 干净页覆盖回收页里的旧内容
  111. im.metas = nil
  112. im.invalidateIndexCache()
  113. }
  114. // loadMetas 解析索引元数据链 (Init / Reload 共用的读取部分)
  115. func (im *IndexMetaPage) loadMetas(pageID uint32) []IndexMeta {
  116. metas := make([]IndexMeta, 0)
  117. for pageID != 0 {
  118. im.lastPageID = pageID
  119. buf := make([]byte, PageSize)
  120. if err := im.handle.ReadPage(pageID, buf); err != nil {
  121. break
  122. }
  123. hdr := page.HdrOf(buf)
  124. // 页类型 / 自述页号校验: 读索引元数据链绝不能"盘上有多少就解析多少"。
  125. // 新页可能刚从空闲链摘出来, 盘上留着上一世的内容 (典型: WAL 页 Type=7、
  126. // UseSize=16384、NextPage 指向旧 WAL 链)。不校验就会把整页旧内容按 IndexMeta
  127. // 数组解析, 还顺着旧链一路解析下去 —— 实测建表时解出 91 条垃圾元数据
  128. // (EntryPage=16777216), 最终让 DropTable 去 FreePage 从未分配过的页号:
  129. // 写到几十 GB 偏移、DROP 假死 52s、稀疏文件暴涨到 40GB。
  130. if hdr.Type != page.IndexMetaIndex || hdr.PageID != pageID {
  131. break
  132. }
  133. offset := im.hdrSize
  134. for offset+im.metaSize <= hdr.UseSize {
  135. m := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  136. if m.Flag&0x80 == 0 {
  137. metas = append(metas, *m)
  138. }
  139. offset += im.metaSize
  140. }
  141. pageID = hdr.NextPage
  142. }
  143. return metas
  144. }
  145. // Reload 从磁盘重新读回索引元数据, 原地覆盖 im.metas —— 供事务 Rollback 使用。
  146. //
  147. // metas 是 Init 时从页里**拷贝**到 Go 堆的独立副本, 丢弃页缓存并不会刷新它。
  148. // 不重载的话 EntryPage 仍停留在事务中的值 (指向事务里新建的 B+ 树根, 删空时还会被
  149. // 写成 0), 而那个根页已随缓存一起被丢弃 → 回滚后所有索引查找都走空。
  150. //
  151. // firstPageID 必须由调用方从"刚从磁盘读回的 TableInfo.FirstIndexID"传入:
  152. // im.hdr.PageID 同样是内存副本, 事务内首次建索引时可能已被改写。
  153. func (im *IndexMetaPage) Reload(firstPageID uint32) {
  154. im.hdr.PageID = firstPageID
  155. im.hdr.NextPage = 0
  156. im.lastPageID = firstPageID
  157. im.rowCache = nil // 回表定位缓存指向事务中的 DataPage, 必须失效
  158. metas := im.loadMetas(firstPageID)
  159. if len(metas) == len(im.metas) {
  160. copy(im.metas, metas) // 长度一致时原地覆盖, 外部持有的 &metas[i] 继续有效
  161. } else {
  162. im.metas = metas
  163. }
  164. im.invalidateIndexCache() // 元信息已变, 缓存里的 &metas[i] 与 handler 必须重算
  165. }
  166. // PageID 返回索引元数据页 ID (调试用)
  167. func (im *IndexMetaPage) PageID() uint32 { return im.hdr.PageID }
  168. // HdrSize 返回页头大小 (调试用)
  169. func (im *IndexMetaPage) HdrSize() uint32 { return im.hdrSize }
  170. // 创建索引(纯元数据,不碰IndexPage)
  171. //
  172. // unique 是变参 (只取第一个值): 这样 `CreateIndex(name, typ, fields)` 的既有调用
  173. // 一处都不用改, 需要唯一约束的调用方写成 `CreateIndex(name, typ, fields, true)` 即可。
  174. func (im *IndexMetaPage) CreateIndex(indexName string, itype page.IndexType, fieldIDs []byte, unique ...bool) (*IndexMeta, error) {
  175. uniqueIdx := len(unique) > 0 && unique[0]
  176. // 名字超长必须显式报错。下面用 copy 写入定长数组, 超长会被静默截断: 存进盘里的
  177. // 名字与调用方用来查询的名字不一致, 索引建了却永远查不到 —— 必须拦在建的这一步。
  178. // (自动命名 pk_<表名> / idx_<表名>_<列名> 会额外多出几个字节, 所以名字接近上限时
  179. // 这里就会报错, 而不是默默建出一个查不到的索引。
  180. // 上限取 IndexNameSize 而非 TableNameSize —— 索引名与表名是两套独立额度。)
  181. if len(indexName) > IndexNameSize {
  182. return nil, fmt.Errorf("%w: 索引名 %q 有 %d 字节, 上限 %d",
  183. ErrIndexNameTooLong, indexName, len(indexName), IndexNameSize)
  184. }
  185. // 分量个数闸门。先按 int 判再转 byte: 直接 `byte(len(fieldIDs))` 会在超过 255 时回绕
  186. // (例如 257 个份量截成 1), 于是越界的调用反而"通过"了, 之后按错误的分量数建条目。
  187. if len(fieldIDs) == 0 || len(fieldIDs) > MaxIndexFields {
  188. return nil, fmt.Errorf("%w (本次 %d 个分量, 索引 %q)",
  189. ErrIndexFieldCount, len(fieldIDs), indexName)
  190. }
  191. fc := byte(len(fieldIDs))
  192. // 复合键要求各分量互不相同。重复列会让 extractIndexKey 永远取不齐分量
  193. // (某一分量无人填充), 索引静默变成空索引 —— 写入成功却查不到, 因此直接拒绝。
  194. var seen [256]bool
  195. for _, fid := range fieldIDs {
  196. if seen[fid] {
  197. return nil, ErrIndexFieldDuplicated
  198. }
  199. seen[fid] = true
  200. }
  201. // 行存储索引 (主键 B+ 树 / 时序) 的键同时就是"行在哪里"的定位键,
  202. // 复合键会让回表语义失效 (ResolveByPK 用 pkID, 与键是两回事), 明确拒绝。
  203. if fc > 1 {
  204. if h, ok := index.Compose.GetHandler(itype); ok && handlerStoresRows(h) {
  205. return nil, ErrIndexCompositeRowStore
  206. }
  207. }
  208. // 行存储索引不支持唯一约束, 明确拒绝而不是静默忽略:
  209. // - 主键索引本就唯一 (InsertRowWithID 用 ErrDuplicatePK 兜着), 唯一标记是废话;
  210. // - 时序索引的多行同刻是【刻意允许】的 (同一时刻的多个测点), 给它加唯一约束
  211. // 等于把时序表写死 —— 而"忽略这个标记"会让调用方以为约束生效了,
  212. // 之后插入同刻第二行成功时才发觉不对, 那时数据已经写进去了。
  213. if uniqueIdx {
  214. if h, ok := index.Compose.GetHandler(itype); ok && handlerStoresRows(h) {
  215. return nil, fmt.Errorf("%w (索引 %q)", ErrUniqueRowStore, indexName)
  216. }
  217. }
  218. // 一张表只能有一个行存储索引。行数据 ([pkID + rowData]) 只可能落在其中一个里, 而
  219. // resolveIndexes 取的是"第一个实现 RowStoreHandler 的索引"; 再建一个不会立刻出错, 只会得到一个
  220. // "元数据说自己是行存储、却一行都没有"的索引 —— 之后所有按行存储解读它的路径
  221. // (回表、删除、重建) 都会错乱, 而且错得没有来路。所以拦在建的这一步。
  222. //
  223. // 主键 (B+ 树) 与时序都属于行存储, 因此"建了主键表再补时序索引"同样被拒 —— 这不是
  224. // 额外限制, 而是引擎的真实约束: 一份行数据不可能同时按 pkID 和按时间分段存放。
  225. if h, ok := index.Compose.GetHandler(itype); ok && handlerStoresRows(h) {
  226. for i := range im.metas {
  227. m := &im.metas[i]
  228. if m.Flag&0x80 != 0 { // 已退位的元数据不占位
  229. continue
  230. }
  231. if indexStoresRows(m) {
  232. return nil, fmt.Errorf("%w: 表上已有行存储索引 %q (类型 %d), 不能再建 %q (类型 %d)",
  233. ErrRowStoreExists, util.BytesToString(m.IndexName[:]), m.IndexType, indexName, itype)
  234. }
  235. }
  236. }
  237. for _, m := range im.metas {
  238. if util.BytesToString(m.IndexName[:]) == indexName && m.Flag&0x80 == 0 {
  239. return nil, ErrIndexExists
  240. }
  241. }
  242. var m IndexMeta
  243. copy(m.IndexName[:], indexName)
  244. m.IndexType = itype
  245. m.FieldCnt = fc
  246. copy(m.FieldIDs[:fc], fieldIDs)
  247. m.Flag = 0
  248. m.SetUnique(uniqueIdx) // bit7 是软删除 (Deleted), 新索引一律不带; 唯一位按参数置
  249. m.EntryPage = 0
  250. var buf []byte
  251. var hdr *page.PageHdr
  252. if im.hdr.PageID == 0 {
  253. pid := im.handle.NewPage()
  254. buf, hdr = im.InitHeader(pid)
  255. im.hdr.PageID = pid
  256. im.lastPageID = pid
  257. } else {
  258. buf = make([]byte, PageSize)
  259. im.handle.ReadPage(im.lastPageID, buf)
  260. hdr = page.HdrOf(buf)
  261. }
  262. offset := hdr.UseSize
  263. if offset+im.metaSize > PageSize {
  264. newID := im.handle.NewPage()
  265. newBuf, newHdr := im.InitHeader(newID)
  266. hdr.NextPage = newID
  267. im.handle.SavePage(im.lastPageID, buf) // 保存下当前页 不然就被新开的页替换了
  268. im.lastPageID = newID
  269. buf = newBuf
  270. hdr = newHdr
  271. offset = im.hdrSize
  272. }
  273. dst := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  274. *dst = m
  275. hdr.UseSize = offset + im.metaSize
  276. im.handle.SavePage(im.lastPageID, buf)
  277. im.metas = append(im.metas, m)
  278. im.invalidateIndexCache() // metas 扩容后旧缓存里的 &metas[i] 会失效
  279. // 必须返回 im.metas 里的那一份, 而不是局部变量 m 的地址:
  280. // 调用方 (Table.CreateIndex → backfillIndex) 会在建完树后回写 EntryPage。
  281. // 若返回 &m, 回写只落在局部副本 + 磁盘页上, 而查询走的是 im.metas ——
  282. // 于是"回填了存量行却查不到", 直到关库重开 (metas 从盘上重载) 才恢复正常。
  283. // 回填过程中不会 append 新 meta, 故该指针在本次调用内保持有效。
  284. return &im.metas[len(im.metas)-1], nil
  285. }
  286. // 查找单个索引
  287. func (im *IndexMetaPage) FindIndex(idxName string) (*IndexMeta, bool) {
  288. for i := range im.metas {
  289. m := &im.metas[i]
  290. if util.BytesToString(m.IndexName[:]) == idxName && m.Flag&0x80 == 0 {
  291. return m, true
  292. }
  293. }
  294. return nil, false
  295. }
  296. // 删除索引
  297. func (im *IndexMetaPage) DropIndex(idxName string) error {
  298. cur := im.hdr.PageID
  299. for cur != 0 {
  300. buf := make([]byte, PageSize)
  301. im.handle.ReadPage(cur, buf)
  302. hdr := page.HdrOf(buf)
  303. // 同 loadMetas: 页类型/自述页号对不上就不是索引元数据页, 不能在这上面按名字改 Flag
  304. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  305. break
  306. }
  307. offset := im.hdrSize
  308. for offset+im.metaSize <= hdr.UseSize {
  309. m := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  310. if util.BytesToString(m.IndexName[:]) == idxName && m.Flag&0x80 == 0 {
  311. m.Flag |= 0x80
  312. im.handle.SavePage(cur, buf)
  313. for i := range im.metas {
  314. if util.BytesToString(im.metas[i].IndexName[:]) == idxName {
  315. im.metas = append(im.metas[:i], im.metas[i+1:]...)
  316. im.invalidateIndexCache() // metas 已删除元素, 旧缓存 (含下标) 失效
  317. break
  318. }
  319. }
  320. return nil
  321. }
  322. offset += im.metaSize
  323. }
  324. cur = hdr.NextPage
  325. }
  326. return ErrIndexNotFound
  327. }
  328. // RenameIndex 重命名索引 (只改 IndexMeta.IndexName 字符串, 零索引页迁移)。
  329. func (im *IndexMetaPage) RenameIndex(oldName, newName string) error {
  330. if len(newName) > IndexNameSize {
  331. return fmt.Errorf("新索引名 %q 太长: %d > %d", newName, len(newName), IndexNameSize)
  332. }
  333. // 新名不能和别的活索引重名
  334. for _, m := range im.metas {
  335. if util.BytesToString(m.IndexName[:]) == newName && m.Flag&0x80 == 0 {
  336. return fmt.Errorf("索引 %q 已存在", newName)
  337. }
  338. }
  339. // 盘上改
  340. cur := im.hdr.PageID
  341. for cur != 0 {
  342. buf := make([]byte, PageSize)
  343. im.handle.ReadPage(cur, buf)
  344. hdr := page.HdrOf(buf)
  345. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  346. break
  347. }
  348. offset := im.hdrSize
  349. for offset+im.metaSize <= hdr.UseSize {
  350. m := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  351. if util.BytesToString(m.IndexName[:]) == oldName && m.Flag&0x80 == 0 {
  352. for i := range m.IndexName {
  353. m.IndexName[i] = 0
  354. }
  355. copy(m.IndexName[:], newName)
  356. if err := im.handle.SavePage(cur, buf); err != nil {
  357. return err
  358. }
  359. // 内存态同步
  360. for i := range im.metas {
  361. if util.BytesToString(im.metas[i].IndexName[:]) == oldName {
  362. for j := range im.metas[i].IndexName {
  363. im.metas[i].IndexName[j] = 0
  364. }
  365. copy(im.metas[i].IndexName[:], newName)
  366. break
  367. }
  368. }
  369. im.invalidateIndexCache()
  370. return nil
  371. }
  372. offset += im.metaSize
  373. }
  374. cur = hdr.NextPage
  375. }
  376. return ErrIndexNotFound
  377. }
  378. func (im *IndexMetaPage) InitHeader(pageID uint32) ([]byte, *page.PageHdr) {
  379. buf := make([]byte, PageSize)
  380. hdr := page.HdrOf(buf)
  381. hdr.Type = page.IndexMetaIndex
  382. hdr.PageID = pageID
  383. hdr.UseSize = uint32(unsafe.Sizeof(*hdr))
  384. im.handle.SavePage(pageID, buf)
  385. return buf, hdr
  386. }
  387. // ======================== 高层索引操作 ========================
  388. // rowStoreMeta 返回承载行数据的一级索引 (主键/时序)。
  389. // 本层不认识索引类型枚举: 谁是"行存储"由处理器是否实现 RowStoreHandler 回答。
  390. func (im *IndexMetaPage) rowStoreMeta() (*IndexMeta, index.RowStoreHandler, bool) {
  391. im.resolveIndexes()
  392. if im.rowIdx < 0 {
  393. return nil, nil, false
  394. }
  395. ri := im.active[im.rowIdx]
  396. return ri.meta, ri.rowStore, true
  397. }
  398. // Name 索引名 (去掉定长补零)。
  399. func (m *IndexMeta) Name() string {
  400. if m == nil {
  401. return ""
  402. }
  403. return util.BytesToString(m.IndexName[:])
  404. }
  405. // Fields 索引分量在表 master registry 中的字段下标 (按建索引时给的顺序)。
  406. // 返回拷贝: 调用方不该拿到会随元数据增删而失效的内存。
  407. func (m *IndexMeta) Fields() []byte {
  408. if m == nil || m.FieldCnt == 0 {
  409. return nil
  410. }
  411. n := int(m.FieldCnt)
  412. if n > len(m.FieldIDs) {
  413. n = len(m.FieldIDs)
  414. }
  415. out := make([]byte, n)
  416. copy(out, m.FieldIDs[:n])
  417. return out
  418. }
  419. // Deleted 索引是否已被软删除 (Flag 最高位)。
  420. func (m *IndexMeta) Deleted() bool {
  421. return m != nil && util.IsHighBitSet(m.Flag)
  422. }
  423. // Unique 该索引是否为唯一索引 (同一索引键只允许一行; NULL 分量不进索引, 不参与约束)。
  424. //
  425. // 这是唯一约束的【权威标记】: 列上的 types.FlagUnique 只是声明, 约束由索引在
  426. // 写入路径上执行 (插入/更新前先按键探一次), 所以判定必须以索引元信息为准。
  427. func (m *IndexMeta) Unique() bool {
  428. return m != nil && m.Flag&IndexFlagUnique != 0
  429. }
  430. // SetUnique 就地设置/取消唯一标记 (仅改内存元信息, 落盘由 saveMeta 负责)。
  431. func (m *IndexMeta) SetUnique(v bool) {
  432. if m == nil {
  433. return
  434. }
  435. if v {
  436. m.Flag |= IndexFlagUnique
  437. } else {
  438. m.Flag &^= IndexFlagUnique
  439. }
  440. }
  441. // SetName 就地设置索引名 (仅改内存元信息, 落盘由 saveMeta 负责)。
  442. func (m *IndexMeta) SetName(name string) error {
  443. if m == nil {
  444. return fmt.Errorf("nil IndexMeta")
  445. }
  446. if len(name) > IndexNameSize {
  447. return fmt.Errorf("索引名 %q 太长: %d > %d", name, len(name), IndexNameSize)
  448. }
  449. for i := range m.IndexName {
  450. m.IndexName[i] = 0
  451. }
  452. copy(m.IndexName[:], name)
  453. return nil
  454. }
  455. // GetIndexes 返回本表全部可用索引的元数据拷贝 (按创建顺序, 已删除的跳过)。
  456. //
  457. // 元数据是迁移/导出类工具的入口: 在此之前 core 包外没有任何办法枚举一张表的索引
  458. // (metas 未导出, 只能逐名 FindIndex —— 那要求调用方事先知道所有索引名)。
  459. // 返回拷贝而不是内部指针: metas 会随 CreateIndex/DropIndex 重新分配,
  460. // 外部持有 &metas[i] 只会在下一次建/删索引时变成野指针。
  461. func (im *IndexMetaPage) GetIndexes() []IndexMeta {
  462. im.resolveIndexes()
  463. out := make([]IndexMeta, 0, len(im.active))
  464. for _, ri := range im.active {
  465. out = append(out, *ri.meta)
  466. }
  467. return out
  468. }
  469. // GetRowStoreIndex 返回承载行数据的一级索引 (VRTX 的主键索引 / 时序索引) 的元数据拷贝。
  470. // 第二个返回值为 false 表示这张表没有行存储索引 —— 此时行数据无处安放,
  471. // 调用方应当报错而不是"照常建表".
  472. func (im *IndexMetaPage) GetRowStoreIndex() (IndexMeta, bool) {
  473. m, _, ok := im.rowStoreMeta()
  474. if !ok {
  475. return IndexMeta{}, false
  476. }
  477. return *m, true
  478. }
  479. // resolveIndexes 惰性构建并返回活跃索引缓存 (跳过已删除/无处理器的索引)。
  480. //
  481. // 缓存的 &metas[i] 指针在 metas 发生增删或整体替换后会失效, 因此 CreateIndex /
  482. // DropIndex / Reload / Init 都必须调用 invalidateIndexCache。
  483. func (im *IndexMetaPage) resolveIndexes() []resolvedIndex {
  484. if im.activeResolved {
  485. return im.active
  486. }
  487. im.active = im.active[:0]
  488. im.rowIdx = -1
  489. for i := range im.metas {
  490. m := &im.metas[i]
  491. if m.Flag&0x80 != 0 {
  492. continue
  493. }
  494. h, ok := index.Compose.GetHandler(m.IndexType)
  495. if !ok {
  496. continue
  497. }
  498. rs, _ := h.(index.RowStoreHandler)
  499. if im.rowIdx < 0 && rs != nil {
  500. im.rowIdx = len(im.active)
  501. }
  502. im.active = append(im.active, resolvedIndex{meta: m, handler: h, rowStore: rs})
  503. }
  504. im.activeResolved = true
  505. return im.active
  506. }
  507. // invalidateIndexCache 使活跃索引缓存失效 (元信息增删/重载后必须调用)。
  508. func (im *IndexMetaPage) invalidateIndexCache() {
  509. im.activeResolved = false
  510. im.active = im.active[:0]
  511. im.rowIdx = -1
  512. }
  513. // indexStoresRows 询问索引处理器: 该索引是否承载行数据。
  514. // 实现 index.RowStoreHandler 即承载; 调用方据此判断"行存在哪里",
  515. // 不需要认识具体索引类型 (主键/时序)。
  516. func indexStoresRows(m *IndexMeta) bool {
  517. if m == nil {
  518. return false
  519. }
  520. h, ok := index.Compose.GetHandler(m.IndexType)
  521. if !ok {
  522. return false
  523. }
  524. return handlerStoresRows(h)
  525. }
  526. // handlerStoresRows 该处理器是否承载行数据 (实现 index.RowStoreHandler)。
  527. func handlerStoresRows(h index.IndexHandler) bool {
  528. _, ok := h.(index.RowStoreHandler)
  529. return ok
  530. }
  531. // resolver 提供给索引处理器的回表函数: 二级索引内部据此"去找一级索引"取整行,
  532. // 上层无需参与, 也无需知道哪个索引才是主键。
  533. func (im *IndexMetaPage) resolver() index.RowResolver {
  534. return func(pkID uint64) ([]byte, bool) { return im.ResolveByPK(pkID) }
  535. }
  536. // findIndexMetaByName 按索引名查找活跃索引的元信息 (跳过已删除)。
  537. func (im *IndexMetaPage) findIndexMetaByName(name string) *IndexMeta {
  538. for i := range im.metas {
  539. if util.BytesToString(im.metas[i].IndexName[:]) == name && im.metas[i].Flag&0x80 == 0 {
  540. return &im.metas[i]
  541. }
  542. }
  543. return nil
  544. }
  545. func (im *IndexMetaPage) saveMeta(m *IndexMeta) {
  546. cur := im.hdr.PageID
  547. for cur != 0 {
  548. buf := make([]byte, PageSize)
  549. im.handle.ReadPage(cur, buf)
  550. hdr := page.HdrOf(buf)
  551. // 同 loadMetas: 页类型/自述页号对不上就不是索引元数据页, 不能在这上面按名字覆盖元数据
  552. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  553. break
  554. }
  555. offset := im.hdrSize
  556. for offset+im.metaSize <= hdr.UseSize {
  557. dst := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  558. if util.BytesToString(dst.IndexName[:]) == util.BytesToString(m.IndexName[:]) {
  559. *dst = *m
  560. im.handle.SavePage(cur, buf)
  561. return
  562. }
  563. offset += im.metaSize
  564. }
  565. cur = hdr.NextPage
  566. }
  567. }
  568. // rowStoreInsertKey 行存储索引的定位键:
  569. //
  570. // - 主键 B+ 树: 恒为 pkID(BE)。查询/删除/更新侧一律按 pkID 定位, 与镜像列
  571. // 的存储编码无关 —— 旧实现统一走 extractIndexKey (取镜像列字节), 镜像列是
  572. // Int64 等带 sort-key 编码的类型时, 树键与 pkID-BE 不一致: 按 pkID 查找
  573. // 退化成全叶扫描, 删除/更新按错键静默失效 (ErrNotFound 按索引逐个吞掉)。
  574. // Uint64 镜像因"存储字节==BE"而侥幸正确, 掩盖了这条错位。
  575. // - 时序索引: 键 = 时间分量 (extractIndexKey); 其顺序断言/分段路由都在同一
  576. // 编码内自洽 (sort-key 保序), 维持原样。
  577. //
  578. // 插入/删除/更新三侧必须用同一键源, 否则写进树里的键和去树里找的键对不上。
  579. func rowStoreInsertKey(meta *IndexMeta, pkID uint64, rowData []byte, fields []types.FieldInfo, versions []SchemaVersion) []byte {
  580. if meta.IndexType == page.IndexTypePrimary {
  581. key := make([]byte, 8)
  582. binary.BigEndian.PutUint64(key, pkID)
  583. return key
  584. }
  585. return extractIndexKey(rowData, meta, fields, versions)
  586. }
  587. // extractIndexKey 从行数据中提取索引键 (版本感知)
  588. // rowData 格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  589. //
  590. // 复合索引的键 = 按 FieldIDs 顺序, 把各分量字段的原始字节【补齐到档位宽度】后定长拼接。
  591. // - 顺序取 FieldIDs 而非行内物理顺序 —— 查询侧据此才能做"列前缀"范围推导:
  592. // (a, b) 上的索引既能服务 a=?, 也能服务 a=? AND b>?;
  593. // - 每个分量长度 = FieldSize(字段类型, 列级参数), 与查询侧编键完全一致, 因此拼接键是
  594. // 定长的, 字节序比较等价于"先比 a 再比 b"的字典序。宽度必须带列级参数 (字符串列的
  595. // 宽度码): 按类型默认档切键会把宽字符串列切成半截, 后面那个分量整体读错位;
  596. // - v9 起行内字符串列按【实际长度】存储, 取出的字节要 PadToWidth 补零到档位宽度 ——
  597. // 索引键必须保持定长, 二级索引布局 [key][PKID8] 与 idxKeyLenOf 才不会变形;
  598. // - 任一分量在该行版本里取不到 (列被 Drop / schema 版本不匹配) → 返回 nil,
  599. // 该索引跳过这一行, 与单列索引"取不到键就跳过"的既有语义一致。
  600. func extractIndexKey(rowData []byte, meta *IndexMeta, fields []types.FieldInfo, versions []SchemaVersion) []byte {
  601. if meta == nil || meta.FieldCnt == 0 || len(rowData) < 1 {
  602. return nil
  603. }
  604. n := int(meta.FieldCnt)
  605. if n > len(meta.FieldIDs) {
  606. return nil
  607. }
  608. // 从 rowData 读取该行的 schema 版本; 列位置由行内偏移表给出 —— v9 字符串列变长,
  609. // 旧实现"从偏移 1 起累加档位宽度"必然错位。
  610. layout, ok := parseRowLayoutOf(fields, versions, rowData, rowData[0])
  611. if !ok {
  612. return nil
  613. }
  614. sv := layout.sv
  615. rowN := len(sv.FieldIndices)
  616. if rowN == 0 {
  617. return nil
  618. }
  619. col0Width := layout.col0Width
  620. if col0Width <= 0 {
  621. return nil
  622. }
  623. ends := layout.ends
  624. // seg[k] 指向拼键用的定长字节切片 (补齐后的副本或原位切片), 全部取齐后再一次性拼键。
  625. var (
  626. seg [16][]byte
  627. need = n
  628. )
  629. for i, idx := range sv.FieldIndices {
  630. fi := int(idx)
  631. if fi >= len(fields) {
  632. return nil
  633. }
  634. size := int(fields[fi].Width())
  635. if size == 0 { // 未知类型/非法列参数: 切不出定长分量, 拼出的键会错位
  636. return nil
  637. }
  638. for k := 0; k < n; k++ {
  639. if int(meta.FieldIDs[k]) != fi {
  640. continue
  641. }
  642. // 分量是 NULL → 该行不进本索引 (键里放不下"缺失"这一档, 上层按 nil 键跳过)。
  643. if types.RowIsNull(rowData, ends, rowN, i) {
  644. return nil
  645. }
  646. start := int(types.RowFieldStart(ends, i))
  647. end := int(types.RowFieldEnd(ends, i, col0Width))
  648. if end < start || end > len(rowData) {
  649. return nil
  650. }
  651. // 行内存的是实际长度, 索引键要的是档位定长: 补零对齐, 与查询侧 EncodeValue 同源。
  652. seg[k] = types.PadToWidth(rowData[start:end], uint16(size))
  653. need--
  654. break
  655. }
  656. if need == 0 {
  657. break // 分量已取齐, 后面的字段与本索引无关
  658. }
  659. }
  660. if need != 0 {
  661. return nil // 有分量不在该版本中 (如该列已被 Drop), 无法构成完整键
  662. }
  663. total := 0
  664. for k := 0; k < n; k++ {
  665. total += len(seg[k])
  666. }
  667. key := make([]byte, 0, total)
  668. for k := 0; k < n; k++ {
  669. key = append(key, seg[k]...)
  670. }
  671. return key
  672. }
  673. // InsertRow 插入行到所有索引
  674. func (im *IndexMetaPage) InsertRow(fields []types.FieldInfo, pkID uint64, rowData []byte, activeVer byte, versions []SchemaVersion) error {
  675. im.rowCache = nil // 插入可能创建新 DataPage, 失效缓存
  676. indexes := im.resolveIndexes()
  677. // 唯一约束预检必须先于任何写入: 逐个索引边查边写的话, 撞车时前面几个索引
  678. // (含承载行数据的行存储索引) 已经写进去了, 只剩一个错误返回 —— 库里多出一行
  679. // 半成品, 而且它的行还在、二级条目不全, 之后按任何二级索引都查不到它。
  680. if err := im.checkUniqueForKey(indexes, rowData, pkID, fields, activeVer, versions); err != nil {
  681. return err
  682. }
  683. for _, ri := range indexes {
  684. m := ri.meta
  685. indexKey := extractIndexKey(rowData, m, fields, versions)
  686. if indexStoresRows(m) {
  687. indexKey = rowStoreInsertKey(m, pkID, rowData, fields, versions)
  688. }
  689. if indexKey == nil {
  690. continue
  691. }
  692. entryPage := m.EntryPage
  693. _, err := ri.handler.Insert(im.handle, &entryPage, indexKey, pkID, rowData, len(indexKey))
  694. if err != nil {
  695. return err
  696. }
  697. if entryPage != m.EntryPage {
  698. m.EntryPage = entryPage
  699. im.saveMeta(m)
  700. }
  701. }
  702. return nil
  703. }
  704. // InsertRows 批量插入: pkIDs[i] 与 rowDatas[i] 一一对应。
  705. //
  706. // 与单行 InsertRow 的语义差异只在"摊销", 不在"语义":
  707. // - 唯一约束仍是"先全部预检、再动任何写入"—— 每行对既有树的查重照做,
  708. // 另补"批内查重"(单行路径里批内冲突会在第 i+1 行的树查重中被发现, 批量
  709. // 路径一次性排序比出来, 语义等价且更便宜)。
  710. // - 实现了 BatchInserter 的索引 (主键 B+ 树 / 二级 B+ 树 / 复合索引) 走批量路径;
  711. // 其余 (时序 / hash) 退化为逐条 Insert, 行为与单行路径逐字节一致。
  712. //
  713. // 失败语义与单行路径相同: 中途报错时前面已写入的索引不回滚 (调用方靠事务回滚)。
  714. func (im *IndexMetaPage) InsertRows(fields []types.FieldInfo, pkIDs []uint64, rowDatas [][]byte, activeVer byte, versions []SchemaVersion) error {
  715. if len(rowDatas) == 0 {
  716. return nil
  717. }
  718. im.rowCache = nil // 批量插入必然新建 DataPage, 一次性失效缓存
  719. indexes := im.resolveIndexes()
  720. // 唯一约束预检: 全部查完才动第一笔写入 (与单行路径同一铁律)
  721. hasUnique := false
  722. for _, ri := range indexes {
  723. if ri.meta.Unique() {
  724. hasUnique = true
  725. break
  726. }
  727. }
  728. if hasUnique {
  729. for i := range rowDatas {
  730. if err := im.checkUniqueForKey(indexes, rowDatas[i], pkIDs[i], fields, activeVer, versions); err != nil {
  731. return err
  732. }
  733. }
  734. // 批内查重: 同一唯一索引里两行同键
  735. for _, ri := range indexes {
  736. if !ri.meta.Unique() {
  737. continue
  738. }
  739. keys := make([][]byte, 0, len(rowDatas))
  740. for i := range rowDatas {
  741. if k := extractIndexKey(rowDatas[i], ri.meta, fields, versions); k != nil {
  742. keys = append(keys, k)
  743. }
  744. }
  745. slices.SortFunc(keys, bytes.Compare)
  746. for j := 1; j < len(keys); j++ {
  747. if bytes.Equal(keys[j-1], keys[j]) {
  748. return fmt.Errorf("%w: 索引 %q, 批内重复键",
  749. ErrDuplicatedKey, util.BytesToString(ri.meta.IndexName[:]))
  750. }
  751. }
  752. }
  753. }
  754. for _, ri := range indexes {
  755. m := ri.meta
  756. entryPage := m.EntryPage
  757. var err error
  758. if bi, ok := ri.handler.(index.BatchInserter); ok {
  759. rows := make([]index.BatchRow, len(rowDatas))
  760. for i := range rowDatas {
  761. var ik []byte
  762. if indexStoresRows(m) {
  763. ik = rowStoreInsertKey(m, pkIDs[i], rowDatas[i], fields, versions)
  764. } else {
  765. ik = extractIndexKey(rowDatas[i], m, fields, versions)
  766. }
  767. rows[i] = index.BatchRow{
  768. PKID: pkIDs[i],
  769. IndexKey: ik,
  770. RowData: rowDatas[i],
  771. }
  772. }
  773. err = bi.InsertBatch(im.handle, &entryPage, rows)
  774. } else {
  775. for i := range rowDatas {
  776. indexKey := extractIndexKey(rowDatas[i], m, fields, versions)
  777. if indexStoresRows(m) {
  778. indexKey = rowStoreInsertKey(m, pkIDs[i], rowDatas[i], fields, versions)
  779. }
  780. if indexKey == nil {
  781. continue
  782. }
  783. if _, err = ri.handler.Insert(im.handle, &entryPage, indexKey, pkIDs[i], rowDatas[i], len(indexKey)); err != nil {
  784. break
  785. }
  786. }
  787. }
  788. if err != nil {
  789. return err
  790. }
  791. if entryPage != m.EntryPage {
  792. m.EntryPage = entryPage
  793. im.saveMeta(m)
  794. }
  795. }
  796. return nil
  797. }
  798. // CheckUniqueForKey 校验行数据 rowData 是否会撞上某个唯一索引里【别的行】已有的键。
  799. // 冲突返回 ErrDuplicatedKey; 无冲突 (含"该行压根不进这些索引") 返回 nil。
  800. //
  801. // 抽成独立方法是因为它有两个必须分开的调用时机:
  802. // - InsertRow / UpdateByKey 内部各调一次 —— 保证任何入口在写入前都拦得住;
  803. // - Table 的更新路径还要在 serializeRow 之前【再】调一次: serializeRow 会就地
  804. // 覆写旧行的动态堆块, 冲突必须在它动手之前发现 (见 serializeRowForCheck)。
  805. //
  806. // 因此这里只读不写、可重复调用。
  807. func (im *IndexMetaPage) CheckUniqueForKey(rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  808. return im.checkUniqueForKey(im.resolveIndexes(), rowData, pkID, fields, activeVer, versions)
  809. }
  810. // HasUniqueIndex 是否存在活跃的唯一索引。
  811. //
  812. // 更新路径 (checkUniqueUpdate) 用它跳过 serializeRowForCheck: 预检的唯一目的是在
  813. // serializeRow 动手覆写旧行动态堆块之前拦下唯一冲突 —— 没有唯一索引时整行试序列化
  814. // 纯属浪费一次全行序列化。resolveIndexes 的结果有缓存, 本判断近乎零成本。
  815. func (im *IndexMetaPage) HasUniqueIndex() bool {
  816. for _, ri := range im.resolveIndexes() {
  817. if ri.meta.Unique() {
  818. return true
  819. }
  820. }
  821. return false
  822. }
  823. // checkUniqueForKey 是 CheckUniqueForKey 的内核, 收【已解析】的索引集。
  824. // InsertRow / UpdateByKey 手里本来就有一份 resolveIndexes 的结果, 再解析一遍等于
  825. // 每次写入都把索引元信息链多走一趟 —— 插入/更新是热路径, 这趟省掉。
  826. func (im *IndexMetaPage) checkUniqueForKey(indexes []resolvedIndex, rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  827. for _, ri := range indexes {
  828. if !ri.meta.Unique() {
  829. continue
  830. }
  831. indexKey := extractIndexKey(rowData, ri.meta, fields, versions)
  832. if indexKey == nil {
  833. continue // 任一分量为 NULL → 该行不进本索引, 唯一性对 NULL 不设限
  834. }
  835. if im.uniqueConflict(ri, indexKey, pkID) {
  836. return fmt.Errorf("%w: 索引 %q, pkID=%d",
  837. ErrDuplicatedKey, util.BytesToString(ri.meta.IndexName[:]), pkID)
  838. }
  839. }
  840. return nil
  841. }
  842. // uniqueConflict 判断唯一索引里是否已有"别的行"占着这个键。
  843. //
  844. // 用 resolve=nil 直接拿条目里的 8B pkID: 唯一性只关心"是不是同一行", 不需要回表取整行
  845. // (回表会顺着行存储索引再查一次, 每次插入都多一遍 O(页数) 的定位)。返回的 pkID 与
  846. // 当前行相同说明命中的是本行自己的条目 (更新场景), 不算冲突。
  847. func (im *IndexMetaPage) uniqueConflict(ri resolvedIndex, indexKey []byte, pkID uint64) bool {
  848. got, found := ri.handler.Search(im.handle, ri.meta.EntryPage, indexKey, len(indexKey), nil)
  849. if !found || len(got) < 8 {
  850. return false
  851. }
  852. return binary.BigEndian.Uint64(got[:8]) != pkID
  853. }
  854. // searchAt 在已定位的索引元信息上查一行 (统一契约: 返回整行, 带 8B pkID 前缀)。
  855. //
  856. // 本层不判断索引类型: 二级索引所缺的行数据由处理器内部用回表函数补齐。
  857. // 独立抽出是为了让"先定位、再复用"的调用方 (Table.SearchByIndex/DeleteRow/UpdateRow)
  858. // 只做一次树查找, 不必重复「findIndexMetaByName + handler.Search」。
  859. func (im *IndexMetaPage) searchAt(meta *IndexMeta, indexKey []byte) ([]byte, bool) {
  860. if meta == nil {
  861. return nil, false
  862. }
  863. handler, ok := index.Compose.GetHandler(meta.IndexType)
  864. if !ok {
  865. return nil, false
  866. }
  867. return handler.Search(im.handle, meta.EntryPage, indexKey, len(indexKey), im.resolver())
  868. }
  869. // SearchByIndex 通过索引搜索 (统一契约: 返回整行, 带 8B pkID 前缀)
  870. func (im *IndexMetaPage) SearchByIndex(idxName string, indexKey []byte) ([]byte, bool) {
  871. return im.searchAt(im.findIndexMetaByName(idxName), indexKey)
  872. }
  873. // ResolveByPK 按 pkID 查找整行数据 (带 8B pkID 前缀)。
  874. //
  875. // 本层不知道行数据"存在哪种索引里": 由实现 RowStoreHandler 的处理器自己读取
  876. // (主键 B+ 树走 DataPage 二分缓存, 时序索引走条目链)。
  877. func (im *IndexMetaPage) ResolveByPK(pkID uint64) ([]byte, bool) {
  878. m, h, ok := im.rowStoreMeta()
  879. if !ok {
  880. return nil, false
  881. }
  882. return h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache)
  883. }
  884. // BatchResolveByPK 批量回表, 结果按【传入顺序】交付 (sink 回调)。
  885. //
  886. // 传入顺序即二级索引 RangeScan 的输出序 —— B+ 树按索引键序输出、Hash 范围扫描
  887. // 内部也按键排序; needSort=false 的免排序 ORDER BY 路径依赖这个顺序不被打乱,
  888. // 所以内部排序只是为了游标走查, 交付时恢复传入顺序。
  889. //
  890. // budget > 0 时最多交付 budget 行 (LIMIT+OFFSET 场景): 批尺寸随剩余预算收缩,
  891. // 达到预算立即收工 —— Where(重键=值).First() 这类点查只解 1 行, 而不是把
  892. // 命中桶里的上千行全部解出再丢掉。剩余条件会过滤行的场景: 某批交付不足预算时
  893. // 继续下一批, 直到预算耗尽或输入走完。
  894. //
  895. // 实现: 输入的升序拷贝走单调游标 (BatchRowResolver) 收进 map, 再按原序回调;
  896. // 未实现批量接口的处理器退化为逐个 ResolveRow (按传入顺序, 与旧行为一致)。
  897. // 行快照跨 sink 调用有效。sink 返回 false 时提前停止。
  898. func (im *IndexMetaPage) BatchResolveByPK(pkIDs []uint64, budget int,
  899. sink func(pkID uint64, storeRow []byte) bool) {
  900. m, h, ok := im.rowStoreMeta()
  901. if !ok || len(pkIDs) == 0 {
  902. return
  903. }
  904. emitted := 0
  905. br, isBatch := h.(index.BatchRowResolver)
  906. if !isBatch {
  907. for _, pkID := range pkIDs {
  908. storeRow, found := h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache)
  909. if !found {
  910. continue // 行已被删除, 跳过
  911. }
  912. if !sink(pkID, storeRow) {
  913. return
  914. }
  915. emitted++
  916. if budget > 0 && emitted >= budget {
  917. return
  918. }
  919. }
  920. return
  921. }
  922. // 分批: 每批取输入的一个连续子段, 子段单独升序后走游标走查,
  923. // 交付严格按该子段的输入顺序。批与批之间游标重启, 回调里的索引改写安全。
  924. for start := 0; start < len(pkIDs); {
  925. if budget > 0 && budget-emitted <= 0 {
  926. return
  927. }
  928. chunk := resolveChunk
  929. if budget > 0 && budget-emitted < chunk {
  930. chunk = budget - emitted // 预算紧张时批随预算收缩 (点查退化为单行解)
  931. }
  932. end := start + chunk
  933. if end > len(pkIDs) {
  934. end = len(pkIDs)
  935. }
  936. inputChunk := pkIDs[start:end]
  937. if len(inputChunk) == 1 {
  938. // 点查快路径 (LIMIT=1/First 常见): 单行直解, 免排序拷贝与 map
  939. pkID := inputChunk[0]
  940. if storeRow, found := h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache); found {
  941. if !sink(pkID, storeRow) {
  942. return
  943. }
  944. emitted++
  945. }
  946. start = end
  947. continue
  948. }
  949. sortedChunk := make([]uint64, len(inputChunk))
  950. copy(sortedChunk, inputChunk)
  951. slices.Sort(sortedChunk)
  952. rows := make(map[uint64][]byte, len(inputChunk))
  953. br.ResolveMany(im.handle, m.EntryPage, sortedChunk,
  954. func(pkID uint64, storeRow []byte) bool {
  955. rows[pkID] = storeRow
  956. return true
  957. })
  958. for _, pkID := range inputChunk { // 本批输入子段, 原序交付
  959. storeRow, ok := rows[pkID]
  960. if !ok {
  961. continue // 行已被删除
  962. }
  963. if !sink(pkID, storeRow) {
  964. return
  965. }
  966. emitted++
  967. if budget > 0 && emitted >= budget {
  968. return
  969. }
  970. }
  971. start = end
  972. }
  973. }
  974. // InvalidateRowCache 使回表定位缓存失效 (外部直接改动行存储索引后调用)
  975. func (im *IndexMetaPage) InvalidateRowCache() { im.rowCache = nil }
  976. // rowStoreHasPK 判断行存储里是否已存在该 pkID —— 不建立/复用回表定位缓存。
  977. //
  978. // 插入查重专用: ResolveByPK 会按需构建 im.rowCache (O(页数)), 而任何一次插入
  979. // 都会让它失效; 若每行插入都走 ResolveByPK, 逐行插入就退化成 O(N²)
  980. // (千万级写入从分钟级变成不可完成)。这里传 cache=nil 走处理器自己的定位路径。
  981. //
  982. // 语义与 ResolveByPK 一致 (仅看行是否存在, 且过滤墓碑页), 只是不做缓存。
  983. func (im *IndexMetaPage) rowStoreHasPK(pkID uint64) bool {
  984. m, h, ok := im.rowStoreMeta()
  985. if !ok || m == nil {
  986. return false
  987. }
  988. _, found := h.ResolveRow(im.handle, m.EntryPage, pkID, nil)
  989. return found
  990. }
  991. // DeleteByKey 通过索引删除一行 —— 统一入口, 不区分索引类型。
  992. //
  993. // storeRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  994. // 重复树查找 —— 否则删除路径会对同一行做两次 handler.Search。
  995. //
  996. // 步骤:
  997. // 1. 复用调用方已定位的整行 (二级索引所缺的行数据已由处理器内部回表补齐)。
  998. // 2. 遍历全部索引, 让每个处理器各自删掉该行在本索引中的条目,
  999. // pkID 一并传入以便二级索引在重复 key 下精确定位。
  1000. // 主键 <-> 二级索引的级联因此天然成立, 无需在本层判断索引类型。
  1001. func (im *IndexMetaPage) DeleteByKey(idxName string, indexKey []byte, storeRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  1002. im.rowCache = nil // 删除可能释放 DataPage, 失效缓存
  1003. meta := im.findIndexMetaByName(idxName)
  1004. if meta == nil || len(storeRow) < 8 {
  1005. return page.ErrNotFound
  1006. }
  1007. pkID := binary.BigEndian.Uint64(storeRow[:8])
  1008. rowData := storeRow[8:]
  1009. // 每个索引各自删除该行在本索引中的条目
  1010. var firstErr error
  1011. for _, ri := range im.resolveIndexes() {
  1012. m := ri.meta
  1013. key := extractIndexKey(rowData, m, fields, versions)
  1014. if indexStoresRows(m) {
  1015. key = rowStoreInsertKey(m, pkID, rowData, fields, versions)
  1016. }
  1017. if len(key) == 0 {
  1018. if m != meta {
  1019. continue // 取不到行数据, 无法推导该索引键, 跳过
  1020. }
  1021. key = indexKey // 目标索引: 调用方给的键就是它在索引里的键
  1022. }
  1023. entryPage := m.EntryPage
  1024. if err := ri.handler.Delete(im.handle, &entryPage, key, pkID, len(key)); err != nil {
  1025. if err != page.ErrNotFound && firstErr == nil {
  1026. firstErr = err
  1027. }
  1028. }
  1029. if entryPage != m.EntryPage {
  1030. m.EntryPage = entryPage
  1031. im.saveMeta(m)
  1032. }
  1033. }
  1034. return firstErr
  1035. }
  1036. // UpdateByKey 通过索引更新一行 —— 统一入口, 不区分索引类型。
  1037. //
  1038. // oldStoreRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  1039. // 重复回表 —— 否则更新路径会先 ResolveByPK, 失败还会再 SearchByIndex。
  1040. //
  1041. // 每个索引各自处理自己条目的更新:
  1042. // - 行存储索引(主键/时序): 就地改写行数据;
  1043. // - 二级索引: 只有 [key + pkID], 键未变则无需动作, 键变了则按 (旧键, pkID) 删旧插新。
  1044. //
  1045. // 本层只负责"把新行数据 + 新旧键交给处理器", 不判断索引类型。
  1046. func (im *IndexMetaPage) UpdateByKey(indexKey []byte, newRowData []byte, pkID uint64, oldStoreRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  1047. // 取旧行数据: 二级索引需要用旧行提取"旧键", 才能精确删掉旧条目
  1048. var oldRowData []byte
  1049. if len(oldStoreRow) > 8 {
  1050. oldRowData = oldStoreRow[8:]
  1051. }
  1052. indexes := im.resolveIndexes()
  1053. // 唯一约束预检: 新键若已被"别的行"占着, 必须在任何写入之前拒绝。
  1054. // 放在循环里逐个索引边查边更新是不行的 —— 撞车时前面几个索引已经改好了,
  1055. // 旧键已删、新键已插, 而函数返回错误, 调用方以为没更新, 数据却变了一半。
  1056. // (Table 的更新路径还会在 serializeRow 之前先调一次, 以免旧行的动态堆块被
  1057. // 试改 —— 这里这次是给"直接调 UpdateByKey 的调用方"兜底。)
  1058. if err := im.checkUniqueForKey(indexes, newRowData, pkID, fields, activeVer, versions); err != nil {
  1059. return err
  1060. }
  1061. // 逐个索引更新本行的条目
  1062. var firstErr error
  1063. for _, ri := range indexes {
  1064. m := ri.meta
  1065. // 定位键: 能用旧行提取就用旧键; 提取不到时退回调用方给的查找键
  1066. // (主键/时序索引的查找键即其在索引中的键, 与既有行为一致)
  1067. oldKey := indexKey
  1068. oldKeyFromRow := false
  1069. if oldRowData != nil {
  1070. if indexStoresRows(m) {
  1071. if k := rowStoreInsertKey(m, pkID, oldRowData, fields, versions); len(k) > 0 {
  1072. oldKey, oldKeyFromRow = k, true
  1073. }
  1074. } else if k := extractIndexKey(oldRowData, m, fields, versions); len(k) > 0 {
  1075. oldKey, oldKeyFromRow = k, true
  1076. }
  1077. }
  1078. newKey := extractIndexKey(newRowData, m, fields, versions)
  1079. if indexStoresRows(m) {
  1080. newKey = rowStoreInsertKey(m, pkID, newRowData, fields, versions)
  1081. }
  1082. entryPage := m.EntryPage
  1083. var err error
  1084. switch {
  1085. case len(newKey) > 0:
  1086. err = ri.handler.Update(im.handle, &entryPage, oldKey, newKey, newRowData, pkID, len(newKey))
  1087. case oldRowData == nil:
  1088. // 行存储索引 (主键/时序): 键取不到也必须改写行数据, 用旧键原地更新
  1089. err = ri.handler.Update(im.handle, &entryPage, oldKey, oldKey, newRowData, pkID, len(oldKey))
  1090. case oldKeyFromRow:
  1091. // 二级索引且旧行有键、新行取不到键: 该列已变为 NULL (或其版本不再有此列),
  1092. // 这一行已退出本索引 —— 必须删掉旧条目。留下旧条目会让该索引继续命中一个
  1093. // 现在为 NULL 的行, 直接破坏"NULL 不匹配任何比较"的三值语义。
  1094. err = ri.handler.Delete(im.handle, &entryPage, oldKey, pkID, len(oldKey))
  1095. default:
  1096. // 二级索引且新旧都取不到键: 该行本来就不在本索引里, 无需动作。
  1097. }
  1098. if err != nil && err != page.ErrNotFound {
  1099. // 过去这里丢弃了返回值, 导致 schema 演进后更新旧行"静默失败":
  1100. // 对外返回 nil 但数据实际没变。必须向上抛出。
  1101. firstErr = fmt.Errorf("更新索引 %q (pkID=%d) 失败: %w",
  1102. util.BytesToString(m.IndexName[:]), pkID, err)
  1103. }
  1104. if entryPage != m.EntryPage {
  1105. m.EntryPage = entryPage
  1106. im.saveMeta(m)
  1107. }
  1108. if firstErr != nil {
  1109. break
  1110. }
  1111. }
  1112. // 回表定位缓存 (rowCache) 只在"行可能换页"时才失效。
  1113. // PrimaryHandler.Update 的三级策略里, 只有行变长且页内装不下才会换页;
  1114. // 行长不变 / 变短时行仍留在原 DataPage, 缓存依旧有效。
  1115. // 若在这里无条件失效, 逐行更新会把 O(页数) 的缓存全量重建摊到每一行上,
  1116. // 退化成 O(N²) —— 50 万行的批量更新会卡死。
  1117. if oldRowData == nil || len(newRowData) > len(oldRowData) {
  1118. im.rowCache = nil
  1119. }
  1120. return firstErr
  1121. }