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 常见性能陷阱
- N+1 查询:循环中查询关联,应该用
Preload - **SELECT ***:默认查询所有字段,应该
Select("id, name") - 大量 Create:应该用
CreateInBatches - 忘记索引:确保查询条件字段有索引
// 批量创建
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 应用,这个开销可以忽略(瓶颈通常在数据库本身)。性能极端敏感的场景用 sqlx 或 sqlc。