核心概念
本章深入讲解 GORM 的核心类型和运作机制:gorm.DB、Session、链式操作、Model 约定、连接池原理。
*gorm.DB — 数据库实例
*gorm.DB 是 GORM 的核心,一切操作都基于它:
// gorm.Open 返回 *gorm.DB
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})*gorm.DB 不是什么:
- 不是单个数据库连接——它内部维护一个由
database/sql管理的连接池 - 不是线程不安全的——
*gorm.DB是并发安全的,可以在多个 goroutine 中共享
*gorm.DB 包含的核心属性:
| 属性 | 说明 |
|---|---|
db.Config |
全局配置(gorm.Config 的副本) |
db.Statement |
当前语句的状态(SQL 子句、Model、Table、Clauses) |
db.RowsAffected |
最后一条 SQL 影响的行数 |
db.Error |
最后一条 SQL 的错误 |
// 检查操作结果
result := db.Where("name = ?", "张三").Find(&users)
if result.Error != nil {
// 处理错误
}
fmt.Printf("影响 %d 行\n", result.RowsAffected)🚨 陷阱:GORM 的错误不会抛出 panic,而是累积在
db.Error中。务必检查每个操作链的最后一个返回值,或显式检查db.Error。
Session — 会话与链式操作
每次调用 db.Where()、db.Order()、db.Model() 等方法时,GORM 返回一个新的 *gorm.DB 实例——这就是"链式操作"的基础。
// 每条链式方法返回新的 *gorm.DB,不会修改原实例
tx := db.Where("age > ?", 18) // 返回新实例 t1
tx = tx.Order("name DESC") // 返回新实例 t2
tx = tx.Limit(10) // 返回新实例 t3
tx.Find(&users) // 执行查询🔬 深入原理:GORM 通过 Clone 机制在每次链式调用时复制 Statement 并附加新子句(Clause),最终在 Find/Create/Update/Delete 等 Finisher 方法中将所有子句编译为 SQL。
Session 模式的三种形态
// 1. 共享 Session — 默认,每次首个链式调用 Clone 一个新实例
db.Where("age > ?", 18).Find(&users)
// 2. 独立 Session — 全新空白实例
db.Session(&gorm.Session{NewDB: true}).Where("age > ?", 18).Find(&users)
// 3. Session 配置 — 覆盖特定行为
db.Session(&gorm.Session{
DryRun: true, // 只生成 SQL,不执行
SkipDefaultTransaction: true, // 跳过默认事务
PrepareStmt: true, // 使用预编译语句
Context: ctx, // 传递 context
FullSaveAssociations: true, // 保存时同时更新所有关联
}).Create(&user)💡 最佳实践:用
db.Session()临时修改 Session 行为(如 DryRun 调试);用db.WithContext(ctx)传递请求级上下文。
Model — 指定操作的模型
Model() 方法告诉 GORM 你要操作哪个模型:
// 方式一:通过参数类型推断
db.Find(&User{})
db.Find(&users) // []User 切片,GORM 自动推断模型
// 方式二:显式指定(当参数不足以推断时)
db.Model(&User{}).Where("name = ?", "张三").Update("age", 20)
// 方式三:用 Table 指定表名(无需模型)
db.Table("users").Where("name = ?", "张三").Update("age", 20)Model 参数的多种形式
// 结构体(零值字段不参与更新)
db.Model(&User{}).Updates(User{Name: "李四", Age: 0}) // Age=0 不会更新
// map(零值会参与更新)
db.Model(&User{}).Updates(map[string]interface{}{"name": "李四", "age": 0}) // Age=0 会更新
// 指定 ID 的模型
db.Model(&User{ID: 1}).Update("name", "王五") // → WHERE id = 1🚨 陷阱:结构体
Updates不会更新零值字段(0,"",false)。如需更新零值,使用map或Select指定字段。详见 03-基础CRUD。
Finisher 方法 — 触发 SQL 执行
链式方法分为两类:Builder(构建查询)和 Finisher(执行查询)。
| 类别 | 方法 | 说明 |
|---|---|---|
| Finisher | Create, Save, First, Find, Take, Last, Update, Updates, Delete, Count, Row, Rows, Scan, Pluck, Exec, Raw |
触发 SQL 执行 |
| Builder | Where, Not, Or, Select, Omit, Order, Limit, Offset, Group, Having, Joins, Preload, Table, Model, Clauses, Distinct, Debug, Unscoped |
构建查询条件 |
// Builder 不执行 SQL(构建阶段)
db = db.Where("age > ?", 18).Order("name DESC").Limit(10)
// 只有 Finisher 才执行 SQL
db.Find(&users)💡 只有 Finisher 才真正向数据库发送 SQL——这是理解 GORM 执行模型的关键。
连接池原理
GORM 使用 database/sql 的通用连接池,配置方法如下:
sqlDB, err := db.DB()
// 核心参数
sqlDB.SetMaxOpenConns(100) // 最大连接数
sqlDB.SetMaxIdleConns(10) // 最大空闲连接
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活(建议 < MySQL wait_timeout)
sqlDB.SetConnMaxIdleTime(10 * time.Minute) // 空闲连接回收时间
// 实时查看连接池状态
stats := sqlDB.Stats()
fmt.Printf("Open: %d, Idle: %d, InUse: %d, WaitCount: %d\n",
stats.OpenConnections, stats.Idle, stats.InUse, stats.WaitCount)🔬 深入原理:连接池工作流程:
请求到达 → 池中有空闲连接?
├── Yes → 复用空闲连接
└── No → 当前打开数 < MaxOpenConns?
├── Yes → 建立新连接
└── No → 阻塞等待,直到有连接归还或超时MySQL/PostgreSQL 连接池参数推荐
| 参数 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
MaxOpenConns |
min(100, max_connections*0.8) |
min(100, max_connections*0.8) |
不要超过数据库最大连接数 |
MaxIdleConns |
10~25 |
5~10 |
PG fork 进程成本高,不宜过多空闲 |
ConnMaxLifetime |
30min~1h |
30min~1h |
应小于 MySQL wait_timeout(默认 8h) |
ConnMaxIdleTime |
5~10min |
5~10min |
空闲连接回收 |
🚨 陷阱:默认情况下
MaxOpenConns=0(无限制)且ConnMaxLifetime永久有效。生产环境必须显式设置这些参数,否则连接数会无限增长或连接因 MySQLwait_timeout而断开报invalid connection。
错误处理模式
GORM 采用"错误累积"模式,而非返回 error:
// 方式一:检查 result.Error
result := db.Where("name = ?", "张三").Find(&user)
if result.Error != nil {
log.Printf("查询失败: %v", result.Error)
}
// 方式二:检查 RowsAffected
result := db.Delete(&User{}, 1)
if result.RowsAffected == 0 {
log.Println("没有记录被删除")
}
// 方式三:检查全局 db.Error
db.Where("name = ?", "张三").Find(&user)
if db.Error != nil {
log.Printf("操作失败: %v", db.Error)
}常见错误类型:
| 错误 | 含义 | 处理方式 |
|---|---|---|
gorm.ErrRecordNotFound |
First/Last/Take 未找到记录 |
errors.Is(err, gorm.ErrRecordNotFound) |
gorm.ErrInvalidDB |
DB 实例无效 | 检查 gorm.Open 是否成功 |
gorm.ErrInvalidTransaction |
事务操作无效 | 检查事务是否正确开启 |
gorm.ErrNotImplemented |
方法未实现 | 检查驱动是否支持 |
gorm.ErrMissingWhereClause |
更新/删除缺少 WHERE 条件 | 检查是否误操作全表更新/删除 |
gorm.ErrEmptySlice |
空切片传入 Find | 检查参数 |
gorm.ErrInvalidData |
无效数据 | 检查传入参数类型 |
gorm.ErrDuplicatedKey |
主键/唯一键重复 | 检查数据唯一性 |
// 判断是否未找到记录
err := db.First(&user, 1).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// 处理未找到的情况
}💡
First/Last/Take未找到记录时返回gorm.ErrRecordNotFound;Find未找到则不返回错误,返回空切片。
GORM 链式操作完整流程
db.Model(&User{}) // 1. 设置 Model
.Where("age > ?", 18) // 2. 构建 WHERE 子句
.Order("id DESC") // 3. 构建排序
.Limit(10) // 4. 构建限制
.Offset(0) // 5. 构建偏移
.Find(&users) // 6. Finisher:编译子句 → 生成 SQL → 执行 → 写回结果
// 生成 SQL:
// SELECT * FROM users WHERE age > 18 ORDER BY id DESC LIMIT 10 OFFSET 0