sqlh:Go 语言零样板 SQL 库,结构体标签生成 CRUD,50 行搞定
Go 的零样板 SQL。用结构体标签描述表结构,残存的重构它。
如果你写 Go 并且使用 SQL 数据库,一定感到痛苦不堪:每个 CRUD 都要手写 SQL 字符串、每个字段 rows.Scan、都要包一层 Begin/Commit/Rollback,还得时刻保证 DDL 建表和代码里的结构体不脱节。
sqlh 把这一切抹掉,稳定站在「裸 SQL(太累)」和「重型 ORM(太魔)」之间的「黄金中点」。
问题:Go + SQL = 产生无数次 rows.Scan
纯 database/sql 实现的简单 CRUD:
// 1. CREATE TABLE —— 手写 DDL
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
email TEXT,
age INTEGER
)`)
// 2. INSERT —— 显式占位符与参数
_, err = db.Exec(
"INSERT INTO user (name, email, age) VALUES (?, ?, ?)",
"Alice", "alice@example.com", 30,
)
// 3. 按 ID 查询 —— QueryRow + 手写 Scan
var u User
err = db.QueryRow("SELECT id, name, email, age FROM user WHERE id = ?", 1).
Scan(&u.ID, &u.Name, &u.Email, &u.Age)
// 4. 列出全部 —— Query + rows.Next + 循环 Scan
rows, err := db.Query("SELECT id, name, email, age FROM user ORDER BY name ASC")
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name, &u.Email, &u.Age)
users = append(users, u)
}
// 5. UPDATE —— 手写 SQL
_, err = db.Exec("UPDATE user SET email = ?, age = ? WHERE id = ?", "alice.new@example.com", 31, 1)
// 6. DELETE —— 手写 SQL
_, err = db.Exec("DELETE FROM user WHERE id = ?", 1)
六个基础操作约 115 行代码。每加一个字段,得同步改 CREATE TABLE、INSERT 的列清单、SELECT 的列清单,以及 rows.Scan 调用。任何一处拼错,都是运行时错误——编译期无法验证。
现有方案对比
sqlx:更好,但 SQL 还是手写的
sqlx 是 database/sql 流行的扩展,增加了 StructScan、Get、Select 和命名参数。SQL 仍然手写,但 rows.Scan 被自动化了:
var u User
dbx.Get(&u, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
能省下约 30% 的样板(降到约 80 行)。但 CREATE TABLE、INSERT、SELECT、UPDATE、DELETE 仍然要手写。
GORM:全功能 ORM,全量魔法
重量级选手,生成一切。但代价高昂:
- 运行时重度引用
- 陡峭的学习曲线——标签、挂钩、范围、关联
- ~4 MB 二进制体积仅 ORM 本身
- 隐藏复杂程度的魔法——直到它崩溃,调试时间按小时算
对于拥有专职 DBA、模型复杂的大型团队,GORM 是稳健的选择。对于 CLI 工具、创业公司和微服务,有点大材小用。
sqlh:黄金中点
| 特性 | database/sql | sqlx | GORM | sqlh |
|---|---|---|---|---|
| SQL 生成 | ❌ 手写 | ❌ 手写 | ✅ | ✅ |
| rows.Scan | ✅ 需要 | ❌ StructScan | ❌ 自动 | ❌ 自动 |
| 泛型类型安全 | ❌ | ❌ | ❌ | ✅ |
| 自动事务 | ❌ | ❌ | ✅ | ✅ |
| 锁重试 | ❌ | ❌ | ❌ | ✅ |
| 学习曲线 | 中 | 中 | 高 | 低 |
| 二进制费用 | 0 | 约 200KB | 约 4MB | 约 200KB |
核心理念:你的 Go 结构体就是你的表结构。
type User struct {
ID int64 `db:"id" db_key:"not null primary key autoincrement"`
Name string `db:"name" db_key:"unique"`
Email string `db:"email"`
Age int `db:"age"`
}
只需这一个定义,sqlh 就可以生成:
- 创建表:
sqlh.Create[User](db) - 插入:
sqlh.Insert(db, User{Name: "Alice", Email: "alice@example.com", Age: 30}) - 查询:
sqlh.Get[User](db, ...) - 更新:
sqlh.Update(db, ...) - 删除:
sqlh.Delete[User](db, ...)
关键设计决策
泛型优先(Go 1.25+):
Get[User]()在编译期就返回*User,不是interface{}元数据缓存:结构体解析后存入
sync.Map,后续调用复用写操作自动事务:每个 Insert、Update、Delete、Set 都包在 BEGIN...COMMIT 中,出错自动 ROLLBACK
SQLite 锁重试:数据库被锁定错误最多重试 20 次,退避 100ms
多数据库:SQLite(主)、MySQL、PostgreSQL(均在 CI 覆盖),SQL Server(实验性)
50 行 CRUD 对比
package main
import (
"database/sql"
"fmt"
"github.com/kirill-scherba/sqlh"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
ID int64 `db:"id" db_key:"not null primary key autoincrement"`
Name string `db:"name" db_key:"unique"`
Email string `db:"email"`
Age int `db:"age"`
}
func main() {
db, _ := sql.Open("sqlite3", "file::memory:?cache=shared")
defer db.Close()
// 1. 从结构体建表
sqlh.Create[User](db)
// 2. INSERT
aliceID, _ := sqlh.InsertId(db, User{Name: "Alice", Email: "alice@example.com", Age: 30})
bobID, _ := sqlh.InsertId(db, User{Name: "Bob", Email: "bob@example.com", Age: 25})
// 3. 按 ID 查询 —— 返回 *User,不是 interface{}
u, _ := sqlh.Get[User](db, "id", aliceID)
fmt.Printf("Alice: %+v\n", u)
// 4. 列出全部
users, _ := sqlh.List[User](db, nil, nil, nil, nil)
fmt.Printf("All users: %+v\n", users)
// 5. UPDATE
sqlh.Update(db, User{ID: aliceID, Email: "alice.new@example.com", Age: 31})
// 6. DELETE
sqlh.Delete[User](db, "id", bobID)
}
从 115 行缩减到约 50 行,减少约 57%。
适合场景
- CLI 工具和轻量级微服务
- 创业公司快速迭代
- 不想被 GORM 魔法绑架,又不想手写 SQL
- 需要跨数据库支持(SQLite/MySQL/PostgreSQL)
开源地址:https://github.com/kirill-scherba/sqlh