2026-03-07 19:27:44 +08:00
|
|
|
|
# StarDB
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
StarDB 是对 Go `database/sql` 的轻量封装,目标是把常见的数据库操作做得更直白:
|
|
|
|
|
|
- 少量 API 覆盖日常 CRUD、事务、批量写入、结构体映射。
|
|
|
|
|
|
- 兼容原生 `database/sql` 心智,不引入重量级依赖。
|
|
|
|
|
|
- 在可读性、可调试性和性能之间做实用平衡。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
适合:
|
|
|
|
|
|
- 想保留 SQL 控制权,但不想反复写样板代码。
|
|
|
|
|
|
- 需要轻量 ORM 映射(不是全功能 ORM)。
|
|
|
|
|
|
- 需要在生产里追踪 SQL(可选 Hook + 慢 SQL 阈值)。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
不适合:
|
|
|
|
|
|
- 需要完整领域模型关系管理、自动迁移、复杂查询 DSL 的项目。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 安装
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
go get b612.me/stardb
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
要求:
|
|
|
|
|
|
- Go `>= 1.16`
|
|
|
|
|
|
- 自行选择并导入数据库驱动(本库只封装 `database/sql`)
|
|
|
|
|
|
|
|
|
|
|
|
## 常见 DSN 示例
|
|
|
|
|
|
|
|
|
|
|
|
下面示例都可以直接用于 `db.Open(driver, dsn)`,替换为实际账号、密码、库名即可。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### MySQL(`github.com/go-sql-driver/mysql`)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
import _ "github.com/go-sql-driver/mysql"
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
dsn := "app:secret@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
|
|
|
|
|
|
if err := db.Open("mysql", dsn); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
常用参数说明:
|
|
|
|
|
|
- `charset=utf8mb4`:避免字符集问题。
|
|
|
|
|
|
- `parseTime=true`:把 `DATETIME/TIMESTAMP` 解析为 `time.Time`。
|
|
|
|
|
|
- `loc=Local`:指定时间解析时区(也可改成 `Asia/Shanghai`)。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### PostgreSQL(`github.com/lib/pq`)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
import _ "github.com/lib/pq"
|
|
|
|
|
|
|
|
|
|
|
|
dsn := "host=127.0.0.1 port=5432 user=postgres password=secret dbname=demo sslmode=disable"
|
|
|
|
|
|
if err := db.Open("postgres", dsn); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
也可以用 URL 形式:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
urlDSN := "postgres://postgres:secret@127.0.0.1:5432/demo?sslmode=disable"
|
|
|
|
|
|
if err := db.Open("postgres", urlDSN); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### SQLite(`modernc.org/sqlite`)
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
import _ "modernc.org/sqlite"
|
|
|
|
|
|
|
|
|
|
|
|
// 文件数据库
|
|
|
|
|
|
if err := db.Open("sqlite", "file:demo.db"); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 内存数据库(适合测试)
|
|
|
|
|
|
if err := db.Open("sqlite", "file::memory:?cache=shared"); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Windows 路径建议使用 `file:C:/data/demo.db` 这种写法,跨平台更稳。
|
|
|
|
|
|
|
|
|
|
|
|
## 能力概览
|
|
|
|
|
|
|
|
|
|
|
|
| 能力 | 主要 API | 说明 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 连接与连接池 | `Open` `Close` `Ping` `SetPoolConfig` | 保留原生 `sql.DB` 用法 |
|
|
|
|
|
|
| 常规查询 | `Query` `QueryContext` | 自动解析为 `StarRows` |
|
|
|
|
|
|
| 流式查询 | `QueryRaw` `ScanEach` | 大结果集不必全量进内存 |
|
|
|
|
|
|
| 流式 ORM | `ScanEachORM` | 逐行映射结构体 |
|
|
|
|
|
|
| 安全取值 | `Get*` `GetNull*` | 明确错误与 NULL 语义 |
|
|
|
|
|
|
| 结构体 ORM | `rows.Orm` | 支持单个、切片、数组映射 |
|
|
|
|
|
|
| 命名参数 | `QueryX` `ExecX` | `:field` 绑定结构体字段 |
|
|
|
|
|
|
| 结构体写入 | `Insert` `Update` | 通过 `db` tag 生成 SQL |
|
|
|
|
|
|
| 批量写入 | `BatchInsert` `BatchInsertStructs` `SetBatchInsertMaxRows` `SetBatchInsertMaxParams` | 多行插入,支持按行数/参数阈值分片 |
|
|
|
|
|
|
| 事务 | `Begin/Commit/Rollback` `WithTx` | 手动或托管事务 |
|
|
|
|
|
|
| 可观测性 | `SetSQLHooks` `SetSQLSlowThreshold` `SetSQLFingerprintEnabled` `SetSQLFingerprintMode` `SetSQLFingerprintKeepComments` `SetSQLFingerprintCounterEnabled` `SQLFingerprintCounters` `ResetSQLFingerprintCounters` `SQLHookMetaFromContext` `BatchExecMetaFromContext` | Before/After Hook,默认关闭,支持指纹策略、命中计数与批量分片元信息 |
|
|
|
|
|
|
| 方言占位符 | `SetPlaceholderStyle` | `?` / `$1,$2...` |
|
|
|
|
|
|
| 查询构建 | `QueryBuilder` | 支持 `JOIN/GROUP BY/HAVING` |
|
|
|
|
|
|
|
|
|
|
|
|
## 场景选型
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 首选 API | 说明 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 中小结果集查询 | `Query` + `rows.Orm` | 读取方便,开发效率高 |
|
|
|
|
|
|
| 大结果集查询 | `ScanEach` / `ScanEachORM` | 逐行处理,避免全量缓存 |
|
|
|
|
|
|
| 需要底层 `Scan` 控制 | `QueryRaw` | 直接返回 `*sql.Rows` |
|
|
|
|
|
|
| 批量写入 | `BatchInsert` + 分片阈值 | 控制单条 SQL 大小与参数数量 |
|
|
|
|
|
|
| SQL 可观测 | `SetSQLHooks` + `SetSQLSlowThreshold` + 指纹配置 | 支持慢 SQL、指纹、分片元信息 |
|
|
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-20 13:36:59 +08:00
|
|
|
|
"log"
|
|
|
|
|
|
|
2026-03-07 19:27:44 +08:00
|
|
|
|
"b612.me/stardb"
|
2026-03-20 13:36:59 +08:00
|
|
|
|
_ "modernc.org/sqlite"
|
2026-03-07 19:27:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type User struct {
|
2026-03-20 13:36:59 +08:00
|
|
|
|
ID int64 `db:"id"`
|
|
|
|
|
|
Name string `db:"name"`
|
|
|
|
|
|
Age int `db:"age"`
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
db := stardb.NewStarDB()
|
2026-03-20 13:36:59 +08:00
|
|
|
|
if err := db.Open("sqlite", "test.db"); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
2026-03-07 19:27:44 +08:00
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
rows, err := db.Query("SELECT id, name, age FROM users WHERE age >= ?", 18)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
|
}
|
2026-03-07 19:27:44 +08:00
|
|
|
|
defer rows.Close()
|
2026-03-20 13:36:59 +08:00
|
|
|
|
|
2026-03-07 19:27:44 +08:00
|
|
|
|
var users []User
|
2026-03-20 13:36:59 +08:00
|
|
|
|
if err := rows.Orm(&users); err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
2026-03-20 13:36:59 +08:00
|
|
|
|
|
|
|
|
|
|
log.Printf("users: %d", len(users))
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 接入流程
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
按下面顺序接入,可在开发阶段先固定运行边界:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
1. 建立连接并设置连接池。
|
|
|
|
|
|
2. 在查询路径中区分内存模式与流式模式。
|
|
|
|
|
|
3. 在批量写入路径设置分片阈值(按行数和参数数)。
|
|
|
|
|
|
4. 启用 SQL Hook、慢 SQL 阈值、指纹策略。
|
|
|
|
|
|
5. 在调用侧统一使用 `errors.Is` 判定错误类别。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
一个常用初始化示例:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
db := stardb.NewStarDB()
|
|
|
|
|
|
if err := db.Open("mysql", dsn); err != nil {
|
|
|
|
|
|
return err
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetPoolConfig(&stardb.PoolConfig{
|
|
|
|
|
|
MaxOpenConns: 25,
|
|
|
|
|
|
MaxIdleConns: 5,
|
|
|
|
|
|
ConnMaxLifetime: time.Hour,
|
|
|
|
|
|
ConnMaxIdleTime: 10 * time.Minute,
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetBatchInsertMaxRows(500)
|
|
|
|
|
|
db.SetBatchInsertMaxParams(60000)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetSQLSlowThreshold(200 * time.Millisecond)
|
|
|
|
|
|
db.SetSQLFingerprintEnabled(true)
|
|
|
|
|
|
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
|
|
|
|
|
|
db.SetSQLFingerprintKeepComments(false)
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## API 指南
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 1) 连接与连接池
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
db := stardb.NewStarDB()
|
|
|
|
|
|
_ = db.Open("mysql", "user:pass@tcp(localhost:3306)/app")
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetPoolConfig(&stardb.PoolConfig{
|
|
|
|
|
|
MaxOpenConns: 25,
|
|
|
|
|
|
MaxIdleConns: 5,
|
|
|
|
|
|
ConnMaxLifetime: time.Hour,
|
|
|
|
|
|
ConnMaxIdleTime: 10 * time.Minute,
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
也可以直接拿到底层连接:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
raw := db.DB()
|
|
|
|
|
|
raw.SetMaxOpenConns(50)
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 2) 三种查询模式
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### A. 内存模式(默认)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
`Query` 会把结果解析为 `StarRows`,适合中小结果集:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
rows, err := db.Query("SELECT * FROM users WHERE active = ?", true)
|
|
|
|
|
|
if err != nil { /* ... */ }
|
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < rows.Length(); i++ {
|
|
|
|
|
|
row := rows.Row(i)
|
|
|
|
|
|
_ = row.MustString("name")
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### B. 原生流式模式
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
`QueryRaw` 返回 `*sql.Rows`,完全按原生 `Scan` 处理:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
rawRows, err := db.QueryRaw("SELECT id, name FROM users")
|
|
|
|
|
|
if err != nil { /* ... */ }
|
|
|
|
|
|
defer rawRows.Close()
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### C. 回调流式模式(常用)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
`ScanEach` 逐行回调,避免全量缓存:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
关闭内存预读时,使用 `QueryRaw` / `ScanEach` / `ScanEachORM`,不使用 `Query`。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
err := db.ScanEach("SELECT id, name FROM users", func(row *stardb.StarResult) error {
|
|
|
|
|
|
id := row.MustInt64("id")
|
|
|
|
|
|
name := row.MustString("name")
|
|
|
|
|
|
_ = id
|
|
|
|
|
|
_ = name
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
可通过 `stardb.ErrScanStopped` 提前终止:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
count := 0
|
|
|
|
|
|
_ = db.ScanEach("SELECT * FROM users", func(row *stardb.StarResult) error {
|
|
|
|
|
|
count++
|
|
|
|
|
|
if count >= 1000 {
|
|
|
|
|
|
return stardb.ErrScanStopped
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 3) 流式 ORM(逐行映射)
|
|
|
|
|
|
|
|
|
|
|
|
`ScanEachORM` 将每行映射到结构体,再回调。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
var model User
|
|
|
|
|
|
var users []User
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
err := db.ScanEachORM("SELECT id, name, age FROM users", &model, func(target interface{}) error {
|
|
|
|
|
|
u := *(target.(*User)) // 注意拷贝一份,target 会被复用
|
|
|
|
|
|
users = append(users, u)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
同样支持 `Tx` / `Stmt`:
|
|
|
|
|
|
- `tx.ScanEachORM(...)`
|
|
|
|
|
|
- `stmt.ScanEachORM(...)`
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 4) 结果读取与 NULL 语义
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### Must 系列(无错误,失败给零值)
|
|
|
|
|
|
- `MustString` `MustInt64` `MustFloat64` `MustBool` ...
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### 安全系列(带错误)
|
|
|
|
|
|
- `GetString` `GetInt64` `GetFloat64`
|
|
|
|
|
|
- `GetNullString` `GetNullInt64` `GetNullFloat64` `GetNullBool` `GetNullTime`
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
name, err := row.GetString("name")
|
|
|
|
|
|
age, err := row.GetNullInt64("age")
|
|
|
|
|
|
if age.Valid {
|
|
|
|
|
|
// use age.Int64
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 5) ORM 映射
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
type User struct {
|
|
|
|
|
|
ID int64 `db:"id"`
|
|
|
|
|
|
Name string `db:"name"`
|
|
|
|
|
|
}
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
var u User
|
|
|
|
|
|
_ = rows.Orm(&u)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
var list []User
|
|
|
|
|
|
_ = rows.Orm(&list)
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
严格列检查(字段/SQL 变更敏感场景可开启):
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
db.SetStrictORM(true)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
若结构体 tag 大范围调整,可清理反射缓存:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
stardb.ClearReflectCache()
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 6) 命名参数绑定
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
type Filter struct {
|
|
|
|
|
|
Name string `db:"name"`
|
|
|
|
|
|
MinAge int `db:"min_age"`
|
|
|
|
|
|
}
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
f := Filter{Name: "Alice", MinAge: 18}
|
|
|
|
|
|
rows, err := db.QueryX(&f,
|
|
|
|
|
|
"SELECT * FROM users WHERE name = ? AND age >= ?",
|
|
|
|
|
|
":name", ":min_age")
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 7) 写入能力
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### Insert / Update
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
_, _ = db.Insert(&user, "users", "id") // id 作为自增字段跳过
|
|
|
|
|
|
_, _ = db.Update(&user, "users", "id") // id 作为主键
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### BatchInsert
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
columns := []string{"name", "email", "age"}
|
|
|
|
|
|
values := [][]interface{}{
|
|
|
|
|
|
{"Alice", "alice@example.com", 25},
|
|
|
|
|
|
{"Bob", "bob@example.com", 30},
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
2026-03-20 13:36:59 +08:00
|
|
|
|
_, _ = db.BatchInsert("users", columns, values)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
如需避免单条 SQL 过大(参数过多),可打开分片:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetBatchInsertMaxRows(500) // 0 或负数表示关闭分片(默认)
|
|
|
|
|
|
db.SetBatchInsertMaxParams(60000) // 0 表示自动识别常见驱动参数上限
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
分片模式下会在一个事务里按块执行,避免部分写入成功。
|
|
|
|
|
|
自动识别当前覆盖:SQLite `999`、PostgreSQL `65535`、MySQL `65535`、SQL Server `2100`。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
分片行为细节:
|
|
|
|
|
|
- 分片阈值按更严格条件生效:`min(maxRows, maxParams/列数)`(忽略未设置项)。
|
|
|
|
|
|
- 分片关闭条件:`maxRows <= 0` 且 `maxParams <= 0` 且未命中驱动自动阈值。
|
|
|
|
|
|
- 分片执行失败会回滚整个批次。
|
|
|
|
|
|
- 分片结果语义:
|
|
|
|
|
|
- `RowsAffected()` 返回所有分片累计值。
|
|
|
|
|
|
- `LastInsertId()` 返回最后一个分片的 insert id。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### BatchInsertStructs
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
users := []User{{Name: "Alice"}, {Name: "Bob"}}
|
|
|
|
|
|
_, _ = db.BatchInsertStructs("users", users, "id")
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 8) 事务
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### 手动事务
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```go
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
|
|
if err != nil { /* ... */ }
|
|
|
|
|
|
defer tx.Rollback()
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
if _, err := tx.Exec("UPDATE users SET age = age + 1 WHERE id = ?", 1); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return tx.Commit()
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
#### 托管事务(常用)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
err := db.WithTx(func(tx *stardb.StarTx) error {
|
|
|
|
|
|
if _, err := tx.Exec("UPDATE users SET age = ? WHERE id = ?", 26, 1); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := tx.Exec("INSERT INTO logs (msg) VALUES (?)", "age updated"); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
`WithTx` 规则:
|
|
|
|
|
|
- `fn` 返回 `nil` -> `Commit`
|
|
|
|
|
|
- `fn` 返回错误 -> `Rollback`
|
|
|
|
|
|
- `fn` panic -> `Rollback` 后继续抛出 panic
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 9) SQL Hook 与慢 SQL 阈值
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
默认关闭;仅在显式设置时生效。
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetSQLSlowThreshold(200 * time.Millisecond)
|
|
|
|
|
|
db.SetSQLFingerprintEnabled(true) // 可选:在 Hook context 附带 SQL 指纹
|
|
|
|
|
|
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals) // 可选:指纹里脱敏数字/字符串字面量
|
|
|
|
|
|
db.SetSQLFingerprintKeepComments(false) // 默认 false:指纹不保留 SQL 注释
|
|
|
|
|
|
db.SetSQLFingerprintCounterEnabled(true) // 可选:记录指纹命中次数(内存级)
|
|
|
|
|
|
|
|
|
|
|
|
db.SetSQLHooks(
|
|
|
|
|
|
func(ctx context.Context, query string, args []interface{}) {
|
|
|
|
|
|
// before
|
|
|
|
|
|
},
|
|
|
|
|
|
func(ctx context.Context, query string, args []interface{}, d time.Duration, err error) {
|
|
|
|
|
|
// after
|
|
|
|
|
|
if hookMeta, ok := stardb.SQLHookMetaFromContext(ctx); ok {
|
|
|
|
|
|
_ = hookMeta.Fingerprint
|
|
|
|
|
|
}
|
|
|
|
|
|
if meta, ok := stardb.BatchExecMetaFromContext(ctx); ok {
|
|
|
|
|
|
// chunked batch insert metadata
|
|
|
|
|
|
// meta.ChunkIndex / meta.ChunkCount / meta.ChunkRows ...
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
阈值行为:
|
|
|
|
|
|
- `threshold <= 0`:`After` 每次都触发
|
|
|
|
|
|
- `threshold > 0`:仅在“慢于阈值”或“执行出错”时触发
|
|
|
|
|
|
- 打开 `SetSQLFingerprintEnabled(true)` 后,可从 `SQLHookMetaFromContext` 获取 SQL 指纹
|
|
|
|
|
|
- 指纹模式:`SQLFingerprintBasic`(默认,仅归一化)/ `SQLFingerprintMaskLiterals`(归一化 + 字面量脱敏)
|
|
|
|
|
|
- `SetSQLFingerprintKeepComments(true)` 可保留注释文本(默认关闭,利于聚合)
|
|
|
|
|
|
- `SetSQLFingerprintCounterEnabled(true)` 后,可通过 `SQLFingerprintCounters()` 查看命中次数,`ResetSQLFingerprintCounters()` 清空
|
|
|
|
|
|
- 若是分片批量写入,Hook 可通过 `BatchExecMetaFromContext` 读取分片元信息
|
|
|
|
|
|
|
|
|
|
|
|
Hook 上下文字段说明:
|
|
|
|
|
|
- `SQLHookMetaFromContext(ctx)`:
|
|
|
|
|
|
- `Fingerprint`:按配置生成的 SQL 指纹。
|
|
|
|
|
|
- `BatchExecMetaFromContext(ctx)`(仅分片批量写入):
|
|
|
|
|
|
- `ChunkIndex`:当前分片序号(从 1 开始)
|
|
|
|
|
|
- `ChunkCount`:总分片数
|
|
|
|
|
|
- `ChunkRows`:当前分片行数
|
|
|
|
|
|
- `TotalRows`:本次批量总行数
|
|
|
|
|
|
- `ColumnCount`:本次写入列数
|
|
|
|
|
|
|
|
|
|
|
|
### 10) 占位符方言
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
db.SetPlaceholderStyle(stardb.PlaceholderQuestion) // 默认
|
|
|
|
|
|
// 或
|
|
|
|
|
|
db.SetPlaceholderStyle(stardb.PlaceholderDollar) // ? -> $1,$2...
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
### 11) QueryBuilder
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
query, args := stardb.NewQueryBuilder("users u").
|
|
|
|
|
|
Select("u.id", "u.name", "COUNT(o.id) AS order_count").
|
|
|
|
|
|
Join("LEFT JOIN orders o ON o.user_id = u.id").
|
|
|
|
|
|
Where("u.active = ?", true).
|
|
|
|
|
|
GroupBy("u.id", "u.name").
|
|
|
|
|
|
Having("COUNT(o.id) > ?", 2).
|
|
|
|
|
|
OrderBy("order_count DESC").
|
|
|
|
|
|
Limit(20).
|
|
|
|
|
|
Offset(0).
|
|
|
|
|
|
Build()
|
|
|
|
|
|
|
|
|
|
|
|
_ = query
|
|
|
|
|
|
_ = args
|
2026-03-07 19:27:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 错误处理
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
库内置可判定错误,调用侧使用 `errors.Is` 做分支处理:
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-20 13:36:59 +08:00
|
|
|
|
if errors.Is(err, stardb.ErrDBNotInitialized) {
|
|
|
|
|
|
// 未初始化
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(err, stardb.ErrColumnNotFound) {
|
|
|
|
|
|
// 字段/列不匹配
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(err, stardb.ErrNoInsertValues) {
|
|
|
|
|
|
// 批量插入空数据
|
2026-03-07 19:27:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
常见错误类别:
|
|
|
|
|
|
- 生命周期:`ErrDBNotInitialized` `ErrTxNotInitialized` `ErrStmtNotInitialized`
|
|
|
|
|
|
- 参数校验:`ErrQueryEmpty` `ErrTargetNil` `ErrTargetNotPointer` ...
|
|
|
|
|
|
- 映射问题:`ErrColumnNotFound` `ErrFieldNotFound`
|
|
|
|
|
|
- 批量写入:`ErrNoInsertColumns` `ErrNoInsertValues` `ErrBatchRowValueCountMismatch`
|
|
|
|
|
|
- 流式回调:`ErrScanFuncNil` `ErrScanORMFuncNil`
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 使用边界
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
1. 这是轻量封装,不是全功能 ORM。
|
|
|
|
|
|
- 不做模型关系管理(has-many/association)
|
|
|
|
|
|
- 不做自动迁移
|
|
|
|
|
|
- 不做复杂查询 DSL
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
2. 大结果集优先用流式 API。
|
|
|
|
|
|
- `Query` 适合中小结果集
|
|
|
|
|
|
- `ScanEach` / `ScanEachORM` 更稳
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
3. 日志 Hook 按需打开。
|
|
|
|
|
|
- 生产环境最好配合慢 SQL 阈值,减少噪音
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
4. `ScanEachORM` 回调里的 target 会复用。
|
|
|
|
|
|
- 需要持久化时请拷贝结构体值
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 测试、竞态与基准
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
```bash
|
|
|
|
|
|
# 根模块
|
|
|
|
|
|
go test ./...
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
go test -race ./...
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
go test -run ^$ -bench BenchmarkQueryBuilder_ -benchmem ./...
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
# testing 子模块(集成测试/基准)
|
|
|
|
|
|
cd testing
|
|
|
|
|
|
go test ./...
|
|
|
|
|
|
go test -race ./...
|
|
|
|
|
|
go test -run ^$ -bench "Benchmark(QueryX|Orm|ScanEach|BatchInsert)" -benchmem
|
|
|
|
|
|
```
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## 支持数据库驱动
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
本库兼容所有实现 `database/sql` 的驱动。常见示例:
|
|
|
|
|
|
- SQLite: `_ "modernc.org/sqlite"`
|
|
|
|
|
|
- MySQL: `_ "github.com/go-sql-driver/mysql"`
|
|
|
|
|
|
- PostgreSQL: `_ "github.com/lib/pq"`
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
## License
|
2026-03-07 19:27:44 +08:00
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
|
Apache License 2.0
|