编程 sqlh:Go 语言零样板 SQL 库,结构体标签生成 CRUD,50 行搞定

2026-07-09 07:57:04 +0800 CST views 18

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/sqlsqlxGORMsqlh
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, ...)

关键设计决策

  1. 泛型优先(Go 1.25+)Get[User]() 在编译期就返回 *User,不是 interface{}

  2. 元数据缓存:结构体解析后存入 sync.Map,后续调用复用

  3. 写操作自动事务:每个 Insert、Update、Delete、Set 都包在 BEGIN...COMMIT 中,出错自动 ROLLBACK

  4. SQLite 锁重试:数据库被锁定错误最多重试 20 次,退避 100ms

  5. 多数据库: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

复制全文 生成海报 Go SQL ORM sqlx GORM CRUD 数据库 SQLite 泛型 开源

推荐文章

Golang Select 的使用及基本实现
2024-11-18 13:48:21 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
使用 Vue3 和 Axios 实现 CRUD 操作
2024-11-19 01:57:50 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
程序员茄子在线接单