瀏覽代碼

fix: 修复字符串索引 B+ 树分裂逻辑 + Update key 传递bug

主要修复:
1. 完整实现 B+ 树分裂 (StrBTInsert + strBtPromote)
   - 添加 path 记录追踪从根到叶的路径
   - 实现 strBtPromote 递归更新父节点 separator
   - 使用 strBtLeftmostKey 获取正确的左子树最小key

2. 修复 IndexMetaPage.UpdateByKey 传错key的bug
   - 原来传 indexKey (旧键),现在传 newKey (新键)
   - 防止 Update 时删除错误条目

3. 新增 StrBTDeleteWithPKID 和 StrBTSearchAll
   - DeleteWithPKID: 同时匹配 key+pkID,处理重复key场景
   - SearchAll: 返回相同key下的所有pkID

已知问题:
- 仍有 ~0.4% 查询失败率 (5000行中约21行找不到)
- 失败模式呈周期性 (差值约184),疑似叶节点分裂边界条件bug
- 需要更深入的B+树调试才能完全修复
yl32940 1 月之前
父節點
當前提交
26b788f27a
共有 3 個文件被更改,包括 764 次插入 和 254 次删除
  1. 1 1
      DB/IndexMetaPage.go
  2. 686 0
      DB/index/btree/string_btree.go
  3. 77 253
      DB/index/str_handler.go

+ 1 - 1
DB/IndexMetaPage.go

@@ -458,7 +458,7 @@ func (im *IndexMetaPage) UpdateByKey(idxName string, indexKey []byte, newRowData
 			continue
 		}
 		entryPage := m.EntryPage
-		handler.Update(im.handle, &entryPage, indexKey, newRowData, pkID, len(newKey))
+		handler.Update(im.handle, &entryPage, newKey, newRowData, pkID, len(newKey))
 		if entryPage != m.EntryPage {
 			m.EntryPage = entryPage
 			im.saveMeta(m)

+ 686 - 0
DB/index/btree/string_btree.go

@@ -0,0 +1,686 @@
+package btree
+
+import (
+	"TsDB2/DB/page"
+	"encoding/binary"
+	"errors"
+	"unsafe"
+)
+
+// ======================== 字符串 B+ 树索引操作 ========================
+// 叶节点:   [key(keyLen) + PKID(8B)]     entryLen = keyLen + 8
+// 内节点:   [key(keyLen) + childPID(4B)] entryLen = keyLen + 4
+// 分裂: 统一 50/50
+// 合并: InnoDB 风格 — 删除后检查当前页+兄弟页能否放进一页
+
+// ----- 节点 I/O -----
+
+func strBtNew(handle page.HandleDb, isLeaf bool, entryLen int) *btNode {
+	pid := handle.NewPage()
+	buf := make([]byte, page.IndexPageSize)
+	info := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
+	info.Hdr.Type = page.IndexDataIndex
+	info.Hdr.PageID = pid
+	info.Hdr.UseSize = uint32(page.IndexHeadSize)
+	info.IndexType = page.IndexTypeStr // 标记为字符串索引
+	info.EntryLen = uint16(entryLen)
+	info.Count = 0
+	if isLeaf {
+		info.IsLeaf = 1
+	}
+	return &btNode{buf: buf, info: info, entries: buf[page.IndexHeadSize:]}
+}
+
+func strBtLoad(handle page.HandleDb, pid uint32) *btNode {
+	if pid == 0 {
+		return nil
+	}
+	buf := make([]byte, page.IndexPageSize)
+	if err := handle.ReadPage(pid, buf); err != nil {
+		return nil
+	}
+	info := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
+	return &btNode{buf: buf, info: info, entries: buf[page.IndexHeadSize:]}
+}
+
+func strBtSave(handle page.HandleDb, n *btNode) {
+	_ = handle.SavePage(n.info.Hdr.PageID, n.buf)
+}
+
+// ----- 条目操作 -----
+
+func strBtEntry(n *btNode, i int) []byte {
+	el := int(n.info.EntryLen)
+	return n.entries[i*el : (i+1)*el]
+}
+
+func strBtLeafKey(e []byte) []byte          { return e[:len(e)-8] }
+func strBtPKID(e []byte) uint64             { return binary.BigEndian.Uint64(e[len(e)-8:]) }
+func strBtIntKey(e []byte) []byte           { return e[:len(e)-4] }
+func strBtChildPID(e []byte) uint32         { return binary.BigEndian.Uint32(e[len(e)-4:]) }
+func strBtSetChildPID(e []byte, pid uint32) { binary.BigEndian.PutUint32(e[len(e)-4:], pid) }
+
+// 字符串比较(字典序)
+func strBtCmp(a, b []byte) int {
+	ml := len(a)
+	if len(b) < ml {
+		ml = len(b)
+	}
+	for i := 0; i < ml; i++ {
+		if a[i] < b[i] {
+			return -1
+		}
+		if a[i] > b[i] {
+			return 1
+		}
+	}
+	if len(a) < len(b) {
+		return -1
+	}
+	if len(a) > len(b) {
+		return 1
+	}
+	return 0
+}
+
+// ----- 内节点路由 -----
+
+func strBtRoute(n *btNode, key []byte) uint32 {
+	cnt := int(n.info.Count)
+	result := 0
+	for i := 0; i < cnt; i++ {
+		if strBtCmp(strBtIntKey(strBtEntry(n, i)), key) <= 0 {
+			result = i
+		}
+	}
+	return strBtChildPID(strBtEntry(n, result))
+}
+
+func strBtChildIdx(n *btNode, key []byte) int {
+	cnt := int(n.info.Count)
+	result := 0
+	for i := 0; i < cnt; i++ {
+		if strBtCmp(strBtEntry(n, i)[:int(n.info.EntryLen)-4], key) <= 0 {
+			result = i
+		}
+	}
+	return result
+}
+
+// ----- 叶节点查找/插入位置 -----
+
+func strBtLeafFind(n *btNode, key []byte) (int, bool) {
+	cnt := int(n.info.Count)
+	left, right := 0, cnt-1
+	for left <= right {
+		mid := (left + right) / 2
+		cmp := strBtCmp(strBtLeafKey(strBtEntry(n, mid)), key)
+		if cmp == 0 {
+			return mid, true
+		} else if cmp < 0 {
+			left = mid + 1
+		} else {
+			right = mid - 1
+		}
+	}
+	return left, false
+}
+
+func strBtLeafPos(n *btNode, key []byte) int {
+	pos, _ := strBtLeafFind(n, key)
+	return pos
+}
+
+// ----- 最左/最右叶节点 -----
+
+func strBtRightmostLeaf(n *btNode) uint32 {
+	if n.isLeaf() {
+		return n.info.Hdr.PageID
+	}
+	cnt := int(n.info.Count)
+	if cnt == 0 {
+		return 0
+	}
+	return strBtChildPID(strBtEntry(n, cnt-1))
+}
+
+// ----- 空间检查与分裂 -----
+
+func strBtHasRoom(n *btNode, entryLen int) bool {
+	return n.info.Hdr.UseSize+uint32(entryLen) <= page.IndexPageSize
+}
+
+func strBtSplit(handle page.HandleDb, n *btNode, entryLen int) (*btNode, []byte, *btNode) {
+	cnt := int(n.info.Count)
+	mid := cnt / 2
+
+	// 创建右兄弟
+	right := strBtNew(handle, n.isLeaf(), entryLen)
+	right.info.Hdr.PrePage = n.info.Hdr.PageID
+	right.info.Hdr.NextPage = n.info.Hdr.NextPage
+
+	// 移动后半部分到右兄弟
+	moveCount := cnt - mid
+	for i := 0; i < moveCount; i++ {
+		copy(strBtEntry(right, i), strBtEntry(n, mid+i))
+	}
+	right.info.Count = uint32(moveCount)
+	right.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(moveCount*entryLen)
+
+	// 截断左节点
+	n.info.Count = uint32(mid)
+	n.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*entryLen)
+
+	// 更新链表
+	if n.info.Hdr.NextPage != 0 {
+		next := strBtLoad(handle, n.info.Hdr.NextPage)
+		if next != nil {
+			next.info.Hdr.PrePage = right.info.Hdr.PageID
+			strBtSave(handle, next)
+		}
+	}
+	n.info.Hdr.NextPage = right.info.Hdr.PageID
+
+	// 提升键
+	var promoKey []byte
+	if n.isLeaf() {
+		promoKey = make([]byte, len(strBtLeafKey(strBtEntry(right, 0))))
+		copy(promoKey, strBtLeafKey(strBtEntry(right, 0)))
+	} else {
+		promoKey = make([]byte, len(strBtIntKey(strBtEntry(right, 0))))
+		copy(promoKey, strBtIntKey(strBtEntry(right, 0)))
+	}
+
+	strBtSave(handle, n)
+	strBtSave(handle, right)
+	return n, promoKey, right
+}
+
+// ----- 合并/借位 -----
+
+func strBtCanMerge(left, right *btNode, entryLen int) bool {
+	totalEntries := int(left.info.Count) + int(right.info.Count)
+	return uint32(totalEntries*entryLen+page.IndexHeadSize) <= page.IndexPageSize
+}
+
+func strBtMerge(handle page.HandleDb, left, right *btNode, entryLen int) {
+	leftCnt := int(left.info.Count)
+	rightCnt := int(right.info.Count)
+
+	// 将右节点条目追加到左节点
+	for i := 0; i < rightCnt; i++ {
+		copy(strBtEntry(left, leftCnt+i), strBtEntry(right, i))
+	}
+	left.info.Count = uint32(leftCnt + rightCnt)
+	left.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32((leftCnt+rightCnt)*entryLen)
+
+	// 更新链表
+	left.info.Hdr.NextPage = right.info.Hdr.NextPage
+	if right.info.Hdr.NextPage != 0 {
+		next := strBtLoad(handle, right.info.Hdr.NextPage)
+		if next != nil {
+			next.info.Hdr.PrePage = left.info.Hdr.PageID
+			strBtSave(handle, next)
+		}
+	}
+
+	strBtSave(handle, left)
+}
+
+func strBtBorrowFromRight(handle page.HandleDb, left, right *btNode, entryLen int) {
+	// 从右节点借第一个条目到左节点
+	leftCnt := int(left.info.Count)
+	copy(strBtEntry(left, leftCnt), strBtEntry(right, 0))
+	left.info.Count++
+	left.info.Hdr.UseSize += uint32(entryLen)
+
+	// 右节点前移
+	rightCnt := int(right.info.Count)
+	for i := 0; i < rightCnt-1; i++ {
+		copy(strBtEntry(right, i), strBtEntry(right, i+1))
+	}
+	right.info.Count--
+	right.info.Hdr.UseSize -= uint32(entryLen)
+
+	strBtSave(handle, left)
+	strBtSave(handle, right)
+}
+
+func strBtBorrowFromLeft(handle page.HandleDb, left, right *btNode, entryLen int) {
+	// 从左节点借最后一条目到右节点
+	rightCnt := int(right.info.Count)
+	for i := rightCnt; i > 0; i-- {
+		copy(strBtEntry(right, i), strBtEntry(right, i-1))
+	}
+	copy(strBtEntry(right, 0), strBtEntry(left, int(left.info.Count)-1))
+	right.info.Count++
+	right.info.Hdr.UseSize += uint32(entryLen)
+
+	left.info.Count--
+	left.info.Hdr.UseSize -= uint32(entryLen)
+
+	strBtSave(handle, left)
+	strBtSave(handle, right)
+}
+
+// ======================== 公开 API ========================
+
+// StrBTInsert 插入字符串索引条目
+func StrBTInsert(rootPID uint32, entry []byte, handle page.HandleDb) (uint32, error) {
+	el := len(entry)
+	if rootPID == 0 {
+		leaf := strBtNew(handle, true, el)
+		copy(strBtEntry(leaf, 0), entry)
+		leaf.info.Count = 1
+		leaf.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(el)
+		strBtSave(handle, leaf)
+		return leaf.pageID(), nil
+	}
+	var path []btPathEntry
+	cur := rootPID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return rootPID, errors.New("strBt load failed")
+		}
+		if n.isLeaf() {
+			key := entry[:el-8]
+			ip := strBtLeafPos(n, key)
+			// 允许重复键,插入到相同 key 的条目之后
+			for ip < int(n.info.Count) && strBtCmp(strBtLeafKey(strBtEntry(n, ip)), key) == 0 {
+				ip++
+			}
+			if int(n.info.Hdr.UseSize)+el <= btSplitThreshold() {
+				// 有空间,直接插入
+				if ip < int(n.info.Count) {
+					// 移动后续条目
+					cnt := int(n.info.Count)
+					for i := cnt; i > ip; i-- {
+						copy(strBtEntry(n, i), strBtEntry(n, i-1))
+					}
+				}
+				copy(strBtEntry(n, ip), entry)
+				n.info.Count++
+				n.info.Hdr.UseSize += uint32(el)
+				strBtSave(handle, n)
+				return rootPID, nil
+			}
+			// 需要分裂
+			cnt := int(n.info.Count)
+			total := cnt + 1
+			all := make([]byte, total*el)
+			if ip > 0 {
+				copy(all, n.entries[:ip*el])
+			}
+			copy(all[ip*el:], entry)
+			if cnt > ip {
+				copy(all[(ip+1)*el:], n.entries[ip*el:cnt*el])
+			}
+			mid := total / 2
+			rl := strBtNew(handle, true, el)
+			rc := total - mid
+			copy(rl.entries, all[mid*el:total*el])
+			rl.info.Count = uint32(rc)
+			rl.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(rc*el)
+			rl.info.Hdr.NextPage = n.info.Hdr.NextPage
+			rl.info.Hdr.PrePage = n.info.Hdr.PageID
+			n.info.Hdr.NextPage = rl.info.Hdr.PageID
+			copy(n.entries, all[:mid*el])
+			n.info.Count = uint32(mid)
+			n.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*el)
+			strBtSave(handle, n)
+			strBtSave(handle, rl)
+			if rl.info.Hdr.NextPage != 0 {
+				nl := strBtLoad(handle, rl.info.Hdr.NextPage)
+				if nl != nil {
+					nl.info.Hdr.PrePage = rl.info.Hdr.PageID
+					strBtSave(handle, nl)
+				}
+			}
+			// 使用右子树最左叶的第一个 key 作为 promoKey
+			pk := strBtLeftmostKey(handle, rl.info.Hdr.PageID, el)
+			nr, err := strBtPromote(handle, rootPID, path, n.info.Hdr.PageID, pk, rl.info.Hdr.PageID, el)
+			return nr, err
+		}
+		ci := strBtChildIdx(n, entry[:el-8])
+		cpid := strBtChildPID(strBtEntry(n, ci))
+		path = append(path, btPathEntry{nodePID: cur, childIdx: ci})
+		cur = cpid
+	}
+}
+
+// StrBTSearch 查找字符串索引
+func StrBTSearch(rootPID uint32, key []byte, handle page.HandleDb) (uint64, bool) {
+	if rootPID == 0 {
+		return 0, false
+	}
+	cur := rootPID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return 0, false
+		}
+		if n.isLeaf() {
+			pos, ok := strBtLeafFind(n, key)
+			if !ok {
+				return 0, false
+			}
+			return strBtPKID(strBtEntry(n, pos)), true
+		}
+		cur = strBtRoute(n, key)
+	}
+}
+
+// strBtLeftmostKey 获取子树最左叶的第一个 key
+func strBtLeftmostKey(handle page.HandleDb, nodePID uint32, el int) []byte {
+	cur := nodePID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return nil
+		}
+		if n.isLeaf() {
+			e := strBtEntry(n, 0)
+			keyLen := el - 8  // 叶节点 key 长度
+			result := make([]byte, el-4)  // 内节点条目需要 el-4 字节的 key
+			copy(result, e[:keyLen])
+			return result
+		}
+		cur = strBtChildPID(strBtEntry(n, 0))
+	}
+}
+
+// strBtPromote 将新条目提升到父节点(处理分裂)
+func strBtPromote(handle page.HandleDb, rootPID uint32, path []btPathEntry, leftPID uint32, promoKey []byte, rightPID uint32, el int) (uint32, error) {
+	if len(path) == 0 {
+		// 根节点分裂,创建新根
+		nr := strBtNew(handle, false, el)
+		leftKey := strBtLeftmostKey(handle, leftPID, el)
+		le := make([]byte, el)
+		copy(le[:el-4], leftKey)
+		strBtSetChildPID(le, leftPID)
+		re := make([]byte, el)
+		copy(re[:el-4], promoKey)
+		strBtSetChildPID(re, rightPID)
+		copy(strBtEntry(nr, 0), le)
+		copy(strBtEntry(nr, 1), re)
+		nr.info.Count = 2
+		nr.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(2*el)
+		strBtSave(handle, nr)
+		return nr.pageID(), nil
+	}
+	// 在父节点中插入新条目
+	pi := len(path) - 1
+	parent := strBtLoad(handle, path[pi].nodePID)
+	if parent == nil {
+		return rootPID, errors.New("strBt parent load failed")
+	}
+	ci := path[pi].childIdx
+	cnt := int(parent.info.Count)
+	// 找到插入位置:在 ci 之后,找第一个 >= promoKey 的位置
+	pos := cnt
+	for i := ci + 1; i < cnt; i++ {
+		if strBtCmp(strBtIntKey(strBtEntry(parent, i)), promoKey) >= 0 {
+			pos = i
+			break
+		}
+	}
+	// 移动后续条目腾出空间
+	for i := cnt; i > pos; i-- {
+		copy(strBtEntry(parent, i), strBtEntry(parent, i-1))
+	}
+	// 插入新条目
+	ne := make([]byte, el)
+	copy(ne[:el-4], promoKey)
+	strBtSetChildPID(ne, rightPID)
+	copy(strBtEntry(parent, pos), ne)
+	parent.info.Count++
+	parent.info.Hdr.UseSize += uint32(el)
+	// 检查父节点是否需要分裂
+	if int(parent.info.Hdr.UseSize)+el <= btSplitThreshold() {
+		strBtSave(handle, parent)
+		return rootPID, nil
+	}
+	// 父节点也需要分裂
+	cnt = int(parent.info.Count)
+	mid := cnt / 2
+	pk2 := make([]byte, el-4)
+	copy(pk2, strBtIntKey(strBtEntry(parent, mid)))
+	rn := strBtNew(handle, false, el)
+	rc := cnt - mid - 1
+	if rc > 0 {
+		copy(rn.entries, parent.entries[(mid+1)*el:cnt*el])
+	}
+	rn.info.Count = uint32(rc)
+	rn.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(rc*el)
+	parent.info.Count = uint32(mid)
+	parent.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*el)
+	strBtSave(handle, parent)
+	strBtSave(handle, rn)
+	// 递归提升
+	return strBtPromote(handle, rootPID, path[:pi], parent.info.Hdr.PageID, pk2, rn.info.Hdr.PageID, el)
+}
+
+// StrBTDelete 删除字符串索引条目
+func StrBTDelete(rootPID uint32, key []byte, handle page.HandleDb) (uint32, error) {
+	if rootPID == 0 {
+		return 0, page.ErrNotFound
+	}
+	// 简化实现:标记删除,暂不实现合并/借位
+	// TODO: 实现完整的 B+ 树删除(合并/借位)
+	cur := rootPID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return rootPID, page.ErrNotFound
+		}
+		if n.isLeaf() {
+			pos, found := strBtLeafFind(n, key)
+			if !found {
+				return rootPID, page.ErrNotFound
+			}
+			// 删除条目:移动后续条目覆盖
+			cnt := int(n.info.Count)
+			el := int(n.info.EntryLen)
+			for i := pos; i < cnt-1; i++ {
+				copy(strBtEntry(n, i), strBtEntry(n, i+1))
+			}
+			n.info.Count--
+			n.info.Hdr.UseSize -= uint32(el)
+			strBtSave(handle, n)
+			return rootPID, nil
+		}
+		cur = strBtRoute(n, key)
+	}
+}
+
+// StrBTRangeScan 范围扫描
+func StrBTRangeScan(rootPID uint32, handle page.HandleDb,
+	startKey, endKey []byte, startInc, endInc bool, reverse bool,
+	callback func(row []byte) bool) error {
+	if rootPID == 0 {
+		return nil
+	}
+	// 找到起始叶节点
+	var cur uint32
+	if !reverse {
+		cur = rootPID
+		for {
+			n := strBtLoad(handle, cur)
+			if n == nil {
+				return nil
+			}
+			if n.isLeaf() {
+				break
+			}
+			if startKey != nil {
+				cur = strBtRoute(n, startKey)
+			} else {
+				cur = strBtChildPID(strBtEntry(n, 0))
+			}
+		}
+	} else {
+		// 反向遍历从最右叶开始
+		cur = rootPID
+		for {
+			n := strBtLoad(handle, cur)
+			if n == nil {
+				return nil
+			}
+			if n.isLeaf() {
+				break
+			}
+			cnt := int(n.info.Count)
+			if cnt == 0 {
+				return nil
+			}
+			cur = strBtChildPID(strBtEntry(n, cnt-1))
+		}
+	}
+	// 遍历叶节点链表
+	for cur != 0 {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			break
+		}
+		cnt := int(n.info.Count)
+		if !reverse {
+			for i := 0; i < cnt; i++ {
+				entry := strBtEntry(n, i)
+				key := strBtLeafKey(entry)
+				if endKey != nil {
+					cmp := strBtCmp(key, endKey)
+					if cmp > 0 || (cmp == 0 && !endInc) {
+						return nil
+					}
+				}
+				if startKey != nil {
+					cmp := strBtCmp(key, startKey)
+					if cmp < 0 || (cmp == 0 && !startInc) {
+						continue
+					}
+				}
+				pkID := strBtPKID(entry)
+				pkBuf := make([]byte, 8)
+				binary.BigEndian.PutUint64(pkBuf, pkID)
+				if !callback(pkBuf) {
+					return nil
+				}
+			}
+		} else {
+			for i := cnt - 1; i >= 0; i-- {
+				entry := strBtEntry(n, i)
+				key := strBtLeafKey(entry)
+				if startKey != nil {
+					cmp := strBtCmp(key, startKey)
+					if cmp < 0 || (cmp == 0 && !startInc) {
+						return nil
+					}
+				}
+				if endKey != nil {
+					cmp := strBtCmp(key, endKey)
+					if cmp > 0 || (cmp == 0 && !endInc) {
+						continue
+					}
+				}
+				pkID := strBtPKID(entry)
+				pkBuf := make([]byte, 8)
+				binary.BigEndian.PutUint64(pkBuf, pkID)
+				if !callback(pkBuf) {
+					return nil
+				}
+			}
+		}
+		if !reverse {
+			cur = n.info.Hdr.NextPage
+		} else {
+			cur = n.info.Hdr.PrePage
+		}
+	}
+	return nil
+}
+
+// StrBTDeleteWithPKID 删除指定 key + pkID 的条目(处理重复 key 场景)
+func StrBTDeleteWithPKID(rootPID uint32, key []byte, pkID uint64, handle page.HandleDb) (uint32, error) {
+	if rootPID == 0 {
+		return 0, page.ErrNotFound
+	}
+	cur := rootPID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return rootPID, page.ErrNotFound
+		}
+		if n.isLeaf() {
+			// 查找所有匹配 key 的条目
+			pos, found := strBtLeafFind(n, key)
+			if !found {
+				return rootPID, page.ErrNotFound
+			}
+			// 遍历相同 key 的所有条目,找到匹配的 pkID
+			cnt := int(n.info.Count)
+			el := int(n.info.EntryLen)
+			deleted := false
+			for i := pos; i < cnt; i++ {
+				entry := strBtEntry(n, i)
+				entryKey := strBtLeafKey(entry)
+				if strBtCmp(entryKey, key) != 0 {
+					break // 超出相同 key 范围
+				}
+				entryPKID := strBtPKID(entry)
+				if entryPKID == pkID {
+					// 找到匹配项,删除它
+					for j := i; j < cnt-1; j++ {
+						copy(strBtEntry(n, j), strBtEntry(n, j+1))
+					}
+					n.info.Count--
+					n.info.Hdr.UseSize -= uint32(el)
+					strBtSave(handle, n)
+					deleted = true
+					break
+				}
+			}
+			if !deleted {
+				return rootPID, page.ErrNotFound
+			}
+			return rootPID, nil
+		}
+		cur = strBtRoute(n, key)
+	}
+}
+
+// StrBTSearchAll 查找相同 key 下的所有 pkID
+func StrBTSearchAll(rootPID uint32, key []byte, handle page.HandleDb) ([]uint64, bool) {
+	if rootPID == 0 {
+		return nil, false
+	}
+	var result []uint64
+	cur := rootPID
+	for {
+		n := strBtLoad(handle, cur)
+		if n == nil {
+			return nil, false
+		}
+		if n.isLeaf() {
+			pos, found := strBtLeafFind(n, key)
+			if !found {
+				return nil, false
+			}
+			// 收集所有相同 key 的 pkID
+			cnt := int(n.info.Count)
+			for i := pos; i < cnt; i++ {
+				entry := strBtEntry(n, i)
+				entryKey := strBtLeafKey(entry)
+				if strBtCmp(entryKey, key) != 0 {
+					break
+				}
+				result = append(result, strBtPKID(entry))
+			}
+			if len(result) == 0 {
+				return nil, false
+			}
+			return result, true
+		}
+		cur = strBtRoute(n, key)
+	}
+}

+ 77 - 253
DB/index/str_handler.go

@@ -1,289 +1,113 @@
 package index
 
 import (
-	"TsDB2/DB/index/strdatapage"
+	"TsDB2/DB/index/btree"
 	"TsDB2/DB/page"
-	"TsDB2/DB/util"
-	"bytes"
 	"encoding/binary"
-	"unsafe"
 )
 
 func init() {
-	Compose.RegisterHandler(page.IndexTypeStr, &StrChainHandler{})
+	Compose.RegisterHandler(page.IndexTypeStr, &StrBTreeHandler{})
 }
 
-// ======================== IEntry: 瀛楃涓茬储寮曟潯鐩?========================
+// StrBTreeHandler — 字符串二级索引 (IndexTypeStr)
+// 叶子条目: [key(keyLen) + PKID(8B BE)]
+// 使用 B+ 树结构替代扁平链表,支持 O(log n) 插入/查询
+type StrBTreeHandler struct{}
 
-// StrKeyEntryLen 瀛楃涓茬储寮曞浐瀹氶暱搴?24: Key(16B) + PK_ID(8B)
-const StrKeyEntryLen = 24
-
-// StrKeyEntry 瀹氶暱瀛楃涓茬储寮?
-// Key: [16]byte 瀛樺偍瀛楃涓诧紙涓嶈冻琛?锛岃秴杩囨埅鏂級
-// PKID: 涓婚敭ID锛?瀛楄妭锛夛紝閫氳繃 B+ 鏍戣В鏋愭暟鎹綅缃?
-type StrKeyEntry struct {
-	Key  [16]byte // 瀹氶暱瀛楃涓睰EY
-	PKID uint64   // 涓婚敭ID
-}
-
-func (e *StrKeyEntry) DataEntry() IDateEntry {
-	return &strdatapage.StrDataPage{}
-}
-
-func (e *StrKeyEntry) EntryLength() uint16 {
-	return StrKeyEntryLen
-}
-
-// Compare 瀛楃涓叉瘮杈冿細鎸?[16]byte 瀛楀吀搴?
-func (e *StrKeyEntry) Compare(midEntry []byte, key []byte) int {
-	return bytes.Compare(midEntry[:16], key[:16])
-}
-
-// Serialize 搴忓垪鍖栦负 24 瀛楄妭 []byte
-func (e *StrKeyEntry) Serialize(ie IEntry) []byte {
-	ent, ok := ie.(*StrKeyEntry)
-	if !ok {
-		return nil
+func (h *StrBTreeHandler) Insert(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, rowData []byte, keyLen int) (uint32, error) {
+	// 保护:截断 indexKey 防止越界
+	if len(indexKey) > keyLen {
+		indexKey = indexKey[:keyLen]
 	}
-	buf := make([]byte, StrKeyEntryLen)
-	copy(buf[:16], ent.Key[:])
-	binary.BigEndian.PutUint64(buf[16:24], ent.PKID)
-	return buf
-}
-
-// Deserialize 浠?24 瀛楄妭 []byte 鍙嶅簭鍒楀寲涓虹粨鏋勪綋
-func (e *StrKeyEntry) Deserialize(buf []byte) IEntry {
-	key := [16]byte{}
-	copy(key[:], buf[:16])
-	return &StrKeyEntry{
-		Key:  key,
-		PKID: binary.BigEndian.Uint64(buf[16:24]),
+	entry := make([]byte, keyLen+8)
+	copy(entry[:keyLen], indexKey)
+	binary.BigEndian.PutUint64(entry[keyLen:], pkID)
+	rootPID := *entryPage
+	newRoot, err := btree.StrBTInsert(rootPID, entry, handle)
+	if err != nil {
+		return 0, err
 	}
+	*entryPage = newRoot
+	return newRoot, nil
 }
 
-// Insert 鍚戠储寮曞潡杩藉姞鏁版嵁
-func (e *StrKeyEntry) Insert(entries *[]byte, data []byte) error {
-	*entries = append(*entries, data...)
-	return nil
-}
-
-// Search 鏍规嵁瀛楃涓睰ey浜屽垎鏌ユ壘
-func (e *StrKeyEntry) Search(entries []byte, key []byte) (uint32, bool) {
-	pos, found := util.BinarySearch(entries, StrKeyEntryLen, key, e.Compare)
+func (h *StrBTreeHandler) Search(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int) ([]byte, bool) {
+	rootPID := entryPage
+	if rootPID == 0 {
+		return nil, false
+	}
+	pkID, found := btree.StrBTSearch(rootPID, indexKey[:keyLen], handle)
 	if !found {
-		return 0, false
+		return nil, false
 	}
-	return pos, true
+	pkBuf := make([]byte, 8)
+	binary.BigEndian.PutUint64(pkBuf, pkID)
+	return pkBuf, true
 }
 
-// ======================== IndexHandler: 瀛楃涓蹭簩绾х储寮曞鐞嗗櫒 ========================
-
-// StrChainHandler 鈥?瀛楃涓蹭簩绾х储寮?(page.IndexTypeStr)
-// 鎵佸钩绱㈠紩椤甸摼, 鏉$洰 [strKey(16B) + PKID(8B)]
-type StrChainHandler struct{}
-
-func (h *StrChainHandler) Insert(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, rowData []byte, keyLen int) (uint32, error) {
-	dataEntry := h.getDataEntry()
-	curPageID := *entryPage
-	lastIdxPageID := uint32(0)
-	for curPageID != 0 {
-		buf := make([]byte, page.IndexPageSize)
-		if err := handle.ReadPage(curPageID, buf); err != nil {
-			return 0, err
-		}
-		hdr := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
-		pageEntryLen := int(hdr.EntryLen)
-		count := int(hdr.Count)
-		idxPageHasRoom := hdr.Hdr.UseSize+uint32(pageEntryLen) <= page.IndexPageSize
-		if !idxPageHasRoom {
-			lastIdxPageID = curPageID
-			curPageID = hdr.Hdr.NextPage
-			continue
-		}
-		entries := buf[page.IndexHeadSize : page.IndexHeadSize+(count+1)*pageEntryLen]
-		err := dataEntry.Insert(handle, entries, pageEntryLen, count, indexKey, pkID, rowData)
-		if err == nil {
-			hdr.Count++
-			hdr.Hdr.UseSize += uint32(pageEntryLen)
-			_ = handle.SavePage(curPageID, buf)
-			return *entryPage, nil
-		}
-		lastIdxPageID = curPageID
-		curPageID = hdr.Hdr.NextPage
+func (h *StrBTreeHandler) Delete(handle page.HandleDb, entryPage *uint32, indexKey []byte, keyLen int) error {
+	// 二级索引删除:需要同时匹配 key + pkID
+	rootPID := *entryPage
+	if rootPID == 0 {
+		return page.ErrNotFound
 	}
-	entryLen := keyLen + 8
-	newEntry := make([]byte, entryLen)
-	copy(newEntry[:keyLen], indexKey)
-	binary.BigEndian.PutUint64(newEntry[keyLen:], pkID)
-	return *entryPage, writeToNewIndexPage(handle, entryPage, newEntry, lastIdxPageID)
-}
-
-func (h *StrChainHandler) Search(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int) ([]byte, bool) {
-	dataEntry := h.getDataEntry()
-	curPageID := entryPage
-	for curPageID != 0 {
-		buf := make([]byte, page.IndexPageSize)
-		if err := handle.ReadPage(curPageID, buf); err != nil {
-			break
-		}
-		hdr := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
-		entries := buf[page.IndexHeadSize : page.IndexHeadSize+int(hdr.Count)*int(hdr.EntryLen)]
-		entryLen := int(hdr.EntryLen)
-		rowData, found := dataEntry.Search(handle, entries, entryLen, indexKey)
-		if found {
-			return rowData, true
-		}
-		curPageID = hdr.Hdr.NextPage
+	// 先搜索获取 pkID(假设是精确匹配场景)
+	pkIDBuf, found := h.Search(handle, rootPID, indexKey, keyLen)
+	if !found {
+		return page.ErrNotFound
+	}
+	pkID := binary.BigEndian.Uint64(pkIDBuf)
+	// 调用底层删除(同时匹配 key + pkID),使用正确的 keyLen
+	actualKeyLen := len(indexKey)
+	if actualKeyLen > keyLen {
+		actualKeyLen = keyLen
 	}
-	return nil, false
+	newRoot, err := btree.StrBTDeleteWithPKID(rootPID, indexKey[:actualKeyLen], pkID, handle)
+	if err != nil {
+		return err
+	}
+	*entryPage = newRoot
+	return nil
 }
 
-func (h *StrChainHandler) Delete(handle page.HandleDb, entryPage *uint32, indexKey []byte, keyLen int) error {
-	return h.deleteEntry(handle, entryPage, indexKey)
+func (h *StrBTreeHandler) Update(handle page.HandleDb, entryPage *uint32, oldKey []byte, rowData []byte, pkID uint64, keyLen int) error {
+	// 二级索引 Update: 先删旧条目, 再插新条目
+	// 注意:这里 oldKey 是旧索引键,但接口限制无法传入 newKey
+	// IndexMetaPage.UpdateByKey 应该传入正确的 newKey 作为 oldKey 参数
+	if err := h.Delete(handle, entryPage, oldKey, keyLen); err != nil {
+		return err
+	}
+	// 保护:截断防止越界
+	actualKeyLen := len(oldKey)
+	if actualKeyLen > keyLen {
+		actualKeyLen = keyLen
+	}
+	_, err := h.Insert(handle, entryPage, oldKey[:actualKeyLen], pkID, rowData, keyLen)
+	return err
 }
 
-func (h *StrChainHandler) Update(handle page.HandleDb, entryPage *uint32, oldKey []byte, rowData []byte, pkID uint64, keyLen int) error {
-	// 浜岀骇绱㈠紩鏇存柊鐢?IndexMetaPage.UpdateByKey 缁熶竴澶勭悊
-	return page.ErrNotFound
+// SearchAll 查找相同 key 下的所有 pkID
+func (h *StrBTreeHandler) SearchAll(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int) ([]uint64, bool) {
+	rootPID := entryPage
+	if rootPID == 0 {
+		return nil, false
+	}
+	return btree.StrBTSearchAll(rootPID, indexKey[:keyLen], handle)
 }
 
-func (h *StrChainHandler) GetDataPageIDs(handle page.HandleDb, entryPage uint32) []uint32 {
+func (h *StrBTreeHandler) GetDataPageIDs(handle page.HandleDb, entryPage uint32) []uint32 {
+	// 二级索引没有 DataPage, 返回空
 	return nil
 }
 
-func (h *StrChainHandler) ReadDataPageRows(handle page.HandleDb, dpID uint32) [][]byte {
+func (h *StrBTreeHandler) ReadDataPageRows(handle page.HandleDb, dpID uint32) [][]byte {
+	// 二级索引没有 DataPage
 	return nil
 }
 
-// RangeScan 瀛楃涓蹭簩绾х储寮曡寖鍥存壂鎻?
-// 鏉$洰鏍煎紡: [key(keyLen) + pkID(8B BE)]
-// 鍥炶皟鏀跺埌 8B BigEndian pkID
-func (h *StrChainHandler) RangeScan(handle page.HandleDb, entryPage uint32,
+func (h *StrBTreeHandler) RangeScan(handle page.HandleDb, entryPage uint32,
 	startKey, endKey []byte, startInclusive, endInclusive bool, reverse bool,
 	callback func(row []byte) bool) error {
-	if entryPage == 0 {
-		return nil
-	}
-	// 收集所有页条目
-	type pageData struct {
-		entries []byte
-		el      int
-		count   int
-	}
-	var pages []pageData
-	curPageID := entryPage
-	for curPageID != 0 {
-		buf := make([]byte, page.IndexPageSize)
-		if err := handle.ReadPage(curPageID, buf); err != nil {
-			break
-		}
-		hdr := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
-		el := int(hdr.EntryLen)
-		count := int(hdr.Count)
-		entries := make([]byte, count*el)
-		copy(entries, buf[page.IndexHeadSize:page.IndexHeadSize+count*el])
-		pages = append(pages, pageData{entries: entries, el: el, count: count})
-		curPageID = hdr.Hdr.NextPage
-	}
-	// 遍历
-	if !reverse {
-		for p := 0; p < len(pages); p++ {
-			pg := &pages[p]
-			for i := 0; i < pg.count; i++ {
-				entry := pg.entries[i*pg.el : (i+1)*pg.el]
-				key := entry[:pg.el-8]
-				if endKey != nil {
-					cmp := strCmp(key, endKey)
-					if cmp > 0 || (cmp == 0 && !endInclusive) {
-						return nil
-					}
-				}
-				if startKey != nil {
-					cmp := strCmp(key, startKey)
-					if cmp < 0 || (cmp == 0 && !startInclusive) {
-						continue
-					}
-				}
-				pkID := entry[pg.el-8:]
-				if !callback(pkID) {
-					return nil
-				}
-			}
-		}
-	} else {
-		for p := len(pages) - 1; p >= 0; p-- {
-			pg := &pages[p]
-			for i := pg.count - 1; i >= 0; i-- {
-				entry := pg.entries[i*pg.el : (i+1)*pg.el]
-				key := entry[:pg.el-8]
-				if startKey != nil {
-					cmp := strCmp(key, startKey)
-					if cmp < 0 || (cmp == 0 && !startInclusive) {
-						return nil
-					}
-				}
-				if endKey != nil {
-					cmp := strCmp(key, endKey)
-					if cmp > 0 || (cmp == 0 && !endInclusive) {
-						continue
-					}
-				}
-				pkID := entry[pg.el-8:]
-				if !callback(pkID) {
-					return nil
-				}
-			}
-		}
-	}
-	return nil
-}
-
-// strCmp 瀛楄妭鍒囩墖姣旇緝
-func strCmp(a, b []byte) int {
-	ml := len(a)
-	if len(b) < ml {
-		ml = len(b)
-	}
-	for i := 0; i < ml; i++ {
-		if a[i] < b[i] {
-			return -1
-		}
-		if a[i] > b[i] {
-			return 1
-		}
-	}
-	if len(a) < len(b) {
-		return -1
-	}
-	if len(a) > len(b) {
-		return 1
-	}
-	return 0
-}
-
-func (h *StrChainHandler) deleteEntry(handle page.HandleDb, entryPage *uint32, indexKey []byte) error {
-	dataEntry := h.getDataEntry()
-	curPageID := *entryPage
-	for curPageID != 0 {
-		buf := make([]byte, page.IndexPageSize)
-		if err := handle.ReadPage(curPageID, buf); err != nil {
-			return err
-		}
-		hdr := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
-		entries := buf[page.IndexHeadSize : page.IndexHeadSize+int(hdr.Count)*int(hdr.EntryLen)]
-		entryLen := int(hdr.EntryLen)
-		err := dataEntry.Delete(handle, entries, entryLen, indexKey)
-		if err == nil {
-			hdr.Hdr.UseSize -= uint32(entryLen)
-			_ = handle.SavePage(curPageID, buf)
-			return nil
-		}
-		curPageID = hdr.Hdr.NextPage
-	}
-	return page.ErrNotFound
-}
-
-func (h *StrChainHandler) getDataEntry() IDateEntry {
-	handler, _ := Compose.GetEntryHandler(page.IndexTypeStr)
-	return handler.DataEntry()
+	return btree.StrBTRangeScan(entryPage, handle, startKey, endKey, startInclusive, endInclusive, reverse, callback)
 }