|
|
@@ -0,0 +1,525 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "TsDB2/DB"
|
|
|
+ "TsDB2/DB/page"
|
|
|
+ "TsDB2/DB/types"
|
|
|
+ "fmt"
|
|
|
+ "os"
|
|
|
+ "runtime"
|
|
|
+ "sort"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+const N = 200_000
|
|
|
+
|
|
|
+// ======================== 计时工具 ========================
|
|
|
+
|
|
|
+type benchResult struct {
|
|
|
+ name string
|
|
|
+ count int64
|
|
|
+ duration time.Duration
|
|
|
+ detail string
|
|
|
+}
|
|
|
+
|
|
|
+var results []benchResult
|
|
|
+
|
|
|
+func section(title string) {
|
|
|
+ fmt.Printf("\n%s\n%s\n", title, strings.Repeat("=", len(title)))
|
|
|
+}
|
|
|
+
|
|
|
+func timed(name string, fn func() (int64, string)) {
|
|
|
+ t0 := time.Now()
|
|
|
+ cnt, detail := fn()
|
|
|
+ d := time.Since(t0)
|
|
|
+ results = append(results, benchResult{name, cnt, d, detail})
|
|
|
+ qps := float64(cnt) / d.Seconds()
|
|
|
+ if d.Seconds() < 0.001 {
|
|
|
+ fmt.Printf(" %-52s %12d %10v %12s %s\n", name, cnt, d, "N/A", detail)
|
|
|
+ } else {
|
|
|
+ fmt.Printf(" %-52s %12d %10v %10.0f/s %s\n", name, cnt, d, qps, detail)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func findUint64(row []types.FieldValue, field string) uint64 {
|
|
|
+ for _, fv := range row {
|
|
|
+ if string(fv.FieldName) == field {
|
|
|
+ if v, ok := fv.Value.(uint64); ok {
|
|
|
+ return v
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+}
|
|
|
+
|
|
|
+func findString(row []types.FieldValue, field string) string {
|
|
|
+ for _, fv := range row {
|
|
|
+ if string(fv.FieldName) == field {
|
|
|
+ if v, ok := fv.Value.([]byte); ok {
|
|
|
+ return strings.TrimRight(string(v), "\x00")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ""
|
|
|
+}
|
|
|
+
|
|
|
+// ======================== 主函数 ========================
|
|
|
+
|
|
|
+func main() {
|
|
|
+ runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
+ dbName := fmt.Sprintf("bench_%d.db", time.Now().UnixNano())
|
|
|
+ fmt.Printf("TsDB2 Benchmark — %s rows\nDatabase: %s\nCPUs: %d\n",
|
|
|
+ formatNum(N), dbName, runtime.NumCPU())
|
|
|
+
|
|
|
+ os.RemoveAll(dbName)
|
|
|
+ db, err := DB.Open(dbName)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Open error:", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ // ==================== 建表 ====================
|
|
|
+ section("1. 建表 + 建索引")
|
|
|
+ table, err := db.CreateTable("bench", []types.FieldInfo{
|
|
|
+ {types.FieldType_8byte, []byte("id")}, // 0: 主键
|
|
|
+ {types.FieldType_8byte, []byte("age")}, // 1: 二级 B+ 树
|
|
|
+ {types.FieldType_4byte, []byte("score")}, // 2: 无索引
|
|
|
+ {types.FieldType_32byte, []byte("name")}, // 3: 字符串索引
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("CreateTable error:", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ timed("创建表", func() (int64, string) { return 1, "4 fields" })
|
|
|
+
|
|
|
+ _, err = table.CreateIndex("pk_id", page.IndexTypePrimary, []byte{0})
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("CreateIndex pk error:", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ _, err = table.CreateIndex("idx_age", page.IndexTypeBTree, []byte{1})
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("CreateIndex age error:", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ _, err = table.CreateIndex("idx_name", page.IndexTypeStr, []byte{3})
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("CreateIndex name error:", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ timed("创建 3 个索引 (Primary + BTree + Str)", func() (int64, string) { return 3, "" })
|
|
|
+
|
|
|
+ // ==================== 批量插入 ====================
|
|
|
+ section("2. 批量插入 (增)")
|
|
|
+ t0 := time.Now()
|
|
|
+ insertOK := int64(0)
|
|
|
+ reportInterval := N / 10
|
|
|
+ if reportInterval < 100000 {
|
|
|
+ reportInterval = 100000
|
|
|
+ }
|
|
|
+ for i := 0; i < N; i++ {
|
|
|
+ nameBytes := make([]byte, 32)
|
|
|
+ copy(nameBytes, fmt.Sprintf("U%d", i))
|
|
|
+ _, err := table.InsertRow([]types.FieldValue{
|
|
|
+ {FieldName: []byte("id"), Value: uint64(i + 1)},
|
|
|
+ {FieldName: []byte("age"), Value: uint64(18 + i%80)}, // 18-97
|
|
|
+ {FieldName: []byte("score"), Value: int32(i % 1000)}, // 0-999
|
|
|
+ {FieldName: []byte("name"), Value: nameBytes},
|
|
|
+ })
|
|
|
+ if err == nil {
|
|
|
+ insertOK++
|
|
|
+ }
|
|
|
+ if (i+1)%reportInterval == 0 {
|
|
|
+ elapsed := time.Since(t0)
|
|
|
+ qps := float64(insertOK) / elapsed.Seconds()
|
|
|
+ fmt.Printf(" 进度: %s / %s (%.0f rows/s) 已耗时 %v\n",
|
|
|
+ formatNum(int(insertOK)), formatNum(N), qps, elapsed.Round(time.Millisecond))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ insertDur := time.Since(t0)
|
|
|
+ results = append(results, benchResult{"批量插入", insertOK, insertDur,
|
|
|
+ fmt.Sprintf("%.0f rows/s", float64(insertOK)/insertDur.Seconds())})
|
|
|
+ fmt.Printf(" 插入完成: %s rows, 耗时 %v, 吞吐 %.0f rows/s\n",
|
|
|
+ formatNum(int(insertOK)), insertDur.Round(time.Millisecond),
|
|
|
+ float64(insertOK)/insertDur.Seconds())
|
|
|
+
|
|
|
+ // ==================== 一级索引查询 (主键) ====================
|
|
|
+ section("3. 一级索引查询 (主键 B+ 树)")
|
|
|
+
|
|
|
+ timed("PK 精确匹配 Eq(id=N/2)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Eq("id", uint64(N/2), types.FieldType_8byte)).Find()
|
|
|
+ if len(rows) == 0 { return 0, "not found" }
|
|
|
+ return int64(len(rows)), fmt.Sprintf("id=%d", findUint64(rows[0], "id"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK 范围查询 id>=N*0.5 AND id<N*0.5+100", func() (int64, string) {
|
|
|
+ base := uint64(N / 2)
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("id", base, types.FieldType_8byte),
|
|
|
+ DB.Lt("id", base+100, types.FieldType_8byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK 范围查询 id>=N*0.5 AND id<=N*0.5+100", func() (int64, string) {
|
|
|
+ base := uint64(N / 2)
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("id", base, types.FieldType_8byte),
|
|
|
+ DB.Le("id", base+100, types.FieldType_8byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK Gt(id>N-10)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Gt("id", uint64(N-10), types.FieldType_8byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK Le(id<=10)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Le("id", uint64(10), types.FieldType_8byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK FindOne(id=N/3)", func() (int64, string) {
|
|
|
+ row, err := table.Query().Where(DB.Eq("id", uint64(N/3), types.FieldType_8byte)).FindOne()
|
|
|
+ if err != nil || row == nil {
|
|
|
+ return 0, "not found"
|
|
|
+ }
|
|
|
+ return 1, fmt.Sprintf("age=%d", findUint64(row, "age"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK ORDER BY id ASC LIMIT 100", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("id").Limit(100).Find()
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_id=%d", findUint64(rows[0], "id"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK ORDER BY id DESC LIMIT 100", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("id", true).Limit(100).Find()
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_id=%d", findUint64(rows[0], "id"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK COUNT(*)", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK SUM(age)", func() (int64, string) {
|
|
|
+ sum, _ := table.Query().Sum("age")
|
|
|
+ return int64(sum), fmt.Sprintf("%.0f", sum)
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("PK MAX(score) / MIN(score)", func() (int64, string) {
|
|
|
+ maxV, _ := table.Query().Max("score")
|
|
|
+ minV, _ := table.Query().Min("score")
|
|
|
+ return 2, fmt.Sprintf("max=%v min=%v", maxV, minV)
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 二级索引查询 ====================
|
|
|
+ section("4. 二级索引查询 (B+ 树 idx_age)")
|
|
|
+
|
|
|
+ timed("SEC 精确匹配 Eq(age=50)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Eq("age", uint64(50), types.FieldType_8byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC 范围 age>=30 AND age<40", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("age", uint64(30), types.FieldType_8byte),
|
|
|
+ DB.Lt("age", uint64(40), types.FieldType_8byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC 范围 age>=80 AND age<=97", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("age", uint64(80), types.FieldType_8byte),
|
|
|
+ DB.Le("age", uint64(97), types.FieldType_8byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC Gt(age>90)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Gt("age", uint64(90), types.FieldType_8byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC Le(age<=20)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Le("age", uint64(20), types.FieldType_8byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC ORDER BY age ASC LIMIT 50", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("age").Limit(50).Find()
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_age=%d", findUint64(rows[0], "age"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC ORDER BY age DESC LIMIT 50", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("age", true).Limit(50).Find()
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_age=%d", findUint64(rows[0], "age"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC COUNT(age>=50)", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Where(DB.Ge("age", uint64(50), types.FieldType_8byte)).Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC SUM(score) WHERE age BETWEEN 25 AND 35", func() (int64, string) {
|
|
|
+ sum, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("age", uint64(25), types.FieldType_8byte),
|
|
|
+ DB.Le("age", uint64(35), types.FieldType_8byte),
|
|
|
+ )).Sum("score")
|
|
|
+ return int64(sum), fmt.Sprintf("%.0f", sum)
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 字符串索引查询 ====================
|
|
|
+ section("5. 字符串索引查询 (idx_name)")
|
|
|
+
|
|
|
+ timed("STR 精确匹配 Eq(name=last)", func() (int64, string) {
|
|
|
+ nk := make([]byte, 32)
|
|
|
+ copy(nk, fmt.Sprintf("U%d", N-1))
|
|
|
+ rows, _ := table.Query().Where(DB.Eq("name", nk, types.FieldType_32byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("STR 范围 name>='U100' AND name<'U200'", func() (int64, string) {
|
|
|
+ sk := make([]byte, 32)
|
|
|
+ copy(sk, "U100")
|
|
|
+ ek := make([]byte, 32)
|
|
|
+ copy(ek, "U200")
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("name", sk, types.FieldType_32byte),
|
|
|
+ DB.Lt("name", ek, types.FieldType_32byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 全表扫描 + 条件 ====================
|
|
|
+ section("6. 全表扫描 + 复杂条件 (score 无索引)")
|
|
|
+
|
|
|
+ timed("全表: score>900", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Gt("score", int32(900), types.FieldType_4byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("全表: score>=500 AND score<=600", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("score", int32(500), types.FieldType_4byte),
|
|
|
+ DB.Le("score", int32(600), types.FieldType_4byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("全表: In(score, [1,2,3,999])", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.In("score", []interface{}{
|
|
|
+ int32(1), int32(2), int32(3), int32(999),
|
|
|
+ }, types.FieldType_4byte)).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("全表: Not(Eq(age, 50))", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Where(DB.Not(DB.Eq("age", uint64(50), types.FieldType_8byte))).Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("全表: Like(name, 'U100%')", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Like("name", "U100%")).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("全表: Or(Eq(age,18), Eq(age,97))", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Or(
|
|
|
+ DB.Eq("age", uint64(18), types.FieldType_8byte),
|
|
|
+ DB.Eq("age", uint64(97), types.FieldType_8byte),
|
|
|
+ )).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 排序测试 ====================
|
|
|
+ section("7. 排序测试")
|
|
|
+
|
|
|
+ timed("ORDER BY age ASC (内存排序)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Ge("score", int32(990), types.FieldType_4byte)).
|
|
|
+ OrderBy("age").Find()
|
|
|
+ ok := sort.SliceIsSorted(rows, func(i, j int) bool {
|
|
|
+ return findUint64(rows[i], "age") < findUint64(rows[j], "age")
|
|
|
+ })
|
|
|
+ return int64(len(rows)), fmt.Sprintf("sorted=%v", ok)
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("ORDER BY age DESC (内存排序)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.Ge("score", int32(990), types.FieldType_4byte)).
|
|
|
+ OrderBy("age", true).Find()
|
|
|
+ ok := sort.SliceIsSorted(rows, func(i, j int) bool {
|
|
|
+ return findUint64(rows[i], "age") > findUint64(rows[j], "age")
|
|
|
+ })
|
|
|
+ return int64(len(rows)), fmt.Sprintf("sorted=%v", ok)
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("ORDER BY score ASC, age DESC (多字段)", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().Where(DB.And(
|
|
|
+ DB.Ge("score", int32(995), types.FieldType_4byte),
|
|
|
+ DB.Le("score", int32(999), types.FieldType_4byte),
|
|
|
+ )).OrderBy("score").OrderBy("age", true).Find()
|
|
|
+ return int64(len(rows)), ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 分组模拟 ====================
|
|
|
+ section("8. 分组统计 (Scan 流式)")
|
|
|
+ timed("GROUP BY age → COUNT(*) (流式 Scan)", func() (int64, string) {
|
|
|
+ groups := make(map[uint64]int64)
|
|
|
+ table.Query().Scan(func(row []types.FieldValue) bool {
|
|
|
+ age := findUint64(row, "age")
|
|
|
+ groups[age]++
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ return int64(len(groups)), fmt.Sprintf("%d groups", len(groups))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("GROUP BY age → AVG(score) (流式 Scan)", func() (int64, string) {
|
|
|
+ type agg struct{ sum float64; cnt int64 }
|
|
|
+ groups := make(map[uint64]*agg)
|
|
|
+ table.Query().Scan(func(row []types.FieldValue) bool {
|
|
|
+ age := findUint64(row, "age")
|
|
|
+ score := findUint64(row, "score")
|
|
|
+ if groups[age] == nil {
|
|
|
+ groups[age] = &agg{}
|
|
|
+ }
|
|
|
+ groups[age].sum += float64(score)
|
|
|
+ groups[age].cnt++
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ return int64(len(groups)), fmt.Sprintf("%d groups", len(groups))
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 更新 ====================
|
|
|
+ section("9. 批量更新 (改)")
|
|
|
+
|
|
|
+ timed("UPDATE score=0 WHERE id>=1 AND id<=N/10", func() (int64, string) {
|
|
|
+ cnt, err := table.Update().Set("score", int32(0)).Where(DB.And(
|
|
|
+ DB.Ge("id", uint64(1), types.FieldType_8byte),
|
|
|
+ DB.Le("id", uint64(N/10), types.FieldType_8byte),
|
|
|
+ )).Exec()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err.Error()
|
|
|
+ }
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证更新
|
|
|
+ timed("验证: score=0 的行数", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Where(DB.Eq("score", int32(0), types.FieldType_4byte)).Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("UPDATE age=99 WHERE age=18 (二级索引条件)", func() (int64, string) {
|
|
|
+ cnt, err := table.Update().Set("age", uint64(99)).Where(
|
|
|
+ DB.Eq("age", uint64(18), types.FieldType_8byte),
|
|
|
+ ).Exec()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err.Error()
|
|
|
+ }
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 删除 ====================
|
|
|
+ section("10. 批量删除 (删)")
|
|
|
+
|
|
|
+ timed("DELETE WHERE id>N*0.9", func() (int64, string) {
|
|
|
+ cnt, err := table.Delete().Where(
|
|
|
+ DB.Gt("id", uint64(float64(N)*0.9), types.FieldType_8byte),
|
|
|
+ ).Exec()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err.Error()
|
|
|
+ }
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("验证: 删除后总行数", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("DELETE WHERE age=97 (二级索引条件)", func() (int64, string) {
|
|
|
+ cnt, err := table.Delete().Where(
|
|
|
+ DB.Eq("age", uint64(97), types.FieldType_8byte),
|
|
|
+ ).Exec()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err.Error()
|
|
|
+ }
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("最终: 总行数", func() (int64, string) {
|
|
|
+ cnt, _ := table.Query().Count()
|
|
|
+ return cnt, ""
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== Offset + Limit ====================
|
|
|
+ section("11. OFFSET + LIMIT 分页")
|
|
|
+
|
|
|
+ timed("PK 分页: OFFSET N/2 LIMIT 10", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("id").Offset(N/2).Limit(10).Find()
|
|
|
+ if len(rows) == 0 { return 0, "empty" }
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_id=%d", findUint64(rows[0], "id"))
|
|
|
+ })
|
|
|
+
|
|
|
+ timed("SEC 分页: OFFSET N/10 LIMIT 20", func() (int64, string) {
|
|
|
+ rows, _ := table.Query().OrderBy("age").Offset(N/10).Limit(20).Find()
|
|
|
+ if len(rows) == 0 { return 0, "empty" }
|
|
|
+ return int64(len(rows)), fmt.Sprintf("first_age=%d", findUint64(rows[0], "age"))
|
|
|
+ })
|
|
|
+
|
|
|
+ // ==================== 汇总报告 ====================
|
|
|
+ section("性能汇总报告")
|
|
|
+ fmt.Printf("\n %-52s %12s %12s %12s %s\n", "操作", "行数", "耗时", "吞吐(rows/s)", "备注")
|
|
|
+ fmt.Printf(" %s\n", strings.Repeat("-", 120))
|
|
|
+ totalTime := time.Duration(0)
|
|
|
+ for _, r := range results {
|
|
|
+ qps := "N/A"
|
|
|
+ if r.duration.Seconds() >= 0.001 {
|
|
|
+ qps = fmt.Sprintf("%.0f", float64(r.count)/r.duration.Seconds())
|
|
|
+ }
|
|
|
+ fmt.Printf(" %-52s %12s %12v %12s %s\n",
|
|
|
+ r.name, formatNum(int(r.count)), r.duration.Round(time.Millisecond), qps, r.detail)
|
|
|
+ totalTime += r.duration
|
|
|
+ }
|
|
|
+ fmt.Printf("\n 总耗时: %v\n", totalTime.Round(time.Millisecond))
|
|
|
+
|
|
|
+ // 文件信息
|
|
|
+ if fi, err := os.Stat(dbName); err == nil {
|
|
|
+ fmt.Printf(" 数据库文件大小: %s\n", formatBytes(fi.Size()))
|
|
|
+ }
|
|
|
+ fmt.Println("\n=== 基准测试完成 ===")
|
|
|
+}
|
|
|
+
|
|
|
+// ======================== 工具函数 ========================
|
|
|
+
|
|
|
+func formatNum(n int) string {
|
|
|
+ if n < 0 {
|
|
|
+ return "-" + formatNum(-n)
|
|
|
+ }
|
|
|
+ s := fmt.Sprintf("%d", n)
|
|
|
+ var result []byte
|
|
|
+ for i, c := range s {
|
|
|
+ if i > 0 && (len(s)-i)%3 == 0 {
|
|
|
+ result = append(result, ',')
|
|
|
+ }
|
|
|
+ result = append(result, byte(c))
|
|
|
+ }
|
|
|
+ return string(result)
|
|
|
+}
|
|
|
+
|
|
|
+func formatBytes(b int64) string {
|
|
|
+ const unit = 1024
|
|
|
+ if b < unit {
|
|
|
+ return fmt.Sprintf("%d B", b)
|
|
|
+ }
|
|
|
+ div, exp := int64(unit), 0
|
|
|
+ for n := b / unit; n >= unit; n /= unit {
|
|
|
+ div *= unit
|
|
|
+ exp++
|
|
|
+ }
|
|
|
+ return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
|
|
|
+}
|