GORM面试题
2026/1/15大约 6 分钟GORM面试题Go
GORM 面试题
一、基础概念
1.1 什么是 GORM?
答:GORM 是 Go 语言的 ORM(对象关系映射)库,提供完整的数据库操作功能。
核心特性:
- 全功能 ORM(CRUD、关联、事务)
- 自动迁移
- 钩子函数(Before/After Create/Update/Delete)
- 预加载(Eager Loading)
- 事务支持
- 软删除
- 批量操作
1.2 GORM 的优缺点?
答:
优点:
- 开发效率高,减少 SQL 编写
- 类型安全,编译时检查
- 自动防止 SQL 注入
- 功能完善,社区活跃
- 支持多种数据库
缺点:
- 性能略低于原生 SQL
- 复杂查询不如原生 SQL 灵活
- 学习曲线
- 可能产生意外的 SQL
1.3 GORM 支持哪些数据库?
答:
- MySQL
- PostgreSQL
- SQLite
- SQL Server
- TiDB
- ClickHouse(通过插件)
二、模型定义
2.1 如何定义 GORM 模型?
答:
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex"`
Age int `gorm:"default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}关键点:
- 使用结构体标签定义约束
- ID 字段默认为主键
- CreatedAt/UpdatedAt 自动维护
- DeletedAt 启用软删除
2.2 gorm.Model 是什么?
答:
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// 使用
type User struct {
gorm.Model
Name string
}作用:提供常用的基础字段,包括主键、创建时间、更新时间和软删除。
2.3 如何自定义表名?
答:
// 方式1:实现 TableName 方法
func (User) TableName() string {
return "my_users"
}
// 方式2:全局配置单数表名
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})三、CRUD 操作
3.1 Create、Save 和 Updates 的区别?
答:
| 方法 | 说明 | 使用场景 |
|---|---|---|
| Create | 插入新记录 | 创建新数据 |
| Save | 保存所有字段 | 更新整个对象 |
| Update | 更新单个字段 | 更新一个字段 |
| Updates | 更新多个字段 | 更新多个字段(非零值) |
// Create
db.Create(&user)
// Save(更新所有字段)
db.Save(&user)
// Update(单个字段)
db.Model(&user).Update("name", "新名字")
// Updates(多个字段,零值不更新)
db.Model(&user).Updates(User{Name: "新名字", Age: 20})3.2 如何更新零值字段?
答:
// 方式1:使用 Select
db.Model(&user).Select("Age").Updates(User{Age: 0})
// 方式2:使用 map
db.Model(&user).Updates(map[string]interface{}{"age": 0})
// 方式3:使用 UpdateColumn(跳过钩子)
db.Model(&user).UpdateColumn("age", 0)3.3 First、Take 和 Last 的区别?
答:
// First:按主键升序,取第一条
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// Last:按主键降序,取第一条
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
// Take:不排序,随机取一条
db.Take(&user)
// SELECT * FROM users LIMIT 1;四、查询操作
4.1 如何避免 N+1 查询?
答:
// 不好:N+1 查询
var users []User
db.Find(&users)
for _, user := range users {
var articles []Article
db.Where("user_id = ?", user.ID).Find(&articles)
}
// 好:使用 Preload
var users []User
db.Preload("Articles").Find(&users)4.2 Preload 和 Joins 的区别?
答:
| 特性 | Preload | Joins |
|---|---|---|
| 查询次数 | 2次(或更多) | 1次 |
| 适用场景 | 一对多、多对多 | 一对一 |
| 性能 | 数据量大时更好 | 数据量小时更好 |
// Preload:两次查询
db.Preload("Company").Find(&users)
// SELECT * FROM users;
// SELECT * FROM companies WHERE id IN (1,2,3);
// Joins:一次查询
db.Joins("Company").Find(&users)
// SELECT users.*, companies.* FROM users
// LEFT JOIN companies ON companies.id = users.company_id4.3 如何实现分页查询?
答:
func (s *UserService) List(page, pageSize int) ([]User, int64, error) {
var users []User
var total int64
// 统计总数
s.db.Model(&User{}).Count(&total)
// 分页查询
offset := (page - 1) * pageSize
err := s.db.Offset(offset).Limit(pageSize).Find(&users).Error
return users, total, err
}五、关联关系
5.1 GORM 支持哪些关联关系?
答:
- Belongs To(属于):外键在当前表
- Has One(拥有一个):外键在关联表
- Has Many(拥有多个):一对多
- Many To Many(多对多):需要中间表
// Belongs To
type User struct {
CompanyID uint
Company Company
}
// Has One
type User struct {
CreditCard CreditCard
}
// Has Many
type User struct {
Articles []Article
}
// Many To Many
type User struct {
Languages []Language `gorm:"many2many:user_languages;"`
}5.2 如何操作关联数据?
答:
// 创建时保存关联
user := User{
Name: "张三",
Articles: []Article{
{Title: "文章1"},
{Title: "文章2"},
},
}
db.Create(&user)
// 添加关联
db.Model(&user).Association("Articles").Append(&article)
// 替换关联
db.Model(&user).Association("Articles").Replace(&article)
// 删除关联
db.Model(&user).Association("Articles").Delete(&article)
// 清空关联
db.Model(&user).Association("Articles").Clear()
// 统计数量
count := db.Model(&user).Association("Articles").Count()六、事务
6.1 如何使用事务?
答:
// 方式1:手动事务
tx := db.Begin()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
// 方式2:自动事务
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err // 自动回滚
}
return nil // 自动提交
})6.2 事务的隔离级别有哪些?
答:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
// 设置隔离级别
tx := db.Begin(&sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
})6.3 什么是悲观锁和乐观锁?
答:
悲观锁:假设会发生冲突,提前加锁
// FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, 1)乐观锁:假设不会冲突,使用版本号
type User struct {
ID uint
Name string
Version int
}
// 更新时检查版本号
db.Model(&User{}).
Where("id = ? AND version = ?", id, version).
Updates(map[string]interface{}{
"name": "新名字",
"version": version + 1,
})七、性能优化
7.1 如何优化 GORM 性能?
答:
- 连接池配置
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)- 只查询需要的字段
db.Select("id", "name").Find(&users)- 使用索引
type User struct {
Email string `gorm:"index"`
}- 批量操作
db.CreateInBatches(users, 100)- 启用预编译
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
PrepareStmt: true,
})7.2 如何避免 SELECT *?
答:
// 不好
db.Find(&users)
// 好:只查询需要的字段
db.Select("id", "name", "email").Find(&users)
// 或使用专门的结构体
type UserBasic struct {
ID uint
Name string
Email string
}
db.Model(&User{}).Find(&userBasics)八、软删除
8.1 什么是软删除?
答:软删除是指删除记录时不真正删除,而是标记为已删除。
type User struct {
ID uint
Name string
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// 删除(软删除)
db.Delete(&user)
// UPDATE users SET deleted_at='2024-01-01' WHERE id=1;
// 查询时自动过滤
db.Find(&users)
// SELECT * FROM users WHERE deleted_at IS NULL;
// 包含软删除记录
db.Unscoped().Find(&users)
// 永久删除
db.Unscoped().Delete(&user)九、钩子函数
9.1 GORM 有哪些钩子函数?
答:
创建:
- BeforeSave
- BeforeCreate
- AfterCreate
- AfterSave
更新:
- BeforeSave
- BeforeUpdate
- AfterUpdate
- AfterSave
删除:
- BeforeDelete
- AfterDelete
查询:
- AfterFind
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.UUID = uuid.New().String()
return nil
}
func (u *User) AfterCreate(tx *gorm.DB) error {
log.Printf("用户 %s 创建成功", u.Name)
return nil
}十、面试回答模板
10.1 请介绍一下 GORM
GORM 是 Go 语言最流行的 ORM 库,提供完整的数据库操作功能。
核心特性:
- 全功能 ORM,支持 CRUD、关联、事务
- 自动迁移和软删除
- 钩子函数和预加载
- 支持多种数据库
优势:
- 提高开发效率
- 类型安全,防止 SQL 注入
- 代码可读性好
注意事项:
- 性能略低于原生 SQL
- 需要注意 N+1 查询问题
- 合理使用索引和连接池
10.2 如何优化 GORM 性能?
GORM 性能优化主要从以下几个方面:
- 连接池配置:合理设置最大连接数和空闲连接数
- 查询优化:只查询需要的字段,使用索引
- 避免 N+1:使用 Preload 或 Joins 预加载关联
- 批量操作:使用 CreateInBatches 批量插入
- 启用预编译:PrepareStmt 提高重复查询性能
- 缓存:对热点数据使用 Redis 缓存
- 读写分离:使用 dbresolver 插件
