| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- package main
- import (
- "git.ycl.pub/yl32940/TsDB2/DB"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- "fmt"
- "os"
- "time"
- )
- // Record 测试表结构 — 通过 vrtx 标签声明索引,UseTable 自动创建
- type Record struct {
- DB.BaseModel
- Name string `vrtx:"maxlen:32"`
- Age int64 `vrtx:"index:idx_hash_age,type:hash"`
- Score int64 `vrtx:"index:idx_btree_score"`
- Dept string `vrtx:"maxlen:16"`
- }
- // PerfRecord 性能测试用 — 同一字段同时建 Hash 和 B+树 做对比
- type PerfRecord struct {
- DB.BaseModel
- Name string `vrtx:"maxlen:32"`
- Age int64 `vrtx:"index:idx_hash_age,type:hash"`
- Score int64 `vrtx:"index:idx_btree_age"`
- Dept string `vrtx:"maxlen:16"`
- }
- var dbPath string
- var passed, failed int
- func check(msg string, ok bool, detail ...string) {
- if ok {
- passed++
- fmt.Printf(" ✅ %s\n", msg)
- } else {
- failed++
- d := ""
- if len(detail) > 0 {
- d = " — " + detail[0]
- }
- fmt.Printf(" ❌ %s%s\n", msg, d)
- }
- }
- func section(title string) {
- fmt.Printf("\n%s %s\n", "==========", title)
- }
- // searchAge 用引擎的键编码查询 Age 索引, 返回查找错误 (nil 表示命中)。
- //
- // 不能手拼 uint64(age) 大端: int64 列现在落 FieldType_Int64, 用保序编码 (符号位翻转),
- // 手拼的键在 v7 之后与盘上键不匹配, SearchByIndex 会一律 not found。
- func searchAge(ct *core.Table, idxName string, age int64) error {
- key, err := ct.BuildIndexKey(idxName, age)
- if err != nil {
- return err
- }
- _, err = ct.SearchByIndex(idxName, key)
- return err
- }
- // countFindable 统计可被 Hash 索引查找到的 Age 值数量
- func countFindable(db *DB.DB, tableName string, idxName string, minAge, maxAge int64) int {
- coreTbl, _ := db.CoreDB().FindTable(tableName)
- count := 0
- for age := minAge; age < maxAge; age++ {
- if searchAge(coreTbl, idxName, age) == nil {
- count++
- }
- }
- return count
- }
- func main() {
- dbPath = fmt.Sprintf("hash_test_%d.db", time.Now().UnixNano())
- os.Remove(dbPath)
- fmt.Println("============================================================")
- fmt.Println(" TsDB2 Hash 桶式索引 — 完整测试")
- fmt.Println(" 覆盖: CRUD/分页/桶分裂(Split)/桶合并(Merge)/持久化/性能")
- fmt.Println("============================================================")
- // ============================================================
- // 1. 建表 (索引由 vrtx 标签自动创建)
- // ============================================================
- section("1. 建表 + 自动索引")
- db, err := DB.Open(dbPath)
- if err != nil {
- panic(err)
- }
- db.SetAutoCommitDelay(0)
- db.SetBufferPoolCapacity(4000)
- tbl, err := DB.UseTable[Record](db, "hash_test")
- check("UseTable[Record] 建表 (自动创建索引)", err == nil)
- coreTbl, _ := db.CoreDB().FindTable("hash_test")
- // 验证索引已自动创建
- err = searchAge(coreTbl, "idx_hash_age", 25)
- check("idx_hash_age (Hash) 已自动创建", err == nil || err.Error() == "key not found")
- // ============================================================
- // 2. 大批量插入 — 触发多页存储 (3000行, 每桶~187条目)
- // ============================================================
- section("2. 大批量插入 (3000 行, 触发多页)")
- N1 := 3000
- t0 := time.Now()
- for i := 1; i <= N1; i++ {
- err := tbl.Insert(&Record{
- Name: fmt.Sprintf("User%05d", i),
- Age: int64(20 + i%100),
- Score: int64(i % 500),
- Dept: fmt.Sprintf("D%d", i%20),
- })
- if err != nil {
- check(fmt.Sprintf("Insert ID=%d", i), false, err.Error())
- break
- }
- }
- insertTime := time.Since(t0)
- check(fmt.Sprintf("插入 %d 行 耗时 %.2fs (%.0f rows/s)", N1, insertTime.Seconds(), float64(N1)/insertTime.Seconds()), true)
- if err := db.Commit(); err != nil {
- panic(err)
- }
- check("Commit 持久化", true)
- // 验证数据可查
- found1 := countFindable(db, "hash_test", "idx_hash_age", 20, 120)
- check(fmt.Sprintf("Hash 查找: %d/100 个 Age 值可查", found1), found1 > 50)
- u100, err := tbl.Find(100)
- check("PK 查找 ID=100", err == nil && u100 != nil && u100.Name == "User00100")
- // 验证行数
- cnt, _ := tbl.Count()
- check(fmt.Sprintf("行数: %d (应=%d)", cnt, N1), cnt == int64(N1))
- // ============================================================
- // 3. 桶分裂测试 — 集中插入触发 Split (阈值=800)
- // ============================================================
- section("3. 桶分裂测试 (Split)")
- splitN := 1000
- fmt.Printf(" 集中插入 %d 行 Age=25 (全部 hash 到同一桶, 阈值=800)...\n", splitN)
- t0 = time.Now()
- for i := N1 + 1; i <= N1+splitN; i++ {
- if err := tbl.Insert(&Record{
- Name: fmt.Sprintf("Split%05d", i),
- Age: 25,
- Score: int64(i % 300),
- Dept: fmt.Sprintf("S%d", i%5),
- }); err != nil {
- check(fmt.Sprintf("Split Insert ID=%d", i), false, err.Error())
- break
- }
- }
- check(fmt.Sprintf("集中插入 %d 行 耗时 %.2fs", splitN, time.Since(t0).Seconds()), true)
- if err := db.Commit(); err != nil {
- panic(err)
- }
- // 验证分裂后数据完整性
- err25 := searchAge(coreTbl, "idx_hash_age", 25)
- check("分裂后 Hash 查找 Age=25", err25 == nil)
- splitFound := countFindable(db, "hash_test", "idx_hash_age", 20, 120)
- check(fmt.Sprintf("分裂后 Hash 查找: %d/100 个 Age 值可查", splitFound), splitFound > 50)
- uSplit, err := tbl.Find(uint64(N1 + 1))
- check("分裂后 PK 查找", err == nil && uSplit != nil)
- cnt2, _ := tbl.Count()
- check(fmt.Sprintf("分裂后行数: %d (应=%d)", cnt2, N1+splitN), cnt2 == int64(N1+splitN))
- // ============================================================
- // 4. 删除测试
- // ============================================================
- section("4. Hash 索引删除测试")
- delN := 500
- for i := 1; i <= delN; i++ {
- if err := tbl.Delete(uint64(i)); err != nil {
- check(fmt.Sprintf("Delete ID=%d", i), false, err.Error())
- break
- }
- }
- check(fmt.Sprintf("删除 %d 行 (ID 1~%d)", delN, delN), true)
- if err := db.Commit(); err != nil {
- panic(err)
- }
- _, err = tbl.Find(1)
- check("删除后 PK 查找 ID=1 (应不存在)", err != nil)
- _, err = tbl.Find(uint64(delN + 1))
- check(fmt.Sprintf("删除后 PK 查找 ID=%d (应存在)", delN+1), err == nil)
- err = searchAge(coreTbl, "idx_hash_age", 25)
- check("删除后 Hash 查找 Age=25", err == nil)
- // ============================================================
- // 5. 桶合并测试 — 大量删除触发 Merge
- // ============================================================
- section("5. 桶合并测试 (Merge)")
- deleteCount := 0
- for i := uint64(delN + 1); i <= uint64(N1+splitN); i++ {
- if err := tbl.Delete(i); err == nil {
- deleteCount++
- }
- }
- check(fmt.Sprintf("删除 %d 行", deleteCount), deleteCount > 0)
- if err := db.Commit(); err != nil {
- panic(err)
- }
- remaining, _ := tbl.Count()
- check(fmt.Sprintf("剩余行数: %d (已删除全部 %d 行)", remaining, N1+splitN), remaining == 0)
- // 验证全删后哈希索引仍可安全查询
- err = searchAge(coreTbl, "idx_hash_age", 25)
- check("合并后 Hash 索引不崩溃", true)
- // ============================================================
- // 6. 持久化测试 (关闭重开)
- // ============================================================
- section("6. 持久化 + 重开验证")
- countBefore := remaining
- db.Close()
- check("关闭数据库", true)
- db, err = DB.Open(dbPath)
- if err != nil {
- panic(err)
- }
- db.SetBufferPoolCapacity(4000)
- tbl, err = DB.UseTable[Record](db, "hash_test")
- check("重开 UseTable[Record]", err == nil)
- coreTbl, _ = db.CoreDB().FindTable("hash_test")
- countAfter, _ := tbl.Count()
- check(fmt.Sprintf("重开后行数: %d (应=%d)", countAfter, countBefore),
- countAfter == countBefore,
- fmt.Sprintf("expected=%d actual=%d", countBefore, countAfter))
- db.Close()
- // ============================================================
- // 7. 性能对比: Hash vs B+树 (用 PerfRecord 同一字段两种索引)
- // ============================================================
- section("7. 性能对比: Hash vs B+树")
- os.Remove(dbPath)
- db, _ = DB.Open(dbPath)
- db.SetAutoCommitDelay(0)
- db.SetBufferPoolCapacity(4000)
- perfTbl, _ := DB.UseTable[PerfRecord](db, "perf_test")
- perfCoreTbl, _ := db.CoreDB().FindTable("perf_test")
- N := 5000
- t0 = time.Now()
- for i := 1; i <= N; i++ {
- perfTbl.Insert(&PerfRecord{
- Name: fmt.Sprintf("Perf%06d", i),
- Age: int64(20 + i%200),
- Score: int64(i % 1000),
- Dept: fmt.Sprintf("D%d", i%30),
- })
- }
- db.Commit()
- insertT := time.Since(t0)
- fmt.Printf(" 插入 %d 行: %.2fs (%.0f rows/s)\n", N, insertT.Seconds(), float64(N)/insertT.Seconds())
- lookupN := 1000
- t0 = time.Now()
- for i := 0; i < lookupN; i++ {
- age := int64(20 + i%200)
- searchAge(perfCoreTbl, "idx_hash_age", age)
- }
- hashTime := time.Since(t0)
- hashQPS := float64(lookupN) / hashTime.Seconds()
- fmt.Printf(" Hash 查找 %d 次: %.3fs (%.0f qps)\n", lookupN, hashTime.Seconds(), hashQPS)
- t0 = time.Now()
- for i := 0; i < lookupN; i++ {
- age := int64(20 + i%200)
- searchAge(perfCoreTbl, "idx_btree_age", age)
- }
- btreeTime := time.Since(t0)
- btreeQPS := float64(lookupN) / btreeTime.Seconds()
- fmt.Printf(" B+树 查找 %d 次: %.3fs (%.0f qps)\n", lookupN, btreeTime.Seconds(), btreeQPS)
- if hashQPS > 0 && btreeQPS > 0 {
- ratio := hashQPS / btreeQPS
- check(fmt.Sprintf("Hash/B+树 性能比: %.2fx", ratio), true)
- }
- db.Close()
- // ============================================================
- // 总结
- // ============================================================
- section("测试总结")
- total := passed + failed
- fmt.Printf(" 总计: %d 项测试, ✅ 通过: %d, ❌ 失败: %d\n", total, passed, failed)
- if failed == 0 {
- fmt.Println(" 🎉 全部通过!Hash 桶式索引工作正常!")
- } else {
- fmt.Printf(" ⚠️ 有 %d 项测试失败,请检查\n", failed)
- }
- // [KEEP-DB] 保留数据库文件供 Analyzer 分析, 屏蔽运行结束后的清理删除
- // os.Remove(dbPath)
- }
|