Files
srdb/DOCS.md
bourdon 7d2bb4745c 重构:清理项目结构和完善文档
- 添加完整的 DOCS.md 文档(1376 行)
- 更新 README.md,增强项目说明
- 清理临时示例和测试数据
- 删除诊断工具(已完成测试)
- 为 webui 示例准备测试数据
- 优化 .gitignore 配置
- 增强 Query 和 Schema 功能
- 改进 SSTable 编码处理
2025-10-10 18:36:22 +08:00

1377 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SRDB 完整文档
## 目录
- [概述](#概述)
- [安装](#安装)
- [快速开始](#快速开始)
- [类型系统](#类型系统)
- [Schema 管理](#schema-管理)
- [数据操作](#数据操作)
- [查询 API](#查询-api)
- [Scan 方法](#scan-方法)
- [Object 和 Array 类型](#object-和-array-类型)
- [索引](#索引)
- [事务和并发](#事务和并发)
- [性能优化](#性能优化)
- [错误处理](#错误处理)
- [最佳实践](#最佳实践)
- [架构细节](#架构细节)
---
## 概述
SRDB (Simple Row Database) 是一个用 Go 编写的高性能嵌入式数据库,采用 LSM-Tree 架构,专为时序数据和高并发写入场景设计。
### 核心特性
- **高性能写入** - 基于 WAL + MemTable支持 200K+ 写入/秒
- **灵活的 Schema** - 支持 21 种数据类型包括复杂类型Object、Array
- **强大的查询** - 链式 API支持 18 种操作符和复合条件
- **智能 Scan** - 自动扫描到结构体,完整支持复杂类型
- **自动 Compaction** - 后台智能合并,优化存储空间
- **索引支持** - 二级索引加速查询
- **MVCC** - 多版本并发控制,无锁读
### 适用场景
- 时序数据存储(日志、指标、事件)
- 嵌入式数据库(单机应用)
- 高并发写入场景
- 需要复杂数据类型的场景JSON 风格数据)
---
## 安装
```bash
go get code.tczkiot.com/wlw/srdb
```
**最低要求**
- Go 1.21+
- 支持平台Linux、macOS、Windows
---
## 快速开始
### 基本使用流程
```go
package main
import (
"fmt"
"log"
"code.tczkiot.com/wlw/srdb"
)
func main() {
// 1. 打开数据库
db, err := srdb.Open("./data")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 2. 定义 Schema
schema, err := srdb.NewSchema("users", []srdb.Field{
{Name: "id", Type: srdb.Uint32, Indexed: true, Comment: "用户ID"},
{Name: "name", Type: srdb.String, Comment: "用户名"},
{Name: "email", Type: srdb.String, Indexed: true, Comment: "邮箱"},
{Name: "age", Type: srdb.Int32, Comment: "年龄"},
{Name: "settings", Type: srdb.Object, Comment: "设置map"},
{Name: "tags", Type: srdb.Array, Comment: "标签slice"},
})
if err != nil {
log.Fatal(err)
}
// 3. 创建表
table, err := db.CreateTable("users", schema)
if err != nil {
log.Fatal(err)
}
// 4. 插入数据
err = table.Insert(map[string]any{
"id": uint32(1),
"name": "Alice",
"email": "alice@example.com",
"age": int32(25),
"settings": map[string]any{
"theme": "dark",
"lang": "zh-CN",
},
"tags": []any{"golang", "database"},
})
if err != nil {
log.Fatal(err)
}
// 5. 查询数据
var users []User
err = table.Query().
Eq("name", "Alice").
Gte("age", 18).
Scan(&users)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d users\n", len(users))
}
type User struct {
ID uint32 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int32 `json:"age"`
Settings map[string]string `json:"settings"`
Tags []string `json:"tags"`
}
```
---
## 类型系统
SRDB 支持 **21 种数据类型**,精确映射到 Go 的基础类型。
### 整数类型
#### 有符号整数5 种)
| 类型 | Go 类型 | 范围 | 存储大小 |
|------|---------|------|----------|
| `Int` | `int` | 平台相关 | 4/8 字节 |
| `Int8` | `int8` | -128 ~ 127 | 1 字节 |
| `Int16` | `int16` | -32,768 ~ 32,767 | 2 字节 |
| `Int32` | `int32` | -2^31 ~ 2^31-1 | 4 字节 |
| `Int64` | `int64` | -2^63 ~ 2^63-1 | 8 字节 |
#### 无符号整数5 种)
| 类型 | Go 类型 | 范围 | 存储大小 |
|------|---------|------|----------|
| `Uint` | `uint` | 平台相关 | 4/8 字节 |
| `Uint8` | `uint8` | 0 ~ 255 | 1 字节 |
| `Uint16` | `uint16` | 0 ~ 65,535 | 2 字节 |
| `Uint32` | `uint32` | 0 ~ 2^32-1 | 4 字节 |
| `Uint64` | `uint64` | 0 ~ 2^64-1 | 8 字节 |
### 浮点数类型2 种)
| 类型 | Go 类型 | 精度 | 存储大小 |
|------|---------|------|----------|
| `Float32` | `float32` | 单精度 | 4 字节 |
| `Float64` | `float64` | 双精度 | 8 字节 |
### 基础类型4 种)
| 类型 | Go 类型 | 说明 | 存储大小 |
|------|---------|------|----------|
| `String` | `string` | UTF-8 字符串 | 变长 |
| `Bool` | `bool` | 布尔值 | 1 字节 |
| `Byte` | `byte` | 字节uint8 别名) | 1 字节 |
| `Rune` | `rune` | Unicode 字符int32 别名) | 4 字节 |
### 特殊类型2 种)
| 类型 | Go 类型 | 说明 | 依赖 |
|------|---------|------|------|
| `Time` | `time.Time` | 时间戳 | 标准库 |
| `Decimal` | `decimal.Decimal` | 高精度十进制 | shopspring/decimal |
### 复杂类型2 种)
| 类型 | Go 类型 | 说明 | 编码 |
|------|---------|------|------|
| `Object` | `map[string]xxx`, `struct{}`, `*struct{}` | JSON 对象 | JSON |
| `Array` | `[]xxx` | 数组/切片 | JSON |
### 类型选择建议
```go
// ✓ 推荐:根据数据范围选择合适的类型
type Sensor struct {
DeviceID uint32 `srdb:"device_id"` // 0 ~ 42亿
Temperature float32 `srdb:"temperature"` // 单精度足够
Humidity uint8 `srdb:"humidity"` // 0-100
Status bool `srdb:"status"` // 布尔状态
}
// ✗ 避免:盲目使用大类型
type Sensor struct {
DeviceID int64 // 浪费 4 字节
Temperature float64 // 浪费 4 字节
Humidity int64 // 浪费 7 字节!
Status int64 // 浪费 7 字节!
}
```
### 类型转换规则
SRDB 在插入数据时会进行智能类型转换:
1. **相同类型** - 直接接受
2. **兼容类型** - 自动转换(如 `int``int32`
3. **类型提升** - 整数 → 浮点(如 `int32(42)``float64(42.0)`
4. **JSON 兼容** - `float64` → 整数(需为整数值,用于 JSON 反序列化)
5. **负数检查** - 负数不能转为无符号类型
```go
// 示例:类型转换
schema, _ := srdb.NewSchema("test", []srdb.Field{
{Name: "count", Type: srdb.Int64},
{Name: "ratio", Type: srdb.Float32},
})
// ✓ 允许
table.Insert(map[string]any{
"count": uint32(100), // uint32 → int64
"ratio": int32(42), // int32 → float32 (42.0)
})
// ✗ 拒绝
table.Insert(map[string]any{
"count": int32(-1), // 负数不能转为 uint
})
```
---
## Schema 管理
### 创建 Schema
#### 方式 1手动定义
```go
schema, err := srdb.NewSchema("users", []srdb.Field{
{
Name: "id",
Type: srdb.Uint32,
Indexed: true,
Nullable: false,
Comment: "用户ID",
},
{
Name: "name",
Type: srdb.String,
Indexed: false,
Nullable: false,
Comment: "用户名",
},
{
Name: "email",
Type: srdb.String,
Indexed: true,
Nullable: true,
Comment: "邮箱(可选)",
},
})
```
#### 方式 2从结构体自动生成
```go
type User struct {
ID uint32 `srdb:"field:id;indexed;comment:用户ID"`
Name string `srdb:"field:name;comment:用户名"`
Email *string `srdb:"field:email;indexed;comment:邮箱(可选)"`
Age *int32 `srdb:"field:age;comment:年龄(可选)"`
}
fields, err := srdb.StructToFields(User{})
if err != nil {
log.Fatal(err)
}
schema, err := srdb.NewSchema("users", fields)
if err != nil {
log.Fatal(err)
}
```
### Field 结构
```go
type Field struct {
Name string // 字段名(必填)
Type FieldType // 字段类型(必填)
Indexed bool // 是否创建索引
Nullable bool // 是否允许 NULL指针类型自动推断
Comment string // 字段注释
}
```
### Schema Tag 语法
```go
`srdb:"field:字段名;indexed;nullable;comment:注释"`
```
**支持的选项**
- `field:name` - 指定字段名(默认使用 snake_case
- `indexed` - 创建索引
- `nullable` - 允许 NULL仅用于指针类型
- `comment:文本` - 字段注释
**示例**
```go
type User struct {
// 基本字段
ID uint32 `srdb:"field:id;indexed;comment:用户ID"`
Name string `srdb:"field:name;comment:用户名"`
// Nullable 字段(使用指针)
Email *string `srdb:"field:email;indexed;comment:邮箱(可选)"`
Phone *string `srdb:"field:phone;comment:手机号(可选)"`
// 复杂类型
Settings map[string]string `srdb:"field:settings;comment:设置"`
Tags []string `srdb:"field:tags;comment:标签"`
// 忽略字段
Internal string `srdb:"-"`
}
```
### Schema 验证
Schema 在创建时会进行严格验证:
1. **字段名唯一性** - 不能重复
2. **类型有效性** - 必须是支持的类型
3. **Nullable 规则** - 只有指针类型可以标记 nullable
4. **保留字段** - 不能使用 `_seq`, `_time` 等保留字段
```go
// ✗ 错误示例
schema, err := srdb.NewSchema("test", []srdb.Field{
{Name: "id", Type: srdb.String},
{Name: "id", Type: srdb.Int64}, // 错误:字段名重复
})
// ✗ 错误示例
schema, err := srdb.NewSchema("test", []srdb.Field{
{Name: "email", Type: srdb.String, Nullable: true}, // 错误:非指针类型不能 nullable
})
```
---
## 数据操作
### 插入数据
```go
// 单条插入
err := table.Insert(map[string]any{
"id": uint32(1),
"name": "Alice",
"email": "alice@example.com",
"age": int32(25),
})
// 批量插入
users := []map[string]any{
{"id": uint32(1), "name": "Alice", "age": int32(25)},
{"id": uint32(2), "name": "Bob", "age": int32(30)},
{"id": uint32(3), "name": "Charlie", "age": int32(35)},
}
for _, user := range users {
if err := table.Insert(user); err != nil {
log.Printf("插入失败: %v", err)
}
}
```
**注意事项**
- 插入的数据会立即写入 WAL
- 字段类型会自动验证和转换
- 缺失的 nullable 字段会设为 NULL
- 缺失的非 nullable 字段会报错
### 获取数据
```go
// 通过序列号获取
row, err := table.Get(seq)
if err != nil {
log.Fatal(err)
}
fmt.Println(row.Seq) // 序列号
fmt.Println(row.Time) // 时间戳
fmt.Println(row.Data) // 数据 (map[string]any)
```
### 更新数据
SRDB 是 **append-only** 架构,更新操作会创建新版本:
```go
// 更新数据
err := table.Update(seq, map[string]any{
"age": int32(26),
})
// 等价于:
newData := existingData
newData["age"] = int32(26)
table.Insert(newData)
```
### 删除数据
```go
// 标记删除(软删除)
err := table.Delete(seq)
// 物理删除在 Compaction 时进行
```
---
## 查询 API
SRDB 提供流畅的链式查询 API。
### 基本查询
```go
// 等值查询
rows, err := table.Query().Eq("name", "Alice").Rows()
// 不等于
rows, err := table.Query().NotEq("status", "deleted").Rows()
// 大于/小于
rows, err := table.Query().
Gt("age", 18).
Lt("age", 60).
Rows()
// 大于等于/小于等于
rows, err := table.Query().
Gte("score", 60).
Lte("score", 100).
Rows()
```
### 集合查询
```go
// IN
rows, err := table.Query().
In("status", []any{"active", "pending", "processing"}).
Rows()
// NOT IN
rows, err := table.Query().
NotIn("role", []any{"banned", "suspended"}).
Rows()
// BETWEEN
rows, err := table.Query().
Between("age", 18, 60).
Rows()
// NOT BETWEEN
rows, err := table.Query().
NotBetween("price", 1000, 5000).
Rows()
```
### 字符串查询
```go
// 包含子串
rows, err := table.Query().Contains("message", "error").Rows()
// 不包含
rows, err := table.Query().NotContains("message", "debug").Rows()
// 前缀匹配
rows, err := table.Query().StartsWith("email", "admin@").Rows()
// 后缀匹配
rows, err := table.Query().EndsWith("filename", ".log").Rows()
```
### NULL 查询
```go
// IS NULL
rows, err := table.Query().IsNull("email").Rows()
// IS NOT NULL
rows, err := table.Query().NotNull("phone").Rows()
```
### 复合条件
```go
// AND默认
rows, err := table.Query().
Eq("status", "active").
Gte("age", 18).
NotNull("email").
Rows()
// OR
rows, err := table.Query().
Where(srdb.Or(
srdb.Eq("role", "admin"),
srdb.Eq("role", "moderator"),
)).
Rows()
// 复杂组合
rows, err := table.Query().
Where(srdb.And(
srdb.Eq("status", "active"),
srdb.Or(
srdb.Gte("age", 18),
srdb.Eq("verified", true),
),
srdb.Not(srdb.Eq("role", "banned")),
)).
Rows()
```
### 字段选择
```go
// 只查询指定字段(性能优化)
rows, err := table.Query().
Select("id", "name", "email").
Eq("status", "active").
Rows()
// 遍历结果
for rows.Next() {
row := rows.Row()
data := row.Data() // 只包含 id, name, email
fmt.Println(data)
}
```
### 结果获取
```go
// 游标模式(惰性加载,推荐)
rows, err := table.Query().Rows()
defer rows.Close()
for rows.Next() {
row := rows.Row()
fmt.Println(row.Data())
}
// 检查错误
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// 获取第一条
row, err := table.Query().First()
// 获取最后一条
row, err := table.Query().Last()
// 收集所有结果(内存消耗大)
data := rows.Collect()
// 获取总数
count := rows.Count()
```
### 操作符完整列表
| 方法 | 操作符 | 说明 | 示例 |
|------|--------|------|------|
| `Eq(field, value)` | `=` | 等于 | `.Eq("status", "active")` |
| `NotEq(field, value)` | `!=` | 不等于 | `.NotEq("role", "guest")` |
| `Lt(field, value)` | `<` | 小于 | `.Lt("age", 18)` |
| `Gt(field, value)` | `>` | 大于 | `.Gt("score", 60)` |
| `Lte(field, value)` | `<=` | 小于等于 | `.Lte("price", 100)` |
| `Gte(field, value)` | `>=` | 大于等于 | `.Gte("count", 10)` |
| `In(field, values)` | `IN` | 在列表中 | `.In("status", []any{"a", "b"})` |
| `NotIn(field, values)` | `NOT IN` | 不在列表中 | `.NotIn("role", []any{"banned"})` |
| `Between(field, min, max)` | `BETWEEN` | 在范围内 | `.Between("age", 18, 60)` |
| `NotBetween(field, min, max)` | `NOT BETWEEN` | 不在范围内 | `.NotBetween("price", 0, 10)` |
| `Contains(field, pattern)` | `CONTAINS` | 包含子串 | `.Contains("message", "error")` |
| `NotContains(field, pattern)` | `NOT CONTAINS` | 不包含 | `.NotContains("log", "debug")` |
| `StartsWith(field, prefix)` | `STARTS WITH` | 以...开头 | `.StartsWith("email", "admin")` |
| `NotStartsWith(field, prefix)` | `NOT STARTS WITH` | 不以...开头 | `.NotStartsWith("name", "test")` |
| `EndsWith(field, suffix)` | `ENDS WITH` | 以...结尾 | `.EndsWith("file", ".log")` |
| `NotEndsWith(field, suffix)` | `NOT ENDS WITH` | 不以...结尾 | `.NotEndsWith("path", ".tmp")` |
| `IsNull(field)` | `IS NULL` | 为空 | `.IsNull("email")` |
| `NotNull(field)` | `IS NOT NULL` | 不为空 | `.NotNull("phone")` |
---
## Scan 方法
SRDB 提供智能的 Scan 方法,可以将查询结果直接扫描到 Go 结构体。
### Row.Scan() - 扫描单行
```go
row, err := table.Query().Eq("id", 1).First()
if err != nil {
log.Fatal(err)
}
var user User
err = row.Scan(&user)
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name) // "Alice"
```
### Rows.Scan() - 智能扫描
**Rows.Scan 会自动判断目标类型**
- 如果目标是**切片** → 扫描所有行
- 如果目标是**结构体** → 只扫描第一行
```go
// 扫描多行到切片
rows, _ := table.Query().Rows()
defer rows.Close()
var users []User
err := rows.Scan(&users)
// 扫描单行到结构体(智能判断)
rows2, _ := table.Query().Eq("id", 1).Rows()
defer rows2.Close()
var user User
err := rows2.Scan(&user) // 自动只扫描第一行
```
### QueryBuilder.Scan() - 最简洁的方式
```go
// 扫描多行
var users []User
err := table.Query().Scan(&users)
// 扫描单行
var user User
err := table.Query().Eq("id", 1).Scan(&user)
// 带条件扫描
var activeUsers []User
err := table.Query().
Eq("status", "active").
Gte("age", 18).
Scan(&activeUsers)
```
### 部分字段扫描
```go
// 定义简化的结构体
type UserBrief struct {
Name string `json:"name"`
Email string `json:"email"`
}
// 只扫描指定字段
var briefs []UserBrief
err := table.Query().
Select("name", "email").
Scan(&briefs)
// 结果只包含 name 和 email 字段
```
### 复杂类型扫描
```go
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Settings map[string]string `json:"settings"` // Object
Tags []string `json:"tags"` // Array
Metadata map[string]any `json:"metadata"` // Object with any
Scores []int `json:"scores"` // Array of int
}
var user User
err := table.Query().Eq("name", "Alice").Scan(&user)
// 访问复杂类型
fmt.Println(user.Settings["theme"]) // "dark"
fmt.Println(user.Tags[0]) // "golang"
fmt.Println(user.Metadata["version"]) // "1.0"
fmt.Println(user.Scores[0]) // 95
```
### Scan 的工作原理
1. **Row.Scan**
- 使用 `json.Marshal` 将 row.Data() 转为 JSON
- 使用 `json.Unmarshal` 解码到目标结构体
- 应用字段过滤(如果调用了 Select
2. **Rows.Scan**
- 使用 `reflect` 检查目标类型
- 如果是切片:调用 Collect() 获取所有行,然后 JSON 转换
- 如果是结构体:调用 First() 获取第一行,然后调用 Row.Scan
3. **QueryBuilder.Scan**
- 直接调用 Rows.Scan
---
## Object 和 Array 类型
SRDB 原生支持复杂的数据类型,可以存储 JSON 风格的对象和数组。
### Object 类型
Object 类型可以存储:
- `map[string]string`
- `map[string]any`
- `struct{}`
- `*struct{}`
#### 定义 Object 字段
```go
type User struct {
Settings map[string]string `srdb:"field:settings"`
Metadata map[string]any `srdb:"field:metadata"`
}
// 或手动定义
schema, _ := srdb.NewSchema("users", []srdb.Field{
{Name: "settings", Type: srdb.Object, Comment: "用户设置"},
{Name: "metadata", Type: srdb.Object, Comment: "元数据"},
})
```
#### 插入 Object 数据
```go
err := table.Insert(map[string]any{
"name": "Alice",
"settings": map[string]any{
"theme": "dark",
"language": "zh-CN",
"fontSize": "14px",
},
"metadata": map[string]any{
"version": "1.0",
"author": "Alice",
"tags": []string{"admin", "verified"}, // 嵌套数组
},
})
```
#### 查询和使用 Object
```go
var user User
table.Query().Eq("name", "Alice").Scan(&user)
// 访问 Object 字段
theme := user.Settings["theme"] // "dark"
version := user.Metadata["version"] // "1.0"
// 类型断言for map[string]any
if tags, ok := user.Metadata["tags"].([]any); ok {
fmt.Println(tags[0]) // "admin"
}
```
### Array 类型
Array 类型可以存储任意切片:
- `[]string`
- `[]int`
- `[]any`
- `[]struct{}`
#### 定义 Array 字段
```go
type User struct {
Tags []string `srdb:"field:tags"`
Scores []int `srdb:"field:scores"`
Items []any `srdb:"field:items"`
}
// 或手动定义
schema, _ := srdb.NewSchema("users", []srdb.Field{
{Name: "tags", Type: srdb.Array, Comment: "标签"},
{Name: "scores", Type: srdb.Array, Comment: "分数"},
})
```
#### 插入 Array 数据
```go
err := table.Insert(map[string]any{
"name": "Alice",
"tags": []any{"golang", "database", "lsm-tree"},
"scores": []any{95, 88, 92},
"items": []any{
"item1",
123,
true,
map[string]any{"nested": "value"}, // 嵌套对象
},
})
```
#### 查询和使用 Array
```go
var user User
table.Query().Eq("name", "Alice").Scan(&user)
// 访问 Array 字段
fmt.Println(len(user.Tags)) // 3
fmt.Println(user.Tags[0]) // "golang"
fmt.Println(user.Scores[1]) // 88
// 遍历
for _, tag := range user.Tags {
fmt.Println(tag)
}
// 计算平均分
total := 0
for _, score := range user.Scores {
total += score
}
avg := float64(total) / float64(len(user.Scores))
```
### 嵌套结构
Object 和 Array 可以任意嵌套:
```go
type Config struct {
Server string `json:"server"`
Port int `json:"port"`
Features map[string]bool `json:"features"` // 嵌套 Object
}
type Application struct {
Name string `json:"name"`
Config Config `json:"config"` // 嵌套结构体
Servers []string `json:"servers"` // Array
Tags []string `json:"tags"` // Array
Meta map[string]any `json:"meta"` // Object
}
// 插入嵌套数据
table.Insert(map[string]any{
"name": "MyApp",
"config": map[string]any{
"server": "localhost",
"port": 8080,
"features": map[string]any{
"cache": true,
"logging": false,
},
},
"servers": []any{"server1", "server2", "server3"},
"tags": []any{"production", "v1.0"},
"meta": map[string]any{
"deployedAt": time.Now().Format(time.RFC3339),
"region": "us-west",
"replicas": 3,
},
})
// 查询和访问
var app Application
table.Query().Eq("name", "MyApp").Scan(&app)
fmt.Println(app.Config.Server) // "localhost"
fmt.Println(app.Config.Features["cache"]) // true
fmt.Println(app.Servers[0]) // "server1"
fmt.Println(app.Meta["region"]) // "us-west"
```
### 空值处理
```go
// 插入空 Object 和 Array
table.Insert(map[string]any{
"name": "Charlie",
"settings": map[string]any{}, // 空 Object
"tags": []any{}, // 空 Array
})
// 查询
var user User
table.Query().Eq("name", "Charlie").Scan(&user)
// 安全检查
if len(user.Settings) == 0 {
fmt.Println("设置为空")
}
if len(user.Tags) == 0 {
fmt.Println("没有标签")
}
```
### 存储格式
- **编码方式**JSON
- **存储格式**`[length: uint32][JSON data]`
- **零值**Object 为 `{}`Array 为 `[]`
- **性能**JSON 编码/解码有一定开销,但保证了灵活性
---
## 索引
SRDB 支持二级索引,可以显著加速查询性能。
### 创建索引
```go
// 在 Schema 中标记索引
schema, _ := srdb.NewSchema("users", []srdb.Field{
{Name: "id", Type: srdb.Uint32, Indexed: true}, // 创建索引
{Name: "email", Type: srdb.String, Indexed: true}, // 创建索引
{Name: "name", Type: srdb.String, Indexed: false}, // 不创建索引
})
```
### 索引的工作原理
1. **自动创建**:创建表时,所有标记为 `Indexed: true` 的字段会自动创建索引
2. **自动更新**:插入/更新数据时,索引会自动更新
3. **查询优化**:使用 `Eq()` 查询索引字段时,会自动使用索引
```go
// 使用索引(快速)
rows, _ := table.Query().Eq("email", "alice@example.com").Rows()
// 不使用索引(全表扫描)
rows, _ := table.Query().Contains("name", "Alice").Rows()
```
### 索引适用场景
**适合创建索引**
- ✅ 经常用于等值查询的字段(`Eq`
- ✅ 高基数字段unique 或接近 unique
- ✅ 查询频繁的字段
**不适合创建索引**
- ❌ 低基数字段(如性别、状态等)
- ❌ 很少查询的字段
- ❌ 频繁更新的字段
- ❌ Object 和 Array 类型字段
### 索引性能
| 操作 | 无索引 | 有索引 | 提升 |
|------|--------|--------|------|
| 等值查询 (Eq) | O(N) | O(log N) | ~1000x |
| 范围查询 (Gt/Lt) | O(N) | O(N) | 无提升 |
| 模糊查询 (Contains) | O(N) | O(N) | 无提升 |
---
## 事务和并发
### 并发控制
SRDB 使用 **MVCC (多版本并发控制)** 实现无锁并发读写:
- **写入**:追加到 WAL 和 MemTable使用互斥锁保护
- **读取**:无锁读取,读取的是快照版本
- **Compaction**:后台异步执行,不阻塞读写
```go
// 多个 goroutine 并发写入
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
table.Insert(map[string]any{
"id": uint32(id),
"name": fmt.Sprintf("user_%d", id),
})
}(i)
}
wg.Wait()
// 多个 goroutine 并发读取
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
rows, _ := table.Query().Rows()
defer rows.Close()
for rows.Next() {
_ = rows.Row()
}
}()
}
wg.Wait()
```
### 事务支持
⚠️ **当前版本不支持显式事务**,但保证:
- 单条写入的原子性(通过 WAL
- 数据持久性WAL fsync
- 崩溃恢复WAL 重放)
未来版本计划支持:
- [ ] 显式事务 API
- [ ] 批量操作的原子性
- [ ] ACID 保证
---
## 性能优化
### 写入优化
**1. 批量写入**
```go
// ✓ 好:批量写入,减少 fsync 次数
for i := 0; i < 1000; i++ {
table.Insert(data[i])
}
// ✗ 避免:每次都打开关闭数据库
for i := 0; i < 1000; i++ {
db, _ := srdb.Open("./data")
table.Insert(data[i])
db.Close()
}
```
**2. 调整 MemTable 大小**
```go
// 默认 64MB可以根据内存调整
// 更大的 MemTable = 更少的 flush但占用更多内存
```
### 查询优化
**1. 使用索引**
```go
// ✓ 好:使用索引字段查询
rows, _ := table.Query().Eq("email", "alice@example.com").Rows()
// ✗ 避免:全表扫描
rows, _ := table.Query().Contains("name", "Alice").Rows()
```
**2. 字段选择**
```go
// ✓ 好:只查询需要的字段
rows, _ := table.Query().Select("id", "name").Rows()
// ✗ 避免:查询所有字段
rows, _ := table.Query().Rows()
```
**3. 使用游标模式**
```go
// ✓ 好:惰性加载,节省内存
rows, _ := table.Query().Rows()
defer rows.Close()
for rows.Next() {
process(rows.Row())
}
// ✗ 避免:一次性加载所有数据
data := rows.Collect() // 内存消耗大
```
### 存储优化
**1. 定期 Compaction**
Compaction 会自动触发,但可以手动触发:
```go
// 手动触发 Compaction阻塞
err := table.Compact()
```
**2. 选择合适的类型**
```go
// ✓ 好:根据数据范围选择类型
type Sensor struct {
DeviceID uint32 // 0 ~ 42亿4字节
Value float32 // 单精度4字节
}
// ✗ 避免:使用过大的类型
type Sensor struct {
DeviceID int64 // 8字节浪费4字节
Value float64 // 8字节浪费4字节
}
```
### 内存优化
**1. 及时关闭游标**
```go
// ✓ 好:使用 defer 确保关闭
rows, _ := table.Query().Rows()
defer rows.Close()
// ✗ 避免:忘记关闭
rows, _ := table.Query().Rows()
// ... 使用 rows
// 忘记调用 rows.Close()
```
**2. 避免大量缓存**
```go
// ✗ 避免:缓存大量数据
var cache []map[string]any
rows, _ := table.Query().Rows()
cache = rows.Collect() // 内存消耗大
// ✓ 好:流式处理
rows, _ := table.Query().Rows()
defer rows.Close()
for rows.Next() {
process(rows.Row()) // 逐条处理
}
```
---
## 错误处理
SRDB 使用统一的错误码系统。
### 错误类型
```go
// 创建错误
err := srdb.NewError(srdb.ErrCodeTableNotFound, nil)
// 包装错误
err := srdb.WrapError(baseErr, "failed to insert: %v", data)
// 判断错误类型
if srdb.IsNotFound(err) {
// 处理未找到错误
}
if srdb.IsCorrupted(err) {
// 处理数据损坏错误
}
```
### 常见错误码
| 错误码 | 说明 | 处理方式 |
|--------|------|----------|
| `ErrCodeNotFound` | 数据不存在 | 检查 key 是否正确 |
| `ErrCodeTableNotFound` | 表不存在 | 先创建表 |
| `ErrCodeSchemaValidation` | Schema 验证失败 | 检查字段定义 |
| `ErrCodeTypeConversion` | 类型转换失败 | 检查数据类型 |
| `ErrCodeCorrupted` | 数据损坏 | 恢复备份或重建 |
| `ErrCodeClosed` | 数据库已关闭 | 重新打开数据库 |
### 错误处理最佳实践
```go
// ✓ 好:检查并处理错误
if err := table.Insert(data); err != nil {
if srdb.IsSchemaValidation(err) {
log.Printf("数据验证失败: %v", err)
return
}
log.Printf("插入失败: %v", err)
return
}
// ✗ 避免:忽略错误
table.Insert(data) // 错误未处理
```
---
## 最佳实践
### Schema 设计
1. **选择合适的类型**
```go
// ✓ 根据数据范围选择
DeviceID uint32 // 0 ~ 42亿
Count uint8 // 0 ~ 255
```
2. **合理使用索引**
```go
// ✓ 高基数、频繁查询的字段
Email string `srdb:"indexed"`
// ✗ 低基数字段不需要索引
Gender string // 只有 2-3 个值
```
3. **Nullable 字段使用指针**
```go
Email *string `srdb:"field:email"`
Phone *string `srdb:"field:phone"`
```
### 数据插入
1. **批量插入**
```go
for _, data := range batch {
table.Insert(data)
}
```
2. **验证数据**
```go
if email == "" {
return errors.New("email required")
}
table.Insert(data)
```
### 查询优化
1. **使用索引字段**
```go
// ✓ 使用索引
table.Query().Eq("email", "alice@example.com")
// ✗ 避免全表扫描
table.Query().Contains("email", "@example.com")
```
2. **字段选择**
```go
table.Query().Select("id", "name").Rows()
```
3. **使用 Scan**
```go
var users []User
table.Query().Scan(&users)
```
### 并发访问
1. **读写分离**
```go
// 多个 goroutine 可以安全并发读
go func() {
table.Query().Rows()
}()
```
2. **写入控制**
```go
// 写入使用队列控制并发
```
---
## 架构细节
### LSM-Tree 结构
```
写入流程:
数据 → WAL持久化→ MemTable → Immutable MemTable → Level 0 SST → Compaction → Level 1-6
```
### 文件组织
```
database_dir/
├── database.meta # 数据库元数据
├── MANIFEST # 版本控制
└── table_name/
├── schema.json # 表 Schema
├── MANIFEST # 表级版本控制
├── 000001.wal # WAL 文件
├── 000001.sst # SST 文件
├── 000002.sst
└── idx_email.sst # 索引文件
```
### Compaction 策略
- **Level 0**: 文件数量 ≥ 4 触发
- **Level 1-6**: 总大小超过阈值触发
- **Score 计算**: `size / max_size` 或 `file_count / max_files`
- **文件大小**: L0=2MB, L1=10MB, L2=50MB, L3=100MB, L4+=200MB
### 性能指标
| 操作 | 性能 |
|------|------|
| 顺序写入 | ~100K ops/s |
| 随机写入 | ~50K ops/s |
| 点查询 | ~10K ops/s |
| 范围扫描 | ~1M rows/s |
| 内存使用 | < 150MB (64MB MemTable + overhead) |
---
## 附录
### 参考链接
- [GitHub 仓库](https://code.tczkiot.com/wlw/srdb)
- [API 文档](https://pkg.go.dev/code.tczkiot.com/wlw/srdb)
- [设计文档](DESIGN.md)
- [开发者指南](CLAUDE.md)
### 示例项目
- [所有类型示例](examples/all_types/)
- [Scan 方法示例](examples/scan_demo/)
- [Nullable 示例](examples/nullable/)
- [Web UI](examples/webui/)
### 许可证
MIT License - 详见 [LICENSE](LICENSE) 文件
---
**SRDB** - 简单高效可靠的嵌入式数据库 🚀