Skip to content
Go
核心概念

核心概念

本章深入讲解 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)。如需更新零值,使用 mapSelect 指定字段。详见 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 永久有效。生产环境必须显式设置这些参数,否则连接数会无限增长或连接因 MySQL wait_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.ErrRecordNotFoundFind 未找到则不返回错误,返回空切片。


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