OPERATION_TUTORIAL.md 20 KB

TsDB2 完整操作教程

本教程涵盖 TsDB2 时序数据库的所有核心操作,包括建表、增删改查、条件查询、索引管理、事务控制等。


📋 目录

  1. 基础概念
  2. 建表与索引
  3. 插入数据 (Create)
  4. 查询数据 (Read)
  5. 更新数据 (Update)
  6. 删除数据 (Delete)
  7. 条件查询详解
  8. 排序与分页
  9. 聚合查询
  10. 多表关联
  11. 事务与提交
  12. 高级特性

1. 基础概念

1.1 数据模型定义

TsDB2 使用 Go struct 定义数据模型,通过 tsdb tag 指定字段类型:

package main

import (
    "TsDB2/DB"
    "fmt"
)

// 员工表模型
type Employee struct {
    DB.BaseModel        // 必须嵌入,提供 ID 字段
    Name  string `tsdb:"32byte"`  // 字符串字段,最大32字节
    Age   int64  `tsdb:"8byte"`   // 整数字段,8字节
    Score int64  `tsdb:"8byte"`   // 整数字段,8字节
    Dept  string `tsdb:"16byte"`  // 字符串字段,最大16字节
}

// 订单表模型
type Order struct {
    DB.BaseModel
    EmployeeID int64 `tsdb:"8byte"`
    Amount     int64 `tsdb:"8byte"`
    Status     int64 `tsdb:"8byte"`  // 0=pending, 1=done, 2=cancel
}

支持的字段类型:

  • 1byte ~ 256byte: 变长字符串/二进制
  • 8byte: int64 / uint64
  • 4byte: int32 / uint32
  • 2byte: int16 / uint16
  • 1byte: int8 / uint8

1.2 打开数据库

// 打开或创建数据库文件
db, err := DB.Open("mydb.db")
if err != nil {
    panic(err)
}
defer db.Close()  // 程序退出前关闭

// 设置自动提交延迟(防抖)
db.SetAutoCommitDelay(500 * time.Millisecond)  // 500ms 后自动 Commit

2. 建表与索引

2.1 创建表

// ✅ 推荐方式:自动推导表名(从类型名 Employee -> "employee")
empTbl, err := DB.UseTable[Employee](db)
if err != nil {
    panic(err)
}

// 可选:手动指定表名
orderTbl, err := DB.UseTable[Order](db, "orders")  // 自定义表名为 "orders"
if err != nil {
    panic(err)
}

工作原理:

  • UseTable[Employee](db) → 自动推导表名为 "employee"(首字母小写)
  • UseTable[OrderItem](db) → 自动推导表名为 "orderItem"
  • UseTable[T](db, "custom_name") → 使用自定义表名

2.2 创建索引

TsDB2 支持多种索引类型:

// 获取表的底层 Core 对象
empCore, _ := db.CoreDB().FindTable("employee")

// 1️⃣ 主键 B+ 树索引(自动创建,基于 ID 字段)
// 无需手动创建,UseTable 时自动生成 pk_employee

// 2️⃣ 二级 B+ 树索引(单字段)
empCore.CreateIndex("idx_age", page.IndexTypeBTree, []byte{2})   // Age 字段索引
empCore.CreateIndex("idx_dept", page.IndexTypeBTree, []byte{4})  // Dept 字段索引

// 3️⃣ 时序索引(时间范围查询优化)
// empCore.CreateIndex("idx_time", page.IndexTypeTime, []byte{1})

// 4️⃣ 复合索引(多字段组合)
// empCore.CreateIndex("idx_age_dept", page.IndexTypeCompose, []byte{2, 4})

orderCore, _ := db.CoreDB().FindTable("order")
orderCore.CreateIndex("idx_status", page.IndexTypeBTree, []byte{3})  // Status 索引

索引类型说明:

  • IndexTypePrimary (2): 主键索引,叶子节点存 [Key → DataPage]
  • IndexTypeBTree (5): 二级 B+ 树索引,叶子节点存 [Key → PKID]
  • IndexTypeTime (1): 时序索引,优化时间范围查询
  • IndexTypeCompose (4): 复合索引,多字段联合索引

3. 插入数据 (Create)

3.1 单行插入

err := empTbl.Insert(&Employee{
    Name:  "张三",
    Age:   25,
    Score: 95,
    Dept:  "工程部",
})
if err != nil {
    fmt.Printf("插入失败: %v\n", err)
}

3.2 批量插入

// 插入 10000 条员工记录
for i := 1; i <= 10000; i++ {
    err := empTbl.Insert(&Employee{
        Name:  fmt.Sprintf("Emp%07d", i),
        Age:   int64(20 + (i % 50)),      // 年龄 20~69
        Score: int64(i % 1000),            // 分数 0~999
        Dept:  []string{"工程部", "销售部", "人事部", "财务部", "运维部"}[i%5],
    })
    if err != nil {
        panic(fmt.Sprintf("插入第 %d 行失败: %v", i, err))
    }
    
    // 每 1000 行打印进度
    if i%1000 == 0 {
        fmt.Printf("已插入 %d 行\n", i)
    }
}

3.3 性能优化:批量 Commit

// 禁用自动提交,手动控制 Commit 时机
db.SetAutoCommitDelay(0)  // 禁用防抖

// 批量插入 10 万行
for i := 1; i <= 100000; i++ {
    empTbl.Insert(&Employee{...})
}

// 一次性提交所有数据
fmt.Println("开始 Commit...")
t1 := time.Now()
if err := db.Commit(); err != nil {
    panic(err)
}
fmt.Printf("Commit 完成,耗时: %.2fs\n", time.Since(t1).Seconds())

性能对比:

  • 每行自动 Commit: 10万行约 100秒
  • 批量 Commit: 10万行约 4秒(快 25 倍)

4. 查询数据 (Read)

4.1 主键精确查询

// 根据 ID 查询
emp, err := empTbl.Find(1)
if err != nil {
    fmt.Printf("未找到 ID=1 的记录\n")
} else {
    fmt.Printf("ID=%d, Name=%s, Age=%d, Score=%d, Dept=%s\n",
        emp.ID, emp.Name, emp.Age, emp.Score, emp.Dept)
}

4.2 条件查询

// WHERE Age = 25
rows, err := empTbl.Where("Age", "=", int64(25)).Find()
if err != nil {
    panic(err)
}
fmt.Printf("找到 %d 名 25 岁的员工\n", len(rows))
for _, emp := range rows {
    fmt.Printf("  - %s (Score: %d)\n", emp.Name, emp.Score)
}

4.3 遍历所有记录

// 全表扫描
allEmps, err := empTbl.Find()  // 无条件,返回所有记录
if err != nil {
    panic(err)
}
fmt.Printf("总共有 %d 名员工\n", len(allEmps))

// 循环处理
for i, emp := range allEmps {
    if emp.Score >= 90 {
        fmt.Printf("[%d] 优秀员工: %s, Score=%d\n", i+1, emp.Name, emp.Score)
    }
}

5. 更新数据 (Update)

5.1 根据主键更新

// 更新 ID=1 的员工信息
err := empTbl.Update(&Employee{
    BaseModel: DB.BaseModel{ID: 1},  // 必须指定 ID
    Name:      "李四",
    Age:       30,
    Score:     98,
    Dept:      "技术部",
})
if err != nil {
    fmt.Printf("更新失败: %v\n", err)
}

// 验证更新
emp, _ := empTbl.Find(1)
fmt.Printf("更新后: Name=%s, Age=%d\n", emp.Name, emp.Age)
// 输出: 更新后: Name=李四, Age=30

5.2 批量更新(循环)

// 给所有工程部员工加 10 分
emps, _ := empTbl.Where("Dept", "=", "工程部").Find()
for _, emp := range emps {
    emp.Score += 10
    empTbl.Update(emp)
}
fmt.Printf("已为 %d 名工程部员工加分\n", len(emps))

6. 删除数据 (Delete)

6.1 根据主键删除

// 删除 ID=2 的员工
err := empTbl.Delete(2)
if err != nil {
    fmt.Printf("删除失败: %v\n", err)
}

// 验证删除
_, err = empTbl.Find(2)
if err != nil {
    fmt.Println("✅ ID=2 已成功删除")
}

6.2 批量删除(循环)

// 删除所有分数低于 60 的员工
lowScores, _ := empTbl.Where("Score", "<", int64(60)).Find()
fmt.Printf("找到 %d 名低分员工\n", len(lowScores))

for _, emp := range lowScores {
    empTbl.Delete(emp.ID)
}
fmt.Println("✅ 已删除所有低分员工")

// 验证
remaining, _ := empTbl.Count()
fmt.Printf("剩余员工数: %d\n", remaining)

6.3 级联删除

// 删除部门时,同时删除该部门所有员工
deptToDelete := "临时工"
emps, _ := empTbl.Where("Dept", "=", deptToDelete).Find()
for _, emp := range emps {
    empTbl.Delete(emp.ID)
}
fmt.Printf("已删除 %d 名 %s 员工\n", len(emps), deptToDelete)

7. 条件查询详解

7.1 比较运算符

// 等于 (=)
rows, _ := empTbl.Where("Age", "=", int64(25)).Find()

// 不等于 (!=)
rows, _ = empTbl.Where("Status", "!=", int64(0)).Find()

// 大于 (>)
rows, _ = empTbl.Where("Score", ">", int64(90)).Find()

// 大于等于 (>=)
rows, _ = empTbl.Where("Age", ">=", int64(30)).Find()

// 小于 (<)
rows, _ = empTbl.Where("Score", "<", int64(60)).Find()

// 小于等于 (<=)
rows, _ = empTbl.Where("Age", "<=", int64(25)).Find()

7.2 范围查询

// WHERE Age >= 20 AND Age <= 30
rows, _ := empTbl.
    Where("Age", ">=", int64(20)).
    Where("Age", "<=", int64(30)).
    Find()
fmt.Printf("20~30岁员工: %d 人\n", len(rows))

7.3 IN 查询

// WHERE Dept IN ("工程部", "技术部")
rows, _ := empTbl.Where("Dept", "in", []string{"工程部", "技术部"}).Find()
fmt.Printf("工程/技术部员工: %d 人\n", len(rows))

7.4 LIKE 模糊查询

// WHERE Name LIKE "张%"
rows, _ := empTbl.Where("Name", "like", "张%").Find()
fmt.Printf("姓张的员工: %d 人\n", len(rows))

// WHERE Name LIKE "%三"
rows, _ = empTbl.Where("Name", "like", "%三").Find()
fmt.Printf("名字以'三'结尾的员工: %d 人\n", len(rows))

7.5 多条件组合

// WHERE Age > 25 AND Score >= 80 AND Dept = "工程部"
rows, _ := empTbl.
    Where("Age", ">", int64(25)).
    Where("Score", ">=", int64(80)).
    Where("Dept", "=", "工程部").
    Find()
fmt.Printf("符合条件的员工: %d 人\n", len(rows))

7.6 OR 条件

// 使用 OrCondition
import "TsDB2/DB/core"

cond := core.OrCondition{
    core.EqCondition{Field: "Dept", Value: "工程部"},
    core.EqCondition{Field: "Dept", Value: "技术部"},
}
rows, _ := empTbl.WhereCondition(cond).Find()

8. 排序与分页

8.1 升序排序

// ORDER BY Age ASC
rows, _ := empTbl.OrderBy("Age").Find()
fmt.Println("按年龄升序:")
for _, emp := range rows[:5] {  // 只看前5条
    fmt.Printf("  %s: %d岁\n", emp.Name, emp.Age)
}

8.2 降序排序

// ORDER BY Score DESC
rows, _ := empTbl.OrderBy("Score", true).Find()  // true = 降序
fmt.Println("按分数降序(前5名):")
for i, emp := range rows[:5] {
    fmt.Printf("  [%d] %s: %d分\n", i+1, emp.Name, emp.Score)
}

8.3 LIMIT 限制数量

// ORDER BY Age ASC LIMIT 10
rows, _ := empTbl.OrderBy("Age").Limit(10).Find()
fmt.Printf("最年轻的 10 名员工:\n")
for _, emp := range rows {
    fmt.Printf("  %s: %d岁\n", emp.Name, emp.Age)
}

8.4 OFFSET + LIMIT 分页

// 每页 20 条
pageSize := 20

// 第1页
page1, _ := empTbl.OrderBy("Age").Offset(0).Limit(pageSize).Find()
fmt.Printf("第1页: %d 条\n", len(page1))

// 第2页
page2, _ := empTbl.OrderBy("Age").Offset(pageSize).Limit(pageSize).Find()
fmt.Printf("第2页: %d 条\n", len(page2))

// 第3页
page3, _ := empTbl.OrderBy("Age").Offset(pageSize*2).Limit(pageSize).Find()
fmt.Printf("第3页: %d 条\n", len(page3))

8.5 完整分页示例

func printPage(tbl *DB.Table[Employee], pageNum, pageSize int) {
    offset := (pageNum - 1) * pageSize
    rows, _ := tbl.OrderBy("Age").Offset(offset).Limit(pageSize).Find()
    
    fmt.Printf("\n=== 第 %d 页 (共 %d 条) ===\n", pageNum, len(rows))
    for _, emp := range rows {
        fmt.Printf("  %-15s %2d岁  %3d分  %s\n", 
            emp.Name, emp.Age, emp.Score, emp.Dept)
    }
}

// 打印前3页
for page := 1; page <= 3; page++ {
    printPage(empTbl, page, 20)
}

9. 聚合查询

9.1 COUNT 计数

// SELECT COUNT(*) FROM employee
total, err := empTbl.Count()
if err != nil {
    panic(err)
}
fmt.Printf("员工总数: %d\n", total)

// 带条件的 COUNT
youngCount, _ := empTbl.Where("Age", "<", int64(25)).Count()
fmt.Printf("25岁以下员工: %d 人\n", youngCount)

9.2 MIN/MAX(通过排序实现)

// 最小年龄
minAgeRows, _ := empTbl.OrderBy("Age").Limit(1).Find()
if len(minAgeRows) > 0 {
    fmt.Printf("最小年龄: %d岁 (%s)\n", minAgeRows[0].Age, minAgeRows[0].Name)
}

// 最大分数
maxScoreRows, _ := empTbl.OrderBy("Score", true).Limit(1).Find()
if len(maxScoreRows) > 0 {
    fmt.Printf("最高分数: %d分 (%s)\n", maxScoreRows[0].Score, maxScoreRows[0].Name)
}

9.3 SUM/AVG(循环计算)

// 计算平均分数
allEmps, _ := empTbl.Find()
var totalScore int64
for _, emp := range allEmps {
    totalScore += emp.Score
}
avgScore := float64(totalScore) / float64(len(allEmps))
fmt.Printf("平均分数: %.2f\n", avgScore)

// 各部门平均分
deptMap := make(map[string][]int64)
for _, emp := range allEmps {
    deptMap[emp.Dept] = append(deptMap[emp.Dept], emp.Score)
}
for dept, scores := range deptMap {
    var sum int64
    for _, s := range scores {
        sum += s
    }
    avg := float64(sum) / float64(len(scores))
    fmt.Printf("  %s: 平均 %.2f 分 (%d 人)\n", dept, avg, len(scores))
}

10. 多表关联

10.1 简单关联查询

// 查询某员工的所有订单
employeeID := int64(1)
orders, _ := orderTbl.Where("EmployeeID", "=", employeeID).Find()
fmt.Printf("员工 %d 的订单数: %d\n", employeeID, len(orders))
for _, order := range orders {
    fmt.Printf("  订单ID=%d, 金额=%d, 状态=%d\n", order.ID, order.Amount, order.Status)
}

10.2 嵌套循环关联

// 查询每个员工的订单总数
emps, _ := empTbl.Find()
for _, emp := range emps[:10] {  // 只看前10名员工
    orders, _ := orderTbl.Where("EmployeeID", "=", int64(emp.ID)).Find()
    fmt.Printf("%-15s: %d 个订单\n", emp.Name, len(orders))
}

10.3 复杂关联 + 条件

// 查询工程部员工的状态为"已完成"的订单
deptEmps, _ := empTbl.Where("Dept", "=", "工程部").Find()
var totalAmount int64
var orderCount int

for _, emp := range deptEmps {
    orders, _ := orderTbl.
        Where("EmployeeID", "=", int64(emp.ID)).
        Where("Status", "=", int64(1)).  // 1 = done
        Find()
    
    for _, order := range orders {
        totalAmount += order.Amount
        orderCount++
    }
}

fmt.Printf("工程部已完成订单: %d 笔, 总金额: %d\n", orderCount, totalAmount)

11. 事务与提交

11.1 手动 Commit

// 禁用自动提交
db.SetAutoCommitDelay(0)

// 执行多个操作
empTbl.Insert(&Employee{Name: "A", Age: 25, Score: 90, Dept: "IT"})
empTbl.Insert(&Employee{Name: "B", Age: 30, Score: 85, Dept: "HR"})
empTbl.Delete(5)

// 一次性提交
if err := db.Commit(); err != nil {
    fmt.Printf("提交失败: %v\n", err)
    // 可以回滚或重试
} else {
    fmt.Println("✅ 事务提交成功")
}

11.2 防抖自动提交

// 设置 500ms 防抖
db.SetAutoCommitDelay(500 * time.Millisecond)

// 连续插入,最后一次操作后 500ms 自动 Commit
for i := 1; i <= 100; i++ {
    empTbl.Insert(&Employee{...})
}

// 等待自动提交
time.Sleep(1 * time.Second)
fmt.Println("数据已自动提交")

11.3 脏页阈值自动提交

// 当脏页超过 2000 页时自动触发 Commit
db.SetDirtyThreshold(2000)

// 大量插入时会自动分批 Commit
for i := 1; i <= 100000; i++ {
    empTbl.Insert(&Employee{...})
}
// 无需手动 Commit,系统会根据脏页阈值自动提交

11.4 Close 兜底提交

// 即使忘记 Commit,Close 时也会自动提交
db, _ := DB.Open("test.db")
tbl, _ := DB.UseTable[Employee](db, "test")

for i := 1; i <= 1000; i++ {
    tbl.Insert(&Employee{...})
}

// 没有手动 Commit
db.Close()  // ✅ Close 内部会调用 Commit,确保数据持久化

// 重新打开验证
db2, _ := DB.Open("test.db")
tbl2, _ := DB.UseTable[Employee](db2, "test")
count, _ := tbl2.Count()
fmt.Printf("Close 后数据保留: %d 条\n", count)  // 输出: 1000 条

12. 高级特性

12.1 Schema 演进(添加字段)

// V1: 初始版本
type EmployeeV1 struct {
    DB.BaseModel
    Name string `tsdb:"32byte"`
    Age  int64  `tsdb:"8byte"`
}

// V2: 添加新字段
type EmployeeV2 struct {
    DB.BaseModel
    Name  string `tsdb:"32byte"`
    Age   int64  `tsdb:"8byte"`
    Email string `tsdb:"64byte"`  // 新增字段
}

// TsDB2 自动处理 schema 版本迁移
tbl, _ := DB.UseTable[EmployeeV2](db, "employee")

12.2 删除字段(软删除)

// 标记字段为 dropped(不物理删除,保持兼容性)
empCore.DropField("Score")  // Score 字段标记为已删除

// 查询时不再返回该字段
emp, _ := tbl.Find(1)
// emp.Score 将为默认值 0

12.3 删除表

// 删除整个表(回收所有页)
err := db.DropTable("order")
if err != nil {
    fmt.Printf("删除表失败: %v\n", err)
}

// 验证删除
_, exists := db.CoreDB().FindTable("order")
if !exists {
    fmt.Println("✅ order 表已删除")
}

12.4 索引管理

// 查看所有索引
table, _ := db.CoreDB().FindTable("employee")
for _, idx := range table.GetIndexes() {
    fmt.Printf("索引: %s, 类型: %d, 根页: %d\n", 
        idx.Name, idx.Type, idx.EntryPage)
}

// 删除索引
table.DropIndex("idx_age")

12.5 性能监控

// 统计信息
fi, _ := os.Stat("mydb.db")
fmt.Printf("数据库大小: %.2f MB\n", float64(fi.Size())/1024/1024)

// 表统计
count, _ := empTbl.Count()
fmt.Printf("员工表行数: %d\n", count)

// BufferPool 统计
bp := db.CoreDB().GetBufferPool()
fmt.Printf("缓存页数: %d\n", bp.Len())
fmt.Printf("脏页数: %d\n", bp.DirtyCount())

12.6 并发安全

// TsDB2 不是线程安全的,需要在应用层加锁
var mu sync.Mutex

go func() {
    mu.Lock()
    defer mu.Unlock()
    empTbl.Insert(&Employee{...})
}()

go func() {
    mu.Lock()
    defer mu.Unlock()
    empTbl.Find(1)
}()

🎯 完整示例:员工管理系统

package main

import (
    "TsDB2/DB"
    "fmt"
    "time"
)

type Employee struct {
    DB.BaseModel
    Name  string `tsdb:"32byte"`
    Age   int64  `tsdb:"8byte"`
    Score int64  `tsdb:"8byte"`
    Dept  string `tsdb:"16byte"`
}

func main() {
    // 1. 打开数据库
    db, _ := DB.Open("company.db")
    defer db.Close()
    db.SetAutoCommitDelay(0)  // 手动控制
    
    // 2. 建表 + 索引(自动推导表名)
    tbl, _ := DB.UseTable[Employee](db)  // ✅ 无需传递 "employee"
    core, _ := db.CoreDB().FindTable("employee")
    core.CreateIndex("idx_age", page.IndexTypeBTree, []byte{2})
    core.CreateIndex("idx_dept", page.IndexTypeBTree, []byte{4})
    
    // 3. 批量插入
    fmt.Println("插入数据...")
    depts := []string{"工程部", "销售部", "人事部", "财务部", "运维部"}
    for i := 1; i <= 1000; i++ {
        tbl.Insert(&Employee{
            Name:  fmt.Sprintf("Emp%04d", i),
            Age:   int64(20 + (i % 50)),
            Score: int64(50 + (i % 51)),
            Dept:  depts[i%5],
        })
    }
    db.Commit()
    
    // 4. 查询
    fmt.Println("\n=== 查询示例 ===")
    
    // 4.1 主键查询
    emp, _ := tbl.Find(1)
    fmt.Printf("ID=1: %s, %d岁\n", emp.Name, emp.Age)
    
    // 4.2 条件查询
    young, _ := tbl.Where("Age", "<", int64(25)).Find()
    fmt.Printf("25岁以下: %d 人\n", len(young))
    
    // 4.3 排序 + 分页
    top10, _ := tbl.OrderBy("Score", true).Limit(10).Find()
    fmt.Println("分数Top 10:")
    for i, e := range top10 {
        fmt.Printf("  [%d] %s: %d分\n", i+1, e.Name, e.Score)
    }
    
    // 4.4 聚合
    total, _ := tbl.Count()
    fmt.Printf("总人数: %d\n", total)
    
    // 5. 更新
    fmt.Println("\n=== 更新示例 ===")
    tbl.Update(&Employee{
        BaseModel: DB.BaseModel{ID: 1},
        Score:     100,
    })
    emp, _ = tbl.Find(1)
    fmt.Printf("ID=1 更新后分数: %d\n", emp.Score)
    
    // 6. 删除
    fmt.Println("\n=== 删除示例 ===")
    tbl.Delete(2)
    _, err := tbl.Find(2)
    if err != nil {
        fmt.Println("ID=2 已删除")
    }
    
    // 7. 复杂查询
    fmt.Println("\n=== 复杂查询 ===")
    itHighScore, _ := tbl.
        Where("Dept", "=", "工程部").
        Where("Score", ">", int64(80)).
        Find()
    fmt.Printf("工程部高分员工: %d 人\n", len(itHighScore))
    
    fmt.Println("\n✅ 所有操作完成!")
}

📚 参考资料

  • Analyzer 工具: 可视化查看数据库内部结构
  • full_test: 完整功能测试用例
  • no_commit_test: 事务与自动提交流程测试

祝使用愉快! 🎉