基于实际项目经验,我整理了一些GORMExpr的实用技巧,涵盖从基础到进阶的多种场景:
一、核心用法:原子操作
Expr最经典的场景是实现数据库层面的原子计算,避免并发问题:
go
复制
// 库存扣减(高并发必备) db.Model(&product).Update("stock", gorm.Expr("stock - ?", 1)) // 字段自增/自减 db.Model(&user).Update("age", gorm.Expr("age + ?", 1)) // 复杂计算 db.Model(&order).Update("total", gorm.Expr("total * ? + ?", 0.9, 5))技巧:在秒杀、抢购场景中,务必用Expr保证库存扣减的原子性,避免用First->计算->Save模式导致超卖。
二、动态查询条件拼接
当查询条件需要动态组合时,Expr配合Scopes或Clause非常强大:
go
复制
// 动态JOIN + 条件组合 func withFamilyJoin() func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Joins("INNER JOIN family_members ON students.id = family_members.student_id") } } // 构建动态WHERE子句 whereClause := gorm.Expr("students.name LIKE ?", "%张三%") if searchFamily { db.Scopes(withFamilyJoin()) whereClause = gorm.Expr("? OR family_members.name LIKE ?", whereClause, "%张三%") } db.Clauses(whereClause).First(&student)效果:根据searchFamily开关自动决定是否联表,避免不必要的性能损耗。
三、子查询更新与查询
子查询更新(将查询结果作为更新值):
go
复制
// 将用户公司名更新为关联表的名称 db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id") )子查询条件:
go
复制
// 查询金额大于平均值的订单 db.Where("amount > ?", db.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").QueryExpr()).Find(&orders)四、特殊排序与字段函数
自定义排序(如按指定ID顺序):
go
复制
db.Clauses(clause.OrderBy{ Expression: clause.Expr{ SQL: "FIELD(id, ?)", Vars: []interface{}{[]int{3, 1, 2}}, WithoutParentheses: true, }, }).Find(&users) // 生成:ORDER BY FIELD(id, 3, 1, 2)空间数据查询(PostGIS):
go
复制
db.Where("location = ?", gorm.Expr("ST_PointFromText(?)", "POINT(100 100)")).First(&user)五、批量更新与条件组合
批量更新零值字段(结构体无法更新零值,用map或Expr解决):
go
复制
// 错误:Age=0不会被更新 db.Model(&user).Updates(User{Name: "new", Age: 0}) // 正确:用map或Expr更新零值 db.Model(&user).Updates(map[string]interface{}{"name": "new", "age": 0}) db.Model(&user).Update("age", gorm.Expr("?", 0))范围条件更新:
go
复制
// ID在1-10之间的用户年龄+1 db.Model(&User{}).Where("id BETWEEN ? AND ?", 1, 10).Update("age", gorm.Expr("age + 1"))六、性能与安全最佳实践
表格
复制
| 场景 | 推荐方式 | 避坑指南 |
|---|---|---|
| 简单数值更新 | UpdateColumn+Expr | 跳过Hook,比Save快30%以上 |
| 高并发计数 | Expr原子操作 | 严禁先查后改,100%会超卖 |
| 多字段更新 | Select指定字段 | 避免更新所有字段,减少锁时间 |
| 敏感操作 | 必须加Where条件 | 防止漏条件导致全表更新 |
| 大数据量 | FindInBatches分批 | 避免一次性更新导致锁表 |
重要提醒:
永远在
Model或Where中指定更新范围,避免无WHERE子句的全表更新。测试环境先用
db.Debug()打印SQL,确认无误后再上生产。复杂逻辑优先用
Expr,简单场景用map或struct更清晰。
七、调试技巧
开启SQL日志查看实际生成的表达式:
go
复制
db.Debug().Model(&product).Update("stock", gorm.Expr("stock - ?", 1)) // 输出:UPDATE `products` SET `stock` = stock - 1, `updated_at` = '...' WHERE `id` = 1通过这些技巧,可以在保证安全的前提下,大幅提升GORM操作的灵活性和性能。
func (self *UiWordlibRequest) UpdateWordlibCount(req *BatchBelongRequest) *basedto.IchubResult { var dao = worddao.FindBeanVocabsDao() var ret = dao.UpdateMap2Result(req.VocabId, map[string]interface{}{ "total": gorm.Expr("(select count(*) from vocab_words where vocab_id = ?)", req.VocabId), }) return ret }