跳到主要内容

GORM

问题

Go 中最流行的 ORM GORM 怎么用?有什么注意事项?

答案

基本使用

import "gorm.io/gorm"
import "gorm.io/driver/mysql"

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

// 模型定义
type User struct {
gorm.Model // ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex"`
Age int `gorm:"default:0"`
Posts []Post `gorm:"foreignKey:UserID"` // 一对多
}

// 自动迁移(开发环境)
db.AutoMigrate(&User{})

CRUD

// Create
user := User{Name: "Alice", Email: "alice@example.com"}
db.Create(&user) // user.ID 自动填充

// Read
var user User
db.First(&user, 1) // 按主键
db.Where("name = ?", "Alice").First(&user) // 条件查询
db.Find(&users) // 查询全部

// Update
db.Model(&user).Update("name", "Bob")
db.Model(&user).Updates(User{Name: "Bob", Age: 30})

// Delete(软删除)
db.Delete(&user, 1)
// 真正删除
db.Unscoped().Delete(&user, 1)

关联

// Preload 预加载
db.Preload("Posts").Find(&users)

// 嵌套预加载
db.Preload("Posts.Comments").Find(&users)

// 条件预加载
db.Preload("Posts", "published = ?", true).Find(&users)

// Joins 预加载(单条时更高效)
db.Joins("Company").First(&user, 1)

事务

// 自动事务
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err // 返回错误自动回滚
}
if err := tx.Create(&post).Error; err != nil {
return err
}
return nil // 返回 nil 自动提交
})

性能注意事项

GORM 常见性能陷阱
  1. N+1 查询:循环中查询关联,应该用 Preload
  2. **SELECT ***:默认查询所有字段,应该 Select("id, name")
  3. 大量 Create:应该用 CreateInBatches
  4. 忘记索引:确保查询条件字段有索引
// 批量创建
db.CreateInBatches(users, 100) // 每批 100 条

// 指定字段
db.Select("name", "email").Find(&users)

// 原生 SQL(复杂查询时)
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)

常见面试问题

Q1: GORM 的软删除怎么实现的?

答案gorm.Model 包含 DeletedAt 字段(gorm.DeletedAt 类型)。Delete 时不是真删除,而是设置 deleted_at = NOW()。所有查询自动加 WHERE deleted_at IS NULL 条件。用 Unscoped() 可以查到已删除的记录。

Q2: GORM 和 database/sql 性能差多少?

答案:GORM 因为反射和链式构建,比原生 SQL 慢 2~5 倍。对于大多数 Web 应用,这个开销可以忽略(瓶颈通常在数据库本身)。性能极端敏感的场景用 sqlxsqlc

相关链接