原生 SQL 操作与 GORM 对比
本章深入讲解 Go 标准库 database/sql 的完整用法,并与 GORM 进行全面对比,帮助你根据场景做出正确的技术选择。
目录
- 10.1 database/sql 核心概念
- 10.2 连接数据库
- 10.3 查询操作
- 10.4 写入操作
- 10.5 事务
- 10.6 Prepared Statement
- 10.7 NULL 值处理
- 10.8 连接池管理
- 10.9 错误处理
- 10.10 sqlx — 增强版 database/sql
- 10.11 go-sqlbuilder / squirrel — SQL 构建器
- 10.12 GORM vs database/sql 全面对比
- 10.13 如何选择与混合使用
10.1 database/sql 核心概念
database/sql 是 Go 标准库提供的数据库操作抽象层。它不直接连接数据库,而是通过驱动(Driver)来与不同的数据库交互。
你的代码
↓
database/sql(标准库:连接池、事务、查询抽象)
↓
database/sql/driver(驱动接口)
↓
go-sql-driver/mysql / lib/pq / pgx …(具体驱动实现)
↓
MySQL / PostgreSQL 数据库database/sql 提供什么:
| 能力 | 说明 |
|---|---|
sql.DB |
连接池管理器(非单个连接) |
Query / QueryRow |
查询操作 |
Exec |
写入操作(INSERT/UPDATE/DELETE) |
Prepare |
预编译语句 |
Begin / Commit / Rollback |
事务管理 |
Scan |
将结果写入 Go 变量 |
Null* 类型 |
处理可空字段 |
database/sql 不提供什么:
| 能力 | 替代方案 |
|---|---|
| SQL 构建器 | 手写 SQL 或 sqlx/squirrel |
| ORM 映射 | GORM / sqlx 的 StructScan |
| 迁移 | golang-migrate / Atlas |
| 关联加载 | 手动 Join 或多次查询 |
| 钩子/回调 | 手动封装 |
10.2 连接数据库
安装驱动
# MySQL 驱动
go get github.com/go-sql-driver/mysql
# PostgreSQL 驱动(推荐 pgx,也可用 lib/pq)
go get github.com/jackc/pgx/v5
go get github.com/jackc/pgx/v5/stdlib # database/sql 适配器
# 或
go get github.com/lib/pqMySQL 连接
import (
"database/sql"
_ "github.com/go-sql-driver/mysql" // 匿名导入,注册驱动
)
func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 验证连接
if err := db.Ping(); err != nil {
log.Fatal(err)
}
// 连接池配置
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)
}🚨 陷阱:
sql.Open不会立即建立连接——它只是验证 DSN 格式和初始化连接池。使用db.Ping()才能确认数据库确实可达。
PostgreSQL 连接(pgx)
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
)
func main() {
dsn := "postgres://user:pass@localhost:5432/dbname?sslmode=disable"
db, err := sql.Open("pgx", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatal(err)
}
}10.3 查询操作
单行查询 — QueryRow
type User struct {
ID int
Name string
Age int
CreatedAt time.Time
}
var user User
row := db.QueryRow("SELECT id, name, age, created_at FROM users WHERE id = ?", 1)
err := row.Scan(&user.ID, &user.Name, &user.Age, &user.CreatedAt)
if err == sql.ErrNoRows {
fmt.Println("用户不存在")
} else if err != nil {
log.Fatal(err)
}🚨 陷阱:
QueryRow的Scan才会执行查询。QueryRow本身不返回错误——必须调用Scan并检查其返回的 error。此外,QueryRow返回的*Row不会自动关闭结果集;如果未调用 Scan,后续读取可能阻塞。
多行查询 — Query
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
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil {
log.Fatal(err)
}
users = append(users, u)
}
// 检查遍历过程中是否有错误
if err := rows.Err(); err != nil {
log.Fatal(err)
}🔬 深入原理:rows.Next() 逐行读取结果集。必须在循环后调用 rows.Err() 检查中途发生的错误。rows.Close() 必须用 defer 或显式调用——不关闭会导致连接泄漏。
查询到自定义结构体(手动映射)
// database/sql 不支持直接扫描到结构体,需逐字段映射
rows, _ := db.Query("SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id")
defer rows.Close()
type UserStats struct {
ID int
Name string
OrderCount int
}
var stats []UserStats
for rows.Next() {
var s UserStats
rows.Scan(&s.ID, &s.Name, &s.OrderCount)
stats = append(stats, s)
}10.4 写入操作
Exec — 执行 INSERT/UPDATE/DELETE
// INSERT
result, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", "张三", 18)
if err != nil {
log.Fatal(err)
}
id, _ := result.LastInsertId() // 自增 ID(MySQL 支持,PostgreSQL 不支持)
n, _ := result.RowsAffected() // 影响行数
// UPDATE
result, err = db.Exec("UPDATE users SET age = ? WHERE id = ?", 20, 1)
n, _ = result.RowsAffected()
// DELETE
result, err = db.Exec("DELETE FROM users WHERE id = ?", 1)
// PostgreSQL — 用 RETURNING 获取插入 ID
var newID int
err = db.QueryRow(
"INSERT INTO users (name, age) VALUES ($1, $2) RETURNING id",
"张三", 18,
).Scan(&newID)🚨 陷阱:
result.LastInsertId()在 PostgreSQL 中不工作(pgx 驱动返回-1并报错)。PG 中要用RETURNING id语法。
批量插入
// 手动拼接参数(数据库限制了参数数量)
func BatchInsert(db *sql.DB, users []User) error {
if len(users) == 0 {
return nil
}
// 构建批量 INSERT
query := "INSERT INTO users (name, age) VALUES "
args := make([]interface{}, 0, len(users)*2)
for i, u := range users {
if i > 0 {
query += ", "
}
query += "(?, ?)"
args = append(args, u.Name, u.Age)
}
_, err := db.Exec(query, args...)
return err
}⚡ 对于大量数据(> 1000 条),建议分批插入并限制每批数量,避免超出
max_allowed_packet。
10.5 事务
// 开启事务
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// 确保事务结束(Commit 或 Rollback)
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
// 操作 1
result, err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, 1)
if err != nil {
tx.Rollback()
return
}
if n, _ := result.RowsAffected(); n == 0 {
tx.Rollback()
return
}
// 操作 2
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", 100, 2)
if err != nil {
tx.Rollback()
return
}
// 提交
err = tx.Commit()
if err != nil {
log.Fatal(err)
}事务 + Context
func TransferWithTimeout(ctx context.Context, db *sql.DB, fromID, toID int, amount float64) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
ReadOnly: false,
})
if err != nil {
return err
}
defer tx.Rollback() // Commit 后 Rollback 是空操作
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
return err
}
return tx.Commit()
}💡
defer tx.Rollback()是一个惯用模式——如果Commit成功,后续的Rollback是空操作;如果函数提前返回,Rollback确保事务被回滚。
10.6 Prepared Statement
预编译语句:将 SQL 模板发送到数据库预编译,后续调用只需传参数,减少编译开销:
// 预编译
stmt, err := db.Prepare("SELECT id, name, age FROM users WHERE age > ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// 多次执行
rows1, _ := stmt.Query(18)
rows2, _ := stmt.Query(30)
// 预编译写入
insertStmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
defer insertStmt.Close()
for i := 0; i < 1000; i++ {
insertStmt.Exec(fmt.Sprintf("user_%d", i), i%50)
}| 特性 | database/sql Prepare | GORM PrepareStmt |
|---|---|---|
| 自主管理 | ✅ 手动创建/销毁 | ✅ 自动缓存 |
| 性能 | 高(复用预编译) | 高(自动缓存) |
| 灵活性 | 完全控制 | 透明 |
⚡ 性能提示:
db.Prepare在 MySQL 中创建服务端预编译语句,适合批量操作。但如果 SQL 只执行一次,Prepare + Exec 比直接 Exec 多一次网络往返,反而更慢。
10.7 NULL 值处理
database/sql 中,如果查询的列为 NULL 而你用 Go 基础类型(如 string、int)接收,会报错。
使用 Null 类型
import "database/sql"
type User struct {
ID int
Name string
Email sql.NullString // 可能为 NULL
Age sql.NullInt64 // 可能为 NULL
Bio sql.NullString // 可能为 NULL
}
var u User
row := db.QueryRow("SELECT id, name, email, age, bio FROM users WHERE id = ?", 1)
row.Scan(&u.ID, &u.Name, &u.Email, &u.Age, &u.Bio)
// 检查 NULL
if u.Email.Valid {
fmt.Println(u.Email.String)
} else {
fmt.Println("邮箱未设置")
}使用指针
// 指针方案:Go 中 nil 表示 NULL
type User struct {
ID int
Name string
Email *string // nil → NULL
Age *int // nil → NULL
}COALESCE / IFNULL
// 在 SQL 中用默认值替代 NULL
row := db.QueryRow("SELECT id, name, COALESCE(email, '') as email, COALESCE(age, 0) as age FROM users WHERE id = ?", 1)
var u User // 用基础类型即可
row.Scan(&u.ID, &u.Name, &u.Email, &u.Age)| 方案 | 优点 | 缺点 |
|---|---|---|
sql.NullXxx |
语义明确,区分"未设置"和"零值" | 代码冗长,无 JSON 序列化支持 |
指针 *T |
直观,与 JSON 协同好 | 内存分配,判断需 != nil |
| COALESCE | 代码最简洁 | 无法区分"DB 中是 NULL"和"真的是默认值" |
💡
sql.NullString/sql.NullInt64不支持 JSON 序列化。若同时需要 JSON 输出,用指针*string/*int,或使用github.com/guregu/null等第三方库。
10.8 连接池管理
sql.DB 是连接池,而非单连接——创建一次,全局复用:
// ❌ 错误:每次打开新连接池
func handler(w http.ResponseWriter, r *http.Request) {
db, _ := sql.Open("mysql", dsn)
db.Query("SELECT ...") // 每次请求创建新连接池!泄漏!
}
// ✅ 正确:全局单例
var DB *sql.DB
func init() {
DB, _ = sql.Open("mysql", dsn)
DB.SetMaxOpenConns(100)
DB.SetMaxIdleConns(10)
DB.SetConnMaxLifetime(time.Hour)
}
func handler(w http.ResponseWriter, r *http.Request) {
DB.QueryContext(r.Context(), "SELECT ...")
}连接池监控
stats := db.Stats()
fmt.Printf(`Database Pool Stats:
当前打开连接: %d
空闲连接: %d
使用中连接: %d
累计等待次数: %d (高值表示池太小)
等待时长合计: %v
因空闲关闭: %d
因超时关闭: %d
`,
stats.OpenConnections,
stats.Idle,
stats.InUse,
stats.WaitCount,
stats.WaitDuration,
stats.MaxIdleClosed,
stats.MaxLifetimeClosed,
)⚡
WaitCount > 0表示连接池不够用——需增加MaxOpenConns或优化查询速度。
10.9 错误处理
检查特定错误
import (
"database/sql"
"errors"
"github.com/go-sql-driver/mysql" // MySQL 特定错误
"github.com/jackc/pgx/v5/pgconn" // PostgreSQL 特定错误
)
// 通用
if errors.Is(err, sql.ErrNoRows) {
// 无结果
}
// MySQL 特定错误
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) {
switch mysqlErr.Number {
case 1062:
fmt.Println("主键/唯一键重复")
case 1451:
fmt.Println("外键约束冲突")
case 1213:
fmt.Println("死锁,需要重试")
}
}
// PostgreSQL 特定错误
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case "23505":
fmt.Println("唯一约束冲突")
case "23503":
fmt.Println("外键约束冲突")
case "40P01":
fmt.Println("死锁")
}
}死锁重试
func ExecWithRetry(db *sql.DB, fn func(tx *sql.Tx) error, maxRetries int) error {
for i := 0; i < maxRetries; i++ {
tx, _ := db.Begin()
err := fn(tx)
if err == nil {
return tx.Commit()
}
tx.Rollback()
if !isDeadlockError(err) {
return err // 非死锁错误直接返回
}
time.Sleep(time.Millisecond * time.Duration(10*(i+1))) // 退避策略
}
return errors.New("达到最大重试次数")
}
func isDeadlockError(err error) bool {
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1213 {
return true
}
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "40P01" {
return true
}
return false
}10.10 sqlx — 增强版 database/sql
sqlx 是 database/sql 的轻量级扩展,提供结构体映射和命名参数:
go get github.com/jmoiron/sqlx核心优势
import "github.com/jmoiron/sqlx"
import _ "github.com/go-sql-driver/mysql"
// sqlx.DB 内嵌了 sql.DB,所有 database/sql 方法都能用
db, _ := sqlx.Connect("mysql", dsn)
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
CreatedAt time.Time `db:"created_at"`
}
// StructScan — 自动映射到结构体
var user User
db.Get(&user, "SELECT id, name, age, created_at FROM users WHERE id = ?", 1)
// 多行映射
var users []User
db.Select(&users, "SELECT id, name, age FROM users WHERE age > ?", 18)
// 命名参数
sql := "SELECT * FROM users WHERE name = :name AND age > :age"
arg := map[string]interface{}{"name": "张三", "age": 18}
rows, _ := db.NamedQuery(sql, arg)
// IN 查询(自动展开切片)
ids := []int{1, 2, 3}
query, args, _ := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)
db.Select(&users, query, args...) // sqlx.In 会生成 3 个 ? 占位符
// 事务
tx := db.MustBegin()
tx.Exec("INSERT INTO users (name) VALUES (?)", "张三")
tx.Commit()| 特性 | database/sql | sqlx | GORM |
|---|---|---|---|
| 结构体映射 | ❌ 手动 Scan | ✅ db tag |
✅ gorm tag |
| 命名参数 | ❌ | ✅ NamedQuery |
✅ sql.Named |
| IN 展开 | ❌ 手动拼接 | ✅ sqlx.In |
✅ 自动 |
| SQL 构建器 | ❌ | ❌ | ✅ 链式调用 |
| 关联加载 | ❌ | ❌ | ✅ Preload |
| 迁移 | ❌ | ❌ | ✅ AutoMigrate |
| 学习成本 | 低 | 低 | 中 |
| 性能开销 | 最低(基准) | 极低(thin wrapper) | 中等(ORM 抽象层) |
10.11 go-sqlbuilder / squirrel — SQL 构建器
当你不想用 ORM 但又需要动态构建 SQL 时,SQL 构建器是很好的中间选择:
squirrel
import sq "github.com/Masterminds/squirrel"
// MySQL 风格
users := sq.Select("*").From("users").Where(sq.Eq{"age": 18}).OrderBy("name DESC").Limit(10)
sql, args, _ := users.ToSql()
// SELECT * FROM users WHERE age = ? ORDER BY name DESC LIMIT 10
// PostgreSQL 风格
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
sql, args, _ := psql.Select("*").From("users").Where(sq.Eq{"age": 18}).ToSql()
// SELECT * FROM users WHERE age = $1go-sqlbuilder
import "github.com/huandu/go-sqlbuilder"
sql, args := sqlbuilder.NewSelectBuilder().
Select("id", "name").
From("users").
Where(
sqlbuilder.GreaterThan("age", 18),
sqlbuilder.Equal("status", "active"),
).
OrderBy("created_at DESC").
Limit(10).
Build()10.12 GORM vs database/sql 全面对比
对比总表
| 维度 | database/sql | GORM |
|---|---|---|
| 定位 | 标准库数据库抽象层 | 全功能 ORM |
| 学习曲线 | 低(标准库 API) | 中(需学习框架约定和 Tag 系统) |
| 代码量 | 较多(手动 Scan、拼接 SQL) | 较少(链式调用、自动映射) |
| SQL 控制 | 完全控制(你写什么就是什么) | 高度自动(需理解 Query Builder 生成逻辑) |
| 性能 | 最高(零抽象开销) | 略低(反射、Callback、关联处理) |
| 结构体映射 | 手动 Scan | 自动(反射 + Tag) |
| 关联加载 | 手动 Join + 组装 | Preload / Joins + 自动填充 |
| 迁移 | 无 | AutoMigrate(开发环境) |
| 钩子/回调 | 无 | 完整生命周期钩子 |
| 批量操作 | 手动拼接 SQL | CreateInBatches / 链式 Updates |
| 事务 | Begin/Commit/Rollback | 闭包事务 / 手动事务 / SavePoint |
| SQL 日志 | 无 | 内置 Logger(含慢查询标记) |
| 动态 SQL | 手动拼接(需防护注入) | Scope / 链式条件组合 |
| 依赖 | 仅标准库 + 驱动 | gorm.io/gorm + 驱动 |
| 生成 SQL 质量 | 取决于你写的 SQL | 大多数情况好,复杂场景需检查 |
| 适合项目 | 性能敏感、SQL 密集型 | 常规 CRUD 为主、快速开发 |
性能对比 (benchmark 参考)
| 操作 | database/sql | GORM | 差异 |
|---|---|---|---|
| 插入 1 行 | ~150 μs | ~250 μs | GORM 慢 ~40% |
| 插入 1000 行 (batch) | ~3 ms | ~5 ms | GORM 慢 ~40% |
| 查询 100 行 (struct scan) | ~2 ms | ~4 ms | GORM 慢 ~50% |
| 复杂 JOIN 查询 | ~5 ms | ~8 ms | GORM 慢 ~37% |
注:以上为大致参考值,实际性能受驱动、数据库版本、网络延迟等影响。GORM 的性能开销主要来自反射(结构体映射)、回调链(生命周期钩子)、和 Preload 的多条 SQL。
代码量对比
database/sql 方式:
// 查询单条 ≈ 12 行
row := db.QueryRow("SELECT id, name, age, email, phone, created_at FROM users WHERE id = ?", id)
var u User
err := row.Scan(&u.ID, &u.Name, &u.Age, &u.Email, &u.Phone, &u.CreatedAt)
if err != nil { ... }
// 查询全部 ≈ 15 行
rows, _ := db.Query("SELECT id, name, age FROM users WHERE age > ?", 18)
defer rows.Close()
var users []User
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name, &u.Age)
users = append(users, u)
}
rows.Err()
// 更新 ≈ 5 行
db.Exec("UPDATE users SET name = ?, age = ? WHERE id = ?", name, age, id)
// 插入 ≈ 5 行
result, _ := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", name, age)
id, _ := result.LastInsertId()
// 事务 ≈ 18 行
tx, _ := db.Begin()
defer tx.Rollback()
tx.Exec(...)
tx.Exec(...)
tx.Commit()GORM 方式:
// 查询单条 ≈ 2 行
db.First(&user, id)
// 查询全部 ≈ 2 行
db.Where("age > ?", 18).Find(&users)
// 更新 ≈ 2 行
db.Model(&user).Updates(User{Name: name, Age: age})
// 插入 ≈ 1 行
db.Create(&user) // 自动回填 ID
// 事务 ≈ 5 行
db.Transaction(func(tx *gorm.DB) error {
tx.Create(...)
tx.Create(...)
return nil
})10.13 如何选择与混合使用
选择决策树
你需要什么?
├── 简单 CRUD 为主(80% 以上是基础操作)
│ └── 用 GORM —— 省时省力,代码简洁
│
├── 复杂 SQL 为主(报表、BI、数据分析)
│ ├── 用 database/sql —— 完全控制 SQL
│ └── 或用 sqlx —— 保留 SQL 控制权 + 结构体映射
│
├── 性能敏感(高并发、低延迟)
│ ├── database/sql + 手写 SQL —— 零抽象开销
│ └── 或用 GORM + Raw SQL(性能热点处)
│
├── 团队 SQL 能力较弱
│ └── 用 GORM —— 减少手写 SQL 出错概率
│
├── 需要关联操作(Preload、多级嵌套)
│ └── 用 GORM —— 手写嵌套关联极繁琐
│
└── 需要灵活性与可控性兼顾
├── sqlx —— 轻量结构体映射
└── 或 GORM + Raw SQL 混合 —— 80% 操作用 GORM,20% 复杂操作用 Raw混合使用方案(推荐)
在实际项目中,最好的方式往往是混合使用 GORM 和原生 SQL:
// 1. 全局使用 GORM
var DB *gorm.DB
// 2. 简单 CRUD:用 GORM
func GetUser(id uint) (*User, error) {
var user User
err := DB.First(&user, id).Error
return &user, err
}
func ListActiveUsers(page, size int) ([]User, int64, error) {
var users []User
var total int64
DB.Model(&User{}).Where("status = ?", "active").Count(&total)
DB.Where("status = ?", "active").Scopes(Paginate(page, size)).Find(&users)
return users, total, nil
}
// 3. 复杂统计查询:用 Raw SQL
type SalesReport struct {
Date string `json:"date"`
Total float64 `json:"total"`
Orders int `json:"orders"`
}
func GetSalesReport(start, end time.Time) ([]SalesReport, error) {
var reports []SalesReport
err := DB.Raw(`
SELECT
DATE(created_at) as date,
SUM(amount) as total,
COUNT(*) as orders
FROM orders
WHERE created_at BETWEEN ? AND ?
GROUP BY DATE(created_at)
ORDER BY date
`, start, end).Scan(&reports).Error
return reports, err
}
// 4. 高性能写入:使用 Exec(跳过 GORM 的反射和回调)
func BatchUpdateStatus(userIDs []uint, status string) error {
return DB.Exec(
"UPDATE users SET status = ? WHERE id IN (?)",
status, userIDs,
).Error
}
// 5. 复杂事务 + 悲观锁:混合使用
func TransferMoney(fromID, toID uint, amount float64) error {
return DB.Transaction(func(tx *gorm.DB) error {
// 用 Raw SQL 做行锁(GORM 的 Clauses(Locking) 也可)
var balance float64
row := tx.Raw(
"SELECT balance FROM accounts WHERE id = ? FOR UPDATE",
fromID,
).Row()
row.Scan(&balance)
if balance < amount {
return errors.New("余额不足")
}
tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
return nil
})
}在 GORM 中使用 sqlx
// GORM 提供 *sql.DB 访问
sqlDB, _ := DB.DB()
// 直接用 sqlx 封装(共享连接池)
sqlxDB := sqlx.NewDb(sqlDB, "mysql")
// 复杂查询用 sqlx 的结构体映射
type ComplexResult struct {
UserName string `db:"user_name"`
OrderCnt int `db:"order_cnt"`
TotalAmt float64 `db:"total_amt"`
}
var results []ComplexResult
sqlxDB.Select(&results, `
SELECT u.name as user_name, COUNT(o.id) as order_cnt, SUM(o.amount) as total_amt
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at > ?
GROUP BY u.id
HAVING order_cnt > 5
`, startDate)总结
| 场景 | 推荐方案 |
|---|---|
| 快速原型 / 小中型项目 | GORM — 全功能、低代码量 |
| 高性能 / 低延迟系统 | database/sql — 零开销,完全控制 |
| CRUD 为主,部分复杂查询 | GORM + Raw SQL 混合 — 两者优势兼得 |
| SQL 密集 / 报表 / 数据分析 | database/sql 或 sqlx |
| 团队 ORM 经验丰富 | GORM |
| 团队 SQL 经验丰富 | database/sql 或 sqlx |
| 需要迁移 / 钩子 / 关联 | GORM |
| 微服务(表少、关系简单) | sqlx 或 database/sql — 够用且轻量 |
💡 核心原则:没有银弹。用最能表达意图的工具——简单的事用 GORM 节省时间,复杂的事用原生 SQL 确保精确控制。在同一个项目中混合使用两者是完全可行的,也是很多生产项目的实际做法。