news 2026/3/20 3:33:43

gorm Expr使用小技巧

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gorm Expr使用小技巧

基于实际项目经验,我整理了一些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配合ScopesClause非常强大:

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分批避免一次性更新导致锁表

重要提醒

  • 永远ModelWhere中指定更新范围,避免无WHERE子句的全表更新。

  • 测试环境先用db.Debug()打印SQL,确认无误后再上生产。

  • 复杂逻辑优先用Expr,简单场景用mapstruct更清晰。


七、调试技巧

开启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 }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!