学完这篇你能拿到一套可直接落地的判断标准:什么项目用 GORM、什么场景该退回 SQLx、什么规模值得上 Ent,并且每个库都有能跑起来的最小代码。
第一步:先想清楚三个选型维度
别急着看 Star 数,先回答三个问题:
- 团队 SQL 水平——能熟练写 JOIN、索引、EXPLAIN 吗?能,SQLx 的成本几乎为零。
- 领域模型复杂度——是几张表的 CRUD 后台,还是几十张表互相咬合的图状关系?后者 Ent 的优势最大。
- 交付速度 vs 长期可控——两周要上线选 GORM;要活三年的核心系统,代码生成带来的类型安全更值钱。
第二步:搭一个最小可跑环境
go mod init demo
go get github.com/jmoiron/sqlx
go get gorm.io/gorm gorm.io/driver/mysql
go get entgo.io/ent/cmd/ent
docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 mysql:8.0
统一用这个 DSN(parseTime=true 少了会解析不了 DATETIME):
root:root@tcp(127.0.0.1:3306)/demo?parseTime=true&charset=utf8mb4&loc=Local
第三步:SQLx——手写 SQL,最少魔法
db := sqlx.MustConnect("mysql", dsn)
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
var us []User
db.Select(&us, "SELECT id, name FROM users WHERE age > ?", 18)
db.NamedExec("INSERT INTO users (name) VALUES (:name)", u)
注意:SQLx 不做结构体字段名到列名的自动映射,必须写 db tag;列名对不上不报错,只是零值,非常隐蔽。
事务用 db.Beginx() / tx.Commit() / tx.Rollback(),记得 defer 里判断是否已提交。
第四步:GORM——功能最全,开发最快
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
})
db.AutoMigrate(&User{})
db.Where("age > ?", 18).Preload("Posts").Find(&users)
db.CreateInBatches(users, 1000)
SkipDefaultTransaction: true 能省掉单条写入的隐式事务开销,写入密集时提升明显。
注意:Preload 在循环里调用会产生 N+1,一次查多表用 Preload("Posts") 一次性带出,而不是循环里 Find。
注意:AutoMigrate 只会加字段加索引,不会删列、不改类型,线上别当迁移工具用;正式环境交给 golang-migrate 或 Atlas 管理 DDL。
第五步:Ent——Schema as Code,类型安全最强
go run -mod=mod entgo.io/ent/cmd/ent new User
编辑 ent/schema/user.go 定义 Fields 与 Edges,然后:
go generate ./ent
查询时关系是编译期检查的:
users, err := client.User.
Query().
Where(user.HasPostsWith(post.TitleContains("Go"))).
WithPosts().
All(ctx)
注意:生成的 ent/ 目录要提交进版本库,否则 CI 必须额外安装代码生成工具,构建会变慢也不稳定。
注意:Ent 的 client 从 ent.Open 初始化一次全局复用,别每次请求都开新 client,会把连接池撑爆。
第六步:对照表与选型结论
| 维度 | SQLx | GORM | Ent |
|---|
| SQL 控制力 | 完全 | 中 | 中高 |
| 上手成本 | 低 | 最低 | 较高 |
| 类型安全 | 弱 | 中 | 强 |
| 复杂关系 | 自己写 | 一般 | 最强 |
| 代码生成 | 无 | 无 | 必需 |
结论很直白:
- 内部管理后台、CRUD 型业务、要快速交付 → GORM
- 报表、统计、已有大量 SQL 的存量系统 → SQLx
- 多对多关系复杂、团队多人协作、要长期演进 → Ent
- 很常见的组合:GORM/Ent 负责写与简单读,SQLx 负责复杂统计查询,两者共享同一个
*sql.DB。
第七步:三者共用的工程要点
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(50) // 按数据库上限的四分之一估
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour) // 必须小于 MySQL wait_timeout
- 所有查询方法第一个参数传
ctx,方便加超时和链路追踪。
- MySQL 默认
wait_timeout 是 28800 秒,SetConnMaxLifetime 设长了会拿到已被服务端关闭的连接,报 invalid connection。
小结
- 选型先看团队 SQL 水平与模型复杂度,别只看功能表。
- SQLx 最透明但映射要手写
db tag;GORM 开发最快但要防 N+1 和滥用 AutoMigrate;Ent 类型安全最强但要接受代码生成。
- 三者可以混用,共享连接池是标准做法。
- 生产环境无论如何都要控制连接池寿命,并配合独立迁移工具管理 DDL。