15 KiB
15 KiB
StarDB
StarDB 是对 Go database/sql 的轻量封装,目标是把常见的数据库操作做得更直白:
- 少量 API 覆盖日常 CRUD、事务、批量写入、结构体映射。
- 兼容原生
database/sql心智,不引入重量级依赖。 - 在可读性、可调试性和性能之间做实用平衡。
适合:
- 想保留 SQL 控制权,但不想反复写样板代码。
- 需要轻量 ORM 映射(不是全功能 ORM)。
- 需要在生产里追踪 SQL(可选 Hook + 慢 SQL 阈值)。
不适合:
- 需要完整领域模型关系管理、自动迁移、复杂查询 DSL 的项目。
安装
go get b612.me/stardb
要求:
- Go
>= 1.16 - 自行选择并导入数据库驱动(本库只封装
database/sql)
常见 DSN 示例
下面示例都可以直接用于 db.Open(driver, dsn),替换为实际账号、密码、库名即可。
MySQL(github.com/go-sql-driver/mysql)
import _ "github.com/go-sql-driver/mysql"
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)
}
常用参数说明:
charset=utf8mb4:避免字符集问题。parseTime=true:把DATETIME/TIMESTAMP解析为time.Time。loc=Local:指定时间解析时区(也可改成Asia/Shanghai)。
PostgreSQL(github.com/lib/pq)
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 形式:
urlDSN := "postgres://postgres:secret@127.0.0.1:5432/demo?sslmode=disable"
if err := db.Open("postgres", urlDSN); err != nil {
log.Fatal(err)
}
SQLite(modernc.org/sqlite)
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、指纹、分片元信息 |
快速开始
package main
import (
"log"
"b612.me/stardb"
_ "modernc.org/sqlite"
)
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func main() {
db := stardb.NewStarDB()
if err := db.Open("sqlite", "test.db"); err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("SELECT id, name, age FROM users WHERE age >= ?", 18)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var users []User
if err := rows.Orm(&users); err != nil {
log.Fatal(err)
}
log.Printf("users: %d", len(users))
}
接入流程
按下面顺序接入,可在开发阶段先固定运行边界:
- 建立连接并设置连接池。
- 在查询路径中区分内存模式与流式模式。
- 在批量写入路径设置分片阈值(按行数和参数数)。
- 启用 SQL Hook、慢 SQL 阈值、指纹策略。
- 在调用侧统一使用
errors.Is判定错误类别。
一个常用初始化示例:
db := stardb.NewStarDB()
if err := db.Open("mysql", dsn); err != nil {
return err
}
db.SetPoolConfig(&stardb.PoolConfig{
MaxOpenConns: 25,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
ConnMaxIdleTime: 10 * time.Minute,
})
db.SetBatchInsertMaxRows(500)
db.SetBatchInsertMaxParams(60000)
db.SetSQLSlowThreshold(200 * time.Millisecond)
db.SetSQLFingerprintEnabled(true)
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
db.SetSQLFingerprintKeepComments(false)
API 指南
1) 连接与连接池
db := stardb.NewStarDB()
_ = db.Open("mysql", "user:pass@tcp(localhost:3306)/app")
db.SetPoolConfig(&stardb.PoolConfig{
MaxOpenConns: 25,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
ConnMaxIdleTime: 10 * time.Minute,
})
也可以直接拿到底层连接:
raw := db.DB()
raw.SetMaxOpenConns(50)
2) 三种查询模式
A. 内存模式(默认)
Query 会把结果解析为 StarRows,适合中小结果集:
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")
}
B. 原生流式模式
QueryRaw 返回 *sql.Rows,完全按原生 Scan 处理:
rawRows, err := db.QueryRaw("SELECT id, name FROM users")
if err != nil { /* ... */ }
defer rawRows.Close()
C. 回调流式模式(常用)
ScanEach 逐行回调,避免全量缓存:
关闭内存预读时,使用 QueryRaw / ScanEach / ScanEachORM,不使用 Query。
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
})
可通过 stardb.ErrScanStopped 提前终止:
count := 0
_ = db.ScanEach("SELECT * FROM users", func(row *stardb.StarResult) error {
count++
if count >= 1000 {
return stardb.ErrScanStopped
}
return nil
})
3) 流式 ORM(逐行映射)
ScanEachORM 将每行映射到结构体,再回调。
var model User
var users []User
err := db.ScanEachORM("SELECT id, name, age FROM users", &model, func(target interface{}) error {
u := *(target.(*User)) // 注意拷贝一份,target 会被复用
users = append(users, u)
return nil
})
同样支持 Tx / Stmt:
tx.ScanEachORM(...)stmt.ScanEachORM(...)
4) 结果读取与 NULL 语义
Must 系列(无错误,失败给零值)
MustStringMustInt64MustFloat64MustBool...
安全系列(带错误)
GetStringGetInt64GetFloat64GetNullStringGetNullInt64GetNullFloat64GetNullBoolGetNullTime
name, err := row.GetString("name")
age, err := row.GetNullInt64("age")
if age.Valid {
// use age.Int64
}
5) ORM 映射
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
var u User
_ = rows.Orm(&u)
var list []User
_ = rows.Orm(&list)
严格列检查(字段/SQL 变更敏感场景可开启):
db.SetStrictORM(true)
若结构体 tag 大范围调整,可清理反射缓存:
stardb.ClearReflectCache()
6) 命名参数绑定
type Filter struct {
Name string `db:"name"`
MinAge int `db:"min_age"`
}
f := Filter{Name: "Alice", MinAge: 18}
rows, err := db.QueryX(&f,
"SELECT * FROM users WHERE name = ? AND age >= ?",
":name", ":min_age")
7) 写入能力
Insert / Update
_, _ = db.Insert(&user, "users", "id") // id 作为自增字段跳过
_, _ = db.Update(&user, "users", "id") // id 作为主键
BatchInsert
columns := []string{"name", "email", "age"}
values := [][]interface{}{
{"Alice", "alice@example.com", 25},
{"Bob", "bob@example.com", 30},
}
_, _ = db.BatchInsert("users", columns, values)
如需避免单条 SQL 过大(参数过多),可打开分片:
db.SetBatchInsertMaxRows(500) // 0 或负数表示关闭分片(默认)
db.SetBatchInsertMaxParams(60000) // 0 表示自动识别常见驱动参数上限
分片模式下会在一个事务里按块执行,避免部分写入成功。
自动识别当前覆盖:SQLite 999、PostgreSQL 65535、MySQL 65535、SQL Server 2100。
分片行为细节:
- 分片阈值按更严格条件生效:
min(maxRows, maxParams/列数)(忽略未设置项)。 - 分片关闭条件:
maxRows <= 0且maxParams <= 0且未命中驱动自动阈值。 - 分片执行失败会回滚整个批次。
- 分片结果语义:
RowsAffected()返回所有分片累计值。LastInsertId()返回最后一个分片的 insert id。
BatchInsertStructs
users := []User{{Name: "Alice"}, {Name: "Bob"}}
_, _ = db.BatchInsertStructs("users", users, "id")
8) 事务
手动事务
tx, err := db.Begin()
if err != nil { /* ... */ }
defer tx.Rollback()
if _, err := tx.Exec("UPDATE users SET age = age + 1 WHERE id = ?", 1); err != nil {
return err
}
return tx.Commit()
托管事务(常用)
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
})
WithTx 规则:
fn返回nil->Commitfn返回错误 ->Rollbackfnpanic ->Rollback后继续抛出 panic
9) SQL Hook 与慢 SQL 阈值
默认关闭;仅在显式设置时生效。
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 ...
}
},
)
阈值行为:
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) 占位符方言
db.SetPlaceholderStyle(stardb.PlaceholderQuestion) // 默认
// 或
db.SetPlaceholderStyle(stardb.PlaceholderDollar) // ? -> $1,$2...
11) QueryBuilder
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
错误处理
库内置可判定错误,调用侧使用 errors.Is 做分支处理:
if errors.Is(err, stardb.ErrDBNotInitialized) {
// 未初始化
}
if errors.Is(err, stardb.ErrColumnNotFound) {
// 字段/列不匹配
}
if errors.Is(err, stardb.ErrNoInsertValues) {
// 批量插入空数据
}
常见错误类别:
- 生命周期:
ErrDBNotInitializedErrTxNotInitializedErrStmtNotInitialized - 参数校验:
ErrQueryEmptyErrTargetNilErrTargetNotPointer... - 映射问题:
ErrColumnNotFoundErrFieldNotFound - 批量写入:
ErrNoInsertColumnsErrNoInsertValuesErrBatchRowValueCountMismatch - 流式回调:
ErrScanFuncNilErrScanORMFuncNil
使用边界
- 这是轻量封装,不是全功能 ORM。
- 不做模型关系管理(has-many/association)
- 不做自动迁移
- 不做复杂查询 DSL
- 大结果集优先用流式 API。
Query适合中小结果集ScanEach/ScanEachORM更稳
- 日志 Hook 按需打开。
- 生产环境最好配合慢 SQL 阈值,减少噪音
ScanEachORM回调里的 target 会复用。
- 需要持久化时请拷贝结构体值
测试、竞态与基准
# 根模块
go test ./...
go test -race ./...
go test -run ^$ -bench BenchmarkQueryBuilder_ -benchmem ./...
# testing 子模块(集成测试/基准)
cd testing
go test ./...
go test -race ./...
go test -run ^$ -bench "Benchmark(QueryX|Orm|ScanEach|BatchInsert)" -benchmem
支持数据库驱动
本库兼容所有实现 database/sql 的驱动。常见示例:
- SQLite:
_ "modernc.org/sqlite" - MySQL:
_ "github.com/go-sql-driver/mysql" - PostgreSQL:
_ "github.com/lib/pq"
License
Apache License 2.0