index_meta_page.go 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  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. "slices"
  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. 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. func (im *IndexMetaPage) InitHeader(pageID uint32) ([]byte, *page.PageHdr) {
  329. buf := make([]byte, PageSize)
  330. hdr := page.HdrOf(buf)
  331. hdr.Type = page.IndexMetaIndex
  332. hdr.PageID = pageID
  333. hdr.UseSize = uint32(unsafe.Sizeof(*hdr))
  334. im.handle.SavePage(pageID, buf)
  335. return buf, hdr
  336. }
  337. // ======================== 高层索引操作 ========================
  338. // rowStoreMeta 返回承载行数据的一级索引 (主键/时序)。
  339. // 本层不认识索引类型枚举: 谁是"行存储"由处理器是否实现 RowStoreHandler 回答。
  340. func (im *IndexMetaPage) rowStoreMeta() (*IndexMeta, index.RowStoreHandler, bool) {
  341. im.resolveIndexes()
  342. if im.rowIdx < 0 {
  343. return nil, nil, false
  344. }
  345. ri := im.active[im.rowIdx]
  346. return ri.meta, ri.rowStore, true
  347. }
  348. // Name 索引名 (去掉定长补零)。
  349. func (m *IndexMeta) Name() string {
  350. if m == nil {
  351. return ""
  352. }
  353. return util.BytesToString(m.IndexName[:])
  354. }
  355. // Fields 索引分量在表 master registry 中的字段下标 (按建索引时给的顺序)。
  356. // 返回拷贝: 调用方不该拿到会随元数据增删而失效的内存。
  357. func (m *IndexMeta) Fields() []byte {
  358. if m == nil || m.FieldCnt == 0 {
  359. return nil
  360. }
  361. n := int(m.FieldCnt)
  362. if n > len(m.FieldIDs) {
  363. n = len(m.FieldIDs)
  364. }
  365. out := make([]byte, n)
  366. copy(out, m.FieldIDs[:n])
  367. return out
  368. }
  369. // Deleted 索引是否已被软删除 (Flag 最高位)。
  370. func (m *IndexMeta) Deleted() bool {
  371. return m != nil && util.IsHighBitSet(m.Flag)
  372. }
  373. // Unique 该索引是否为唯一索引 (同一索引键只允许一行; NULL 分量不进索引, 不参与约束)。
  374. //
  375. // 这是唯一约束的【权威标记】: 列上的 types.FlagUnique 只是声明, 约束由索引在
  376. // 写入路径上执行 (插入/更新前先按键探一次), 所以判定必须以索引元信息为准。
  377. func (m *IndexMeta) Unique() bool {
  378. return m != nil && m.Flag&IndexFlagUnique != 0
  379. }
  380. // SetUnique 就地设置/取消唯一标记 (仅改内存元信息, 落盘由 saveMeta 负责)。
  381. func (m *IndexMeta) SetUnique(v bool) {
  382. if m == nil {
  383. return
  384. }
  385. if v {
  386. m.Flag |= IndexFlagUnique
  387. } else {
  388. m.Flag &^= IndexFlagUnique
  389. }
  390. }
  391. // GetIndexes 返回本表全部可用索引的元数据拷贝 (按创建顺序, 已删除的跳过)。
  392. //
  393. // 元数据是迁移/导出类工具的入口: 在此之前 core 包外没有任何办法枚举一张表的索引
  394. // (metas 未导出, 只能逐名 FindIndex —— 那要求调用方事先知道所有索引名)。
  395. // 返回拷贝而不是内部指针: metas 会随 CreateIndex/DropIndex 重新分配,
  396. // 外部持有 &metas[i] 只会在下一次建/删索引时变成野指针。
  397. func (im *IndexMetaPage) GetIndexes() []IndexMeta {
  398. im.resolveIndexes()
  399. out := make([]IndexMeta, 0, len(im.active))
  400. for _, ri := range im.active {
  401. out = append(out, *ri.meta)
  402. }
  403. return out
  404. }
  405. // GetRowStoreIndex 返回承载行数据的一级索引 (VRTX 的主键索引 / 时序索引) 的元数据拷贝。
  406. // 第二个返回值为 false 表示这张表没有行存储索引 —— 此时行数据无处安放,
  407. // 调用方应当报错而不是"照常建表".
  408. func (im *IndexMetaPage) GetRowStoreIndex() (IndexMeta, bool) {
  409. m, _, ok := im.rowStoreMeta()
  410. if !ok {
  411. return IndexMeta{}, false
  412. }
  413. return *m, true
  414. }
  415. // resolveIndexes 惰性构建并返回活跃索引缓存 (跳过已删除/无处理器的索引)。
  416. //
  417. // 缓存的 &metas[i] 指针在 metas 发生增删或整体替换后会失效, 因此 CreateIndex /
  418. // DropIndex / Reload / Init 都必须调用 invalidateIndexCache。
  419. func (im *IndexMetaPage) resolveIndexes() []resolvedIndex {
  420. if im.activeResolved {
  421. return im.active
  422. }
  423. im.active = im.active[:0]
  424. im.rowIdx = -1
  425. for i := range im.metas {
  426. m := &im.metas[i]
  427. if m.Flag&0x80 != 0 {
  428. continue
  429. }
  430. h, ok := index.Compose.GetHandler(m.IndexType)
  431. if !ok {
  432. continue
  433. }
  434. rs, _ := h.(index.RowStoreHandler)
  435. if im.rowIdx < 0 && rs != nil {
  436. im.rowIdx = len(im.active)
  437. }
  438. im.active = append(im.active, resolvedIndex{meta: m, handler: h, rowStore: rs})
  439. }
  440. im.activeResolved = true
  441. return im.active
  442. }
  443. // invalidateIndexCache 使活跃索引缓存失效 (元信息增删/重载后必须调用)。
  444. func (im *IndexMetaPage) invalidateIndexCache() {
  445. im.activeResolved = false
  446. im.active = im.active[:0]
  447. im.rowIdx = -1
  448. }
  449. // indexStoresRows 询问索引处理器: 该索引是否承载行数据。
  450. // 实现 index.RowStoreHandler 即承载; 调用方据此判断"行存在哪里",
  451. // 不需要认识具体索引类型 (主键/时序)。
  452. func indexStoresRows(m *IndexMeta) bool {
  453. if m == nil {
  454. return false
  455. }
  456. h, ok := index.Compose.GetHandler(m.IndexType)
  457. if !ok {
  458. return false
  459. }
  460. return handlerStoresRows(h)
  461. }
  462. // handlerStoresRows 该处理器是否承载行数据 (实现 index.RowStoreHandler)。
  463. func handlerStoresRows(h index.IndexHandler) bool {
  464. _, ok := h.(index.RowStoreHandler)
  465. return ok
  466. }
  467. // resolver 提供给索引处理器的回表函数: 二级索引内部据此"去找一级索引"取整行,
  468. // 上层无需参与, 也无需知道哪个索引才是主键。
  469. func (im *IndexMetaPage) resolver() index.RowResolver {
  470. return func(pkID uint64) ([]byte, bool) { return im.ResolveByPK(pkID) }
  471. }
  472. // findIndexMetaByName 按索引名查找活跃索引的元信息 (跳过已删除)。
  473. func (im *IndexMetaPage) findIndexMetaByName(name string) *IndexMeta {
  474. for i := range im.metas {
  475. if util.BytesToString(im.metas[i].IndexName[:]) == name && im.metas[i].Flag&0x80 == 0 {
  476. return &im.metas[i]
  477. }
  478. }
  479. return nil
  480. }
  481. func (im *IndexMetaPage) saveMeta(m *IndexMeta) {
  482. cur := im.hdr.PageID
  483. for cur != 0 {
  484. buf := make([]byte, PageSize)
  485. im.handle.ReadPage(cur, buf)
  486. hdr := page.HdrOf(buf)
  487. // 同 loadMetas: 页类型/自述页号对不上就不是索引元数据页, 不能在这上面按名字覆盖元数据
  488. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  489. break
  490. }
  491. offset := im.hdrSize
  492. for offset+im.metaSize <= hdr.UseSize {
  493. dst := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  494. if util.BytesToString(dst.IndexName[:]) == util.BytesToString(m.IndexName[:]) {
  495. *dst = *m
  496. im.handle.SavePage(cur, buf)
  497. return
  498. }
  499. offset += im.metaSize
  500. }
  501. cur = hdr.NextPage
  502. }
  503. }
  504. // rowStoreInsertKey 行存储索引的定位键:
  505. //
  506. // - 主键 B+ 树: 恒为 pkID(BE)。查询/删除/更新侧一律按 pkID 定位, 与镜像列
  507. // 的存储编码无关 —— 旧实现统一走 extractIndexKey (取镜像列字节), 镜像列是
  508. // Int64 等带 sort-key 编码的类型时, 树键与 pkID-BE 不一致: 按 pkID 查找
  509. // 退化成全叶扫描, 删除/更新按错键静默失效 (ErrNotFound 按索引逐个吞掉)。
  510. // Uint64 镜像因"存储字节==BE"而侥幸正确, 掩盖了这条错位。
  511. // - 时序索引: 键 = 时间分量 (extractIndexKey); 其顺序断言/分段路由都在同一
  512. // 编码内自洽 (sort-key 保序), 维持原样。
  513. //
  514. // 插入/删除/更新三侧必须用同一键源, 否则写进树里的键和去树里找的键对不上。
  515. func rowStoreInsertKey(meta *IndexMeta, pkID uint64, rowData []byte, fields []types.FieldInfo, versions []SchemaVersion) []byte {
  516. if meta.IndexType == page.IndexTypePrimary {
  517. key := make([]byte, 8)
  518. binary.BigEndian.PutUint64(key, pkID)
  519. return key
  520. }
  521. return extractIndexKey(rowData, meta, fields, versions)
  522. }
  523. // extractIndexKey 从行数据中提取索引键 (版本感知)
  524. // rowData 格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  525. //
  526. // 复合索引的键 = 按 FieldIDs 顺序, 把各分量字段的原始字节【补齐到档位宽度】后定长拼接。
  527. // - 顺序取 FieldIDs 而非行内物理顺序 —— 查询侧据此才能做"列前缀"范围推导:
  528. // (a, b) 上的索引既能服务 a=?, 也能服务 a=? AND b>?;
  529. // - 每个分量长度 = FieldSize(字段类型, 列级参数), 与查询侧编键完全一致, 因此拼接键是
  530. // 定长的, 字节序比较等价于"先比 a 再比 b"的字典序。宽度必须带列级参数 (字符串列的
  531. // 宽度码): 按类型默认档切键会把宽字符串列切成半截, 后面那个分量整体读错位;
  532. // - v9 起行内字符串列按【实际长度】存储, 取出的字节要 PadToWidth 补零到档位宽度 ——
  533. // 索引键必须保持定长, 二级索引布局 [key][PKID8] 与 idxKeyLenOf 才不会变形;
  534. // - 任一分量在该行版本里取不到 (列被 Drop / schema 版本不匹配) → 返回 nil,
  535. // 该索引跳过这一行, 与单列索引"取不到键就跳过"的既有语义一致。
  536. func extractIndexKey(rowData []byte, meta *IndexMeta, fields []types.FieldInfo, versions []SchemaVersion) []byte {
  537. if meta == nil || meta.FieldCnt == 0 || len(rowData) < 1 {
  538. return nil
  539. }
  540. n := int(meta.FieldCnt)
  541. if n > len(meta.FieldIDs) {
  542. return nil
  543. }
  544. // 从 rowData 读取该行的 schema 版本; 列位置由行内偏移表给出 —— v9 字符串列变长,
  545. // 旧实现"从偏移 1 起累加档位宽度"必然错位。
  546. layout, ok := parseRowLayoutOf(fields, versions, rowData, rowData[0])
  547. if !ok {
  548. return nil
  549. }
  550. sv := layout.sv
  551. rowN := len(sv.FieldIndices)
  552. if rowN == 0 {
  553. return nil
  554. }
  555. col0Width := layout.col0Width
  556. if col0Width <= 0 {
  557. return nil
  558. }
  559. ends := layout.ends
  560. // seg[k] 指向拼键用的定长字节切片 (补齐后的副本或原位切片), 全部取齐后再一次性拼键。
  561. var (
  562. seg [16][]byte
  563. need = n
  564. )
  565. for i, idx := range sv.FieldIndices {
  566. fi := int(idx)
  567. if fi >= len(fields) {
  568. return nil
  569. }
  570. size := int(fields[fi].Width())
  571. if size == 0 { // 未知类型/非法列参数: 切不出定长分量, 拼出的键会错位
  572. return nil
  573. }
  574. for k := 0; k < n; k++ {
  575. if int(meta.FieldIDs[k]) != fi {
  576. continue
  577. }
  578. // 分量是 NULL → 该行不进本索引 (键里放不下"缺失"这一档, 上层按 nil 键跳过)。
  579. if types.RowIsNull(rowData, ends, rowN, i) {
  580. return nil
  581. }
  582. start := int(types.RowFieldStart(ends, i))
  583. end := int(types.RowFieldEnd(ends, i, col0Width))
  584. if end < start || end > len(rowData) {
  585. return nil
  586. }
  587. // 行内存的是实际长度, 索引键要的是档位定长: 补零对齐, 与查询侧 EncodeValue 同源。
  588. seg[k] = types.PadToWidth(rowData[start:end], uint16(size))
  589. need--
  590. break
  591. }
  592. if need == 0 {
  593. break // 分量已取齐, 后面的字段与本索引无关
  594. }
  595. }
  596. if need != 0 {
  597. return nil // 有分量不在该版本中 (如该列已被 Drop), 无法构成完整键
  598. }
  599. total := 0
  600. for k := 0; k < n; k++ {
  601. total += len(seg[k])
  602. }
  603. key := make([]byte, 0, total)
  604. for k := 0; k < n; k++ {
  605. key = append(key, seg[k]...)
  606. }
  607. return key
  608. }
  609. // InsertRow 插入行到所有索引
  610. func (im *IndexMetaPage) InsertRow(fields []types.FieldInfo, pkID uint64, rowData []byte, activeVer byte, versions []SchemaVersion) error {
  611. im.rowCache = nil // 插入可能创建新 DataPage, 失效缓存
  612. indexes := im.resolveIndexes()
  613. // 唯一约束预检必须先于任何写入: 逐个索引边查边写的话, 撞车时前面几个索引
  614. // (含承载行数据的行存储索引) 已经写进去了, 只剩一个错误返回 —— 库里多出一行
  615. // 半成品, 而且它的行还在、二级条目不全, 之后按任何二级索引都查不到它。
  616. if err := im.checkUniqueForKey(indexes, rowData, pkID, fields, activeVer, versions); err != nil {
  617. return err
  618. }
  619. for _, ri := range indexes {
  620. m := ri.meta
  621. indexKey := extractIndexKey(rowData, m, fields, versions)
  622. if indexStoresRows(m) {
  623. indexKey = rowStoreInsertKey(m, pkID, rowData, fields, versions)
  624. }
  625. if indexKey == nil {
  626. continue
  627. }
  628. entryPage := m.EntryPage
  629. _, err := ri.handler.Insert(im.handle, &entryPage, indexKey, pkID, rowData, len(indexKey))
  630. if err != nil {
  631. return err
  632. }
  633. if entryPage != m.EntryPage {
  634. m.EntryPage = entryPage
  635. im.saveMeta(m)
  636. }
  637. }
  638. return nil
  639. }
  640. // InsertRows 批量插入: pkIDs[i] 与 rowDatas[i] 一一对应。
  641. //
  642. // 与单行 InsertRow 的语义差异只在"摊销", 不在"语义":
  643. // - 唯一约束仍是"先全部预检、再动任何写入"—— 每行对既有树的查重照做,
  644. // 另补"批内查重"(单行路径里批内冲突会在第 i+1 行的树查重中被发现, 批量
  645. // 路径一次性排序比出来, 语义等价且更便宜)。
  646. // - 实现了 BatchInserter 的索引 (主键 B+ 树 / 二级 B+ 树 / 复合索引) 走批量路径;
  647. // 其余 (时序 / hash) 退化为逐条 Insert, 行为与单行路径逐字节一致。
  648. //
  649. // 失败语义与单行路径相同: 中途报错时前面已写入的索引不回滚 (调用方靠事务回滚)。
  650. func (im *IndexMetaPage) InsertRows(fields []types.FieldInfo, pkIDs []uint64, rowDatas [][]byte, activeVer byte, versions []SchemaVersion) error {
  651. if len(rowDatas) == 0 {
  652. return nil
  653. }
  654. im.rowCache = nil // 批量插入必然新建 DataPage, 一次性失效缓存
  655. indexes := im.resolveIndexes()
  656. // 唯一约束预检: 全部查完才动第一笔写入 (与单行路径同一铁律)
  657. hasUnique := false
  658. for _, ri := range indexes {
  659. if ri.meta.Unique() {
  660. hasUnique = true
  661. break
  662. }
  663. }
  664. if hasUnique {
  665. for i := range rowDatas {
  666. if err := im.checkUniqueForKey(indexes, rowDatas[i], pkIDs[i], fields, activeVer, versions); err != nil {
  667. return err
  668. }
  669. }
  670. // 批内查重: 同一唯一索引里两行同键
  671. for _, ri := range indexes {
  672. if !ri.meta.Unique() {
  673. continue
  674. }
  675. keys := make([][]byte, 0, len(rowDatas))
  676. for i := range rowDatas {
  677. if k := extractIndexKey(rowDatas[i], ri.meta, fields, versions); k != nil {
  678. keys = append(keys, k)
  679. }
  680. }
  681. slices.SortFunc(keys, bytes.Compare)
  682. for j := 1; j < len(keys); j++ {
  683. if bytes.Equal(keys[j-1], keys[j]) {
  684. return fmt.Errorf("%w: 索引 %q, 批内重复键",
  685. ErrDuplicatedKey, util.BytesToString(ri.meta.IndexName[:]))
  686. }
  687. }
  688. }
  689. }
  690. for _, ri := range indexes {
  691. m := ri.meta
  692. entryPage := m.EntryPage
  693. var err error
  694. if bi, ok := ri.handler.(index.BatchInserter); ok {
  695. rows := make([]index.BatchRow, len(rowDatas))
  696. for i := range rowDatas {
  697. var ik []byte
  698. if indexStoresRows(m) {
  699. ik = rowStoreInsertKey(m, pkIDs[i], rowDatas[i], fields, versions)
  700. } else {
  701. ik = extractIndexKey(rowDatas[i], m, fields, versions)
  702. }
  703. rows[i] = index.BatchRow{
  704. PKID: pkIDs[i],
  705. IndexKey: ik,
  706. RowData: rowDatas[i],
  707. }
  708. }
  709. err = bi.InsertBatch(im.handle, &entryPage, rows)
  710. } else {
  711. for i := range rowDatas {
  712. indexKey := extractIndexKey(rowDatas[i], m, fields, versions)
  713. if indexStoresRows(m) {
  714. indexKey = rowStoreInsertKey(m, pkIDs[i], rowDatas[i], fields, versions)
  715. }
  716. if indexKey == nil {
  717. continue
  718. }
  719. if _, err = ri.handler.Insert(im.handle, &entryPage, indexKey, pkIDs[i], rowDatas[i], len(indexKey)); err != nil {
  720. break
  721. }
  722. }
  723. }
  724. if err != nil {
  725. return err
  726. }
  727. if entryPage != m.EntryPage {
  728. m.EntryPage = entryPage
  729. im.saveMeta(m)
  730. }
  731. }
  732. return nil
  733. }
  734. // CheckUniqueForKey 校验行数据 rowData 是否会撞上某个唯一索引里【别的行】已有的键。
  735. // 冲突返回 ErrDuplicatedKey; 无冲突 (含"该行压根不进这些索引") 返回 nil。
  736. //
  737. // 抽成独立方法是因为它有两个必须分开的调用时机:
  738. // - InsertRow / UpdateByKey 内部各调一次 —— 保证任何入口在写入前都拦得住;
  739. // - Table 的更新路径还要在 serializeRow 之前【再】调一次: serializeRow 会就地
  740. // 覆写旧行的动态堆块, 冲突必须在它动手之前发现 (见 serializeRowForCheck)。
  741. //
  742. // 因此这里只读不写、可重复调用。
  743. func (im *IndexMetaPage) CheckUniqueForKey(rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  744. return im.checkUniqueForKey(im.resolveIndexes(), rowData, pkID, fields, activeVer, versions)
  745. }
  746. // HasUniqueIndex 是否存在活跃的唯一索引。
  747. //
  748. // 更新路径 (checkUniqueUpdate) 用它跳过 serializeRowForCheck: 预检的唯一目的是在
  749. // serializeRow 动手覆写旧行动态堆块之前拦下唯一冲突 —— 没有唯一索引时整行试序列化
  750. // 纯属浪费一次全行序列化。resolveIndexes 的结果有缓存, 本判断近乎零成本。
  751. func (im *IndexMetaPage) HasUniqueIndex() bool {
  752. for _, ri := range im.resolveIndexes() {
  753. if ri.meta.Unique() {
  754. return true
  755. }
  756. }
  757. return false
  758. }
  759. // checkUniqueForKey 是 CheckUniqueForKey 的内核, 收【已解析】的索引集。
  760. // InsertRow / UpdateByKey 手里本来就有一份 resolveIndexes 的结果, 再解析一遍等于
  761. // 每次写入都把索引元信息链多走一趟 —— 插入/更新是热路径, 这趟省掉。
  762. func (im *IndexMetaPage) checkUniqueForKey(indexes []resolvedIndex, rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  763. for _, ri := range indexes {
  764. if !ri.meta.Unique() {
  765. continue
  766. }
  767. indexKey := extractIndexKey(rowData, ri.meta, fields, versions)
  768. if indexKey == nil {
  769. continue // 任一分量为 NULL → 该行不进本索引, 唯一性对 NULL 不设限
  770. }
  771. if im.uniqueConflict(ri, indexKey, pkID) {
  772. return fmt.Errorf("%w: 索引 %q, pkID=%d",
  773. ErrDuplicatedKey, util.BytesToString(ri.meta.IndexName[:]), pkID)
  774. }
  775. }
  776. return nil
  777. }
  778. // uniqueConflict 判断唯一索引里是否已有"别的行"占着这个键。
  779. //
  780. // 用 resolve=nil 直接拿条目里的 8B pkID: 唯一性只关心"是不是同一行", 不需要回表取整行
  781. // (回表会顺着行存储索引再查一次, 每次插入都多一遍 O(页数) 的定位)。返回的 pkID 与
  782. // 当前行相同说明命中的是本行自己的条目 (更新场景), 不算冲突。
  783. func (im *IndexMetaPage) uniqueConflict(ri resolvedIndex, indexKey []byte, pkID uint64) bool {
  784. got, found := ri.handler.Search(im.handle, ri.meta.EntryPage, indexKey, len(indexKey), nil)
  785. if !found || len(got) < 8 {
  786. return false
  787. }
  788. return binary.BigEndian.Uint64(got[:8]) != pkID
  789. }
  790. // searchAt 在已定位的索引元信息上查一行 (统一契约: 返回整行, 带 8B pkID 前缀)。
  791. //
  792. // 本层不判断索引类型: 二级索引所缺的行数据由处理器内部用回表函数补齐。
  793. // 独立抽出是为了让"先定位、再复用"的调用方 (Table.SearchByIndex/DeleteRow/UpdateRow)
  794. // 只做一次树查找, 不必重复「findIndexMetaByName + handler.Search」。
  795. func (im *IndexMetaPage) searchAt(meta *IndexMeta, indexKey []byte) ([]byte, bool) {
  796. if meta == nil {
  797. return nil, false
  798. }
  799. handler, ok := index.Compose.GetHandler(meta.IndexType)
  800. if !ok {
  801. return nil, false
  802. }
  803. return handler.Search(im.handle, meta.EntryPage, indexKey, len(indexKey), im.resolver())
  804. }
  805. // SearchByIndex 通过索引搜索 (统一契约: 返回整行, 带 8B pkID 前缀)
  806. func (im *IndexMetaPage) SearchByIndex(idxName string, indexKey []byte) ([]byte, bool) {
  807. return im.searchAt(im.findIndexMetaByName(idxName), indexKey)
  808. }
  809. // ResolveByPK 按 pkID 查找整行数据 (带 8B pkID 前缀)。
  810. //
  811. // 本层不知道行数据"存在哪种索引里": 由实现 RowStoreHandler 的处理器自己读取
  812. // (主键 B+ 树走 DataPage 二分缓存, 时序索引走条目链)。
  813. func (im *IndexMetaPage) ResolveByPK(pkID uint64) ([]byte, bool) {
  814. m, h, ok := im.rowStoreMeta()
  815. if !ok {
  816. return nil, false
  817. }
  818. return h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache)
  819. }
  820. // BatchResolveByPK 批量回表, 结果按【传入顺序】交付 (sink 回调)。
  821. //
  822. // 传入顺序即二级索引 RangeScan 的输出序 —— B+ 树按索引键序输出、Hash 范围扫描
  823. // 内部也按键排序; needSort=false 的免排序 ORDER BY 路径依赖这个顺序不被打乱,
  824. // 所以内部排序只是为了游标走查, 交付时恢复传入顺序。
  825. //
  826. // budget > 0 时最多交付 budget 行 (LIMIT+OFFSET 场景): 批尺寸随剩余预算收缩,
  827. // 达到预算立即收工 —— Where(重键=值).First() 这类点查只解 1 行, 而不是把
  828. // 命中桶里的上千行全部解出再丢掉。剩余条件会过滤行的场景: 某批交付不足预算时
  829. // 继续下一批, 直到预算耗尽或输入走完。
  830. //
  831. // 实现: 输入的升序拷贝走单调游标 (BatchRowResolver) 收进 map, 再按原序回调;
  832. // 未实现批量接口的处理器退化为逐个 ResolveRow (按传入顺序, 与旧行为一致)。
  833. // 行快照跨 sink 调用有效。sink 返回 false 时提前停止。
  834. func (im *IndexMetaPage) BatchResolveByPK(pkIDs []uint64, budget int,
  835. sink func(pkID uint64, storeRow []byte) bool) {
  836. m, h, ok := im.rowStoreMeta()
  837. if !ok || len(pkIDs) == 0 {
  838. return
  839. }
  840. emitted := 0
  841. br, isBatch := h.(index.BatchRowResolver)
  842. if !isBatch {
  843. for _, pkID := range pkIDs {
  844. storeRow, found := h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache)
  845. if !found {
  846. continue // 行已被删除, 跳过
  847. }
  848. if !sink(pkID, storeRow) {
  849. return
  850. }
  851. emitted++
  852. if budget > 0 && emitted >= budget {
  853. return
  854. }
  855. }
  856. return
  857. }
  858. // 分批: 每批取输入的一个连续子段, 子段单独升序后走游标走查,
  859. // 交付严格按该子段的输入顺序。批与批之间游标重启, 回调里的索引改写安全。
  860. for start := 0; start < len(pkIDs); {
  861. if budget > 0 && budget-emitted <= 0 {
  862. return
  863. }
  864. chunk := resolveChunk
  865. if budget > 0 && budget-emitted < chunk {
  866. chunk = budget - emitted // 预算紧张时批随预算收缩 (点查退化为单行解)
  867. }
  868. end := start + chunk
  869. if end > len(pkIDs) {
  870. end = len(pkIDs)
  871. }
  872. inputChunk := pkIDs[start:end]
  873. if len(inputChunk) == 1 {
  874. // 点查快路径 (LIMIT=1/First 常见): 单行直解, 免排序拷贝与 map
  875. pkID := inputChunk[0]
  876. if storeRow, found := h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache); found {
  877. if !sink(pkID, storeRow) {
  878. return
  879. }
  880. emitted++
  881. }
  882. start = end
  883. continue
  884. }
  885. sortedChunk := make([]uint64, len(inputChunk))
  886. copy(sortedChunk, inputChunk)
  887. slices.Sort(sortedChunk)
  888. rows := make(map[uint64][]byte, len(inputChunk))
  889. br.ResolveMany(im.handle, m.EntryPage, sortedChunk,
  890. func(pkID uint64, storeRow []byte) bool {
  891. rows[pkID] = storeRow
  892. return true
  893. })
  894. for _, pkID := range inputChunk { // 本批输入子段, 原序交付
  895. storeRow, ok := rows[pkID]
  896. if !ok {
  897. continue // 行已被删除
  898. }
  899. if !sink(pkID, storeRow) {
  900. return
  901. }
  902. emitted++
  903. if budget > 0 && emitted >= budget {
  904. return
  905. }
  906. }
  907. start = end
  908. }
  909. }
  910. // InvalidateRowCache 使回表定位缓存失效 (外部直接改动行存储索引后调用)
  911. func (im *IndexMetaPage) InvalidateRowCache() { im.rowCache = nil }
  912. // rowStoreHasPK 判断行存储里是否已存在该 pkID —— 不建立/复用回表定位缓存。
  913. //
  914. // 插入查重专用: ResolveByPK 会按需构建 im.rowCache (O(页数)), 而任何一次插入
  915. // 都会让它失效; 若每行插入都走 ResolveByPK, 逐行插入就退化成 O(N²)
  916. // (千万级写入从分钟级变成不可完成)。这里传 cache=nil 走处理器自己的定位路径。
  917. //
  918. // 语义与 ResolveByPK 一致 (仅看行是否存在, 且过滤墓碑页), 只是不做缓存。
  919. func (im *IndexMetaPage) rowStoreHasPK(pkID uint64) bool {
  920. m, h, ok := im.rowStoreMeta()
  921. if !ok || m == nil {
  922. return false
  923. }
  924. _, found := h.ResolveRow(im.handle, m.EntryPage, pkID, nil)
  925. return found
  926. }
  927. // DeleteByKey 通过索引删除一行 —— 统一入口, 不区分索引类型。
  928. //
  929. // storeRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  930. // 重复树查找 —— 否则删除路径会对同一行做两次 handler.Search。
  931. //
  932. // 步骤:
  933. // 1. 复用调用方已定位的整行 (二级索引所缺的行数据已由处理器内部回表补齐)。
  934. // 2. 遍历全部索引, 让每个处理器各自删掉该行在本索引中的条目,
  935. // pkID 一并传入以便二级索引在重复 key 下精确定位。
  936. // 主键 <-> 二级索引的级联因此天然成立, 无需在本层判断索引类型。
  937. func (im *IndexMetaPage) DeleteByKey(idxName string, indexKey []byte, storeRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  938. im.rowCache = nil // 删除可能释放 DataPage, 失效缓存
  939. meta := im.findIndexMetaByName(idxName)
  940. if meta == nil || len(storeRow) < 8 {
  941. return page.ErrNotFound
  942. }
  943. pkID := binary.BigEndian.Uint64(storeRow[:8])
  944. rowData := storeRow[8:]
  945. // 每个索引各自删除该行在本索引中的条目
  946. var firstErr error
  947. for _, ri := range im.resolveIndexes() {
  948. m := ri.meta
  949. key := extractIndexKey(rowData, m, fields, versions)
  950. if indexStoresRows(m) {
  951. key = rowStoreInsertKey(m, pkID, rowData, fields, versions)
  952. }
  953. if len(key) == 0 {
  954. if m != meta {
  955. continue // 取不到行数据, 无法推导该索引键, 跳过
  956. }
  957. key = indexKey // 目标索引: 调用方给的键就是它在索引里的键
  958. }
  959. entryPage := m.EntryPage
  960. if err := ri.handler.Delete(im.handle, &entryPage, key, pkID, len(key)); err != nil {
  961. if err != page.ErrNotFound && firstErr == nil {
  962. firstErr = err
  963. }
  964. }
  965. if entryPage != m.EntryPage {
  966. m.EntryPage = entryPage
  967. im.saveMeta(m)
  968. }
  969. }
  970. return firstErr
  971. }
  972. // UpdateByKey 通过索引更新一行 —— 统一入口, 不区分索引类型。
  973. //
  974. // oldStoreRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  975. // 重复回表 —— 否则更新路径会先 ResolveByPK, 失败还会再 SearchByIndex。
  976. //
  977. // 每个索引各自处理自己条目的更新:
  978. // - 行存储索引(主键/时序): 就地改写行数据;
  979. // - 二级索引: 只有 [key + pkID], 键未变则无需动作, 键变了则按 (旧键, pkID) 删旧插新。
  980. //
  981. // 本层只负责"把新行数据 + 新旧键交给处理器", 不判断索引类型。
  982. func (im *IndexMetaPage) UpdateByKey(indexKey []byte, newRowData []byte, pkID uint64, oldStoreRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  983. // 取旧行数据: 二级索引需要用旧行提取"旧键", 才能精确删掉旧条目
  984. var oldRowData []byte
  985. if len(oldStoreRow) > 8 {
  986. oldRowData = oldStoreRow[8:]
  987. }
  988. indexes := im.resolveIndexes()
  989. // 唯一约束预检: 新键若已被"别的行"占着, 必须在任何写入之前拒绝。
  990. // 放在循环里逐个索引边查边更新是不行的 —— 撞车时前面几个索引已经改好了,
  991. // 旧键已删、新键已插, 而函数返回错误, 调用方以为没更新, 数据却变了一半。
  992. // (Table 的更新路径还会在 serializeRow 之前先调一次, 以免旧行的动态堆块被
  993. // 试改 —— 这里这次是给"直接调 UpdateByKey 的调用方"兜底。)
  994. if err := im.checkUniqueForKey(indexes, newRowData, pkID, fields, activeVer, versions); err != nil {
  995. return err
  996. }
  997. // 逐个索引更新本行的条目
  998. var firstErr error
  999. for _, ri := range indexes {
  1000. m := ri.meta
  1001. // 定位键: 能用旧行提取就用旧键; 提取不到时退回调用方给的查找键
  1002. // (主键/时序索引的查找键即其在索引中的键, 与既有行为一致)
  1003. oldKey := indexKey
  1004. oldKeyFromRow := false
  1005. if oldRowData != nil {
  1006. if indexStoresRows(m) {
  1007. if k := rowStoreInsertKey(m, pkID, oldRowData, fields, versions); len(k) > 0 {
  1008. oldKey, oldKeyFromRow = k, true
  1009. }
  1010. } else if k := extractIndexKey(oldRowData, m, fields, versions); len(k) > 0 {
  1011. oldKey, oldKeyFromRow = k, true
  1012. }
  1013. }
  1014. newKey := extractIndexKey(newRowData, m, fields, versions)
  1015. if indexStoresRows(m) {
  1016. newKey = rowStoreInsertKey(m, pkID, newRowData, fields, versions)
  1017. }
  1018. entryPage := m.EntryPage
  1019. var err error
  1020. switch {
  1021. case len(newKey) > 0:
  1022. err = ri.handler.Update(im.handle, &entryPage, oldKey, newKey, newRowData, pkID, len(newKey))
  1023. case oldRowData == nil:
  1024. // 行存储索引 (主键/时序): 键取不到也必须改写行数据, 用旧键原地更新
  1025. err = ri.handler.Update(im.handle, &entryPage, oldKey, oldKey, newRowData, pkID, len(oldKey))
  1026. case oldKeyFromRow:
  1027. // 二级索引且旧行有键、新行取不到键: 该列已变为 NULL (或其版本不再有此列),
  1028. // 这一行已退出本索引 —— 必须删掉旧条目。留下旧条目会让该索引继续命中一个
  1029. // 现在为 NULL 的行, 直接破坏"NULL 不匹配任何比较"的三值语义。
  1030. err = ri.handler.Delete(im.handle, &entryPage, oldKey, pkID, len(oldKey))
  1031. default:
  1032. // 二级索引且新旧都取不到键: 该行本来就不在本索引里, 无需动作。
  1033. }
  1034. if err != nil && err != page.ErrNotFound {
  1035. // 过去这里丢弃了返回值, 导致 schema 演进后更新旧行"静默失败":
  1036. // 对外返回 nil 但数据实际没变。必须向上抛出。
  1037. firstErr = fmt.Errorf("更新索引 %q (pkID=%d) 失败: %w",
  1038. util.BytesToString(m.IndexName[:]), pkID, err)
  1039. }
  1040. if entryPage != m.EntryPage {
  1041. m.EntryPage = entryPage
  1042. im.saveMeta(m)
  1043. }
  1044. if firstErr != nil {
  1045. break
  1046. }
  1047. }
  1048. // 回表定位缓存 (rowCache) 只在"行可能换页"时才失效。
  1049. // PrimaryHandler.Update 的三级策略里, 只有行变长且页内装不下才会换页;
  1050. // 行长不变 / 变短时行仍留在原 DataPage, 缓存依旧有效。
  1051. // 若在这里无条件失效, 逐行更新会把 O(页数) 的缓存全量重建摊到每一行上,
  1052. // 退化成 O(N²) —— 50 万行的批量更新会卡死。
  1053. if oldRowData == nil || len(newRowData) > len(oldRowData) {
  1054. im.rowCache = nil
  1055. }
  1056. return firstErr
  1057. }