Skip to content

第31章:错误处理与调试 ​

本章目标 ​

学完本章后,你将能够:

  1. 识别和处理 GORM 的各种错误类型
  2. 正确使用 errors.Is() 判断错误
  3. 掌握 GORM 调试三板斧
  4. 避免常见的错误处理陷阱

前置知识 ​

  • 需要先学习:第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})  // 单次用自定义 Logger

31.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,不要忽略

练习题 ​

  1. 写一段代码处理 ErrRecordNotFound,区分"记录不存在"和"数据库错误"。
  2. 尝试触发 ErrMissingWhereClause,然后正确处理。
  3. 使用 Debug() 观察一条复杂查询的耗时。
  4. 比较 db.Error 和 result.Error 在不同场景下的行为。
  5. (思考题)为什么 GORM 不允许无 WHERE 的批量更新/删除?这个设计的利弊是什么?

把零散知识,整理成可以反复走的路。