第31章:错误处理与调试
本章目标
学完本章后,你将能够:
- 识别和处理 GORM 的各种错误类型
- 正确使用
errors.Is()判断错误 - 掌握 GORM 调试三板斧
- 避免常见的错误处理陷阱
前置知识
- 需要先学习:第03-04章(连接与配置)、第09-28章(CRUD)
- 需要了解:Go 的 error 处理(
errors.Is/errors.As)
31.1 GORM 错误类型总览
go
// 内置错误常量
gorm.ErrRecordNotFound // 记录不存在
gorm.ErrInvalidTransaction // 无效事务
gorm.ErrNotImplemented // 功能未实现
gorm.ErrMissingWhereClause // 缺少 WHERE 条件
gorm.ErrUnsupportedRelation // 不支持的关联
gorm.ErrPrimaryKeyRequired // 主键缺失
gorm.ErrModelValueRequired // 模型值缺失
gorm.ErrInvalidData // 无效数据
gorm.ErrUnsupportedDriver // 不支持的驱动
gorm.ErrRegistered // 已注册
gorm.ErrInvalidField // 无效字段
gorm.ErrEmptySlice // 空切片
gorm.ErrDryRunModeUnsupported // DryRun 不支持此操作31.2 常见错误处理
ErrRecordNotFound
go
var user User
result := db.First(&user, 999)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
fmt.Println("用户不存在")
} else {
fmt.Println("数据库错误:", result.Error)
}
}ErrMissingWhereClause
go
// 批量更新/删除忘记 WHERE
result := db.Model(&User{}).Update("status", "active")
// result.Error = gorm.ErrMissingWhereClause
// ✅ 加上条件或允许全表
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("status", "active")ErrDuplicatedKey(MySQL 驱动层)
go
result := db.Create(&User{Email: "existing@example.com"})
if result.Error != nil {
// 判断是否是唯一键冲突
if strings.Contains(result.Error.Error(), "Duplicate entry") {
fmt.Println("邮箱已被使用")
}
}31.3 db.Error vs result.Error
go
// db.Error:获取最近一次操作的错误
db.Where("name = ?", "张三").Find(&users)
if db.Error != nil {
// 处理错误
}
// result.Error:特定操作的错误(推荐)
result := db.Where("name = ?", "张三").Find(&users)
if result.Error != nil {
// 更精确
}📌 推荐使用
result.Error。db.Error在所有链式操作中共享,可能被后续操作覆盖。
31.4 调试三板斧
1. Debug():打印 SQL
go
db.Debug().Where("age > ?", 18).Find(&users)
// 直接看到生成的 SQL 和执行时间2. DryRun:预览 SQL
go
stmt := db.Session(&gorm.Session{DryRun: true}).Create(&user)
fmt.Println(stmt.Statement.SQL.String())
// 看到 SQL,但数据库不受影响3. Logger:调整日志级别
go
db.Logger.LogMode(logger.Info) // 全局打印所有 SQL
db.Session(&gorm.Session{Logger: customLogger}) // 单次用自定义 Logger31.5 错误处理最佳实践
go
func GetUserByID(db *gorm.DB, id uint) (*User, error) {
var user User
result := db.First(&user, id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, ErrUserNotFound // 自定义业务错误
}
return nil, fmt.Errorf("查询用户失败: %w", result.Error)
}
return &user, nil
}常见错误
错误1:用 == 比较错误
go
// ❌ 错误的比较方式
if result.Error == gorm.ErrRecordNotFound { }
// ✅ 用 errors.Is
if errors.Is(result.Error, gorm.ErrRecordNotFound) { }错误2:忽略 Error
go
// ❌ 不检查错误
db.Create(&user)
fmt.Println(user.ID) // 可能为 0,你不知道是否成功
// ✅ 始终检查
result := db.Create(&user)
if result.Error != nil { ... }错误3:用 db.Error 而不是 result.Error
go
// ❌ 链式调用的 Error 可能不精确
db.Where("a = ?", 1)
db.Where("b = ?", 2).Find(&users)
err := db.Error // 不清楚是哪一步的 Error
// ✅ 用 result
result := db.Where("a = ?", 1).Where("b = ?", 2).Find(&users)
err := result.Error // 明确是这次操作的 Error本章小结
errors.Is(result.Error, gorm.ErrRecordNotFound)判断错误类型- 用
result.Error而非db.Error - 调试:Debug() 看 SQL,DryRun 预览,Logger 控制输出
- 永远检查 Error,不要忽略
练习题
- 写一段代码处理
ErrRecordNotFound,区分"记录不存在"和"数据库错误"。 - 尝试触发
ErrMissingWhereClause,然后正确处理。 - 使用 Debug() 观察一条复杂查询的耗时。
- 比较
db.Error和result.Error在不同场景下的行为。 - (思考题)为什么 GORM 不允许无 WHERE 的批量更新/删除?这个设计的利弊是什么?