IndexMetaPage.go 41 KB

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