GORM Clauses 使用场景盘点:你可能忽略的强大特性

2 阅读3分钟

作为 Go 语言中最受欢迎的 ORM 之一,GORM 以其简洁的 API 和强大的功能赢得了广大开发者的青睐。

本文介绍在面对更复杂的业务场景时,如数据合并(Upsert)、高并发下的数据锁,或需要利用特定数据库的高级特性时,GORM 的高级功能—— Clauses(子句)。


Clause(子句)

在 GORM 中,一个 Clause(子句)是 SQL 语句的一个组成部分,例如 SELECT、FROM、WHERE、ORDER BY 等。db.Clauses 方法接受一个或多个 clause.Interface 类型的参数,这些参数会直接影响最终生成的 SQL。

它的核心作用是为 GORM 语句添加一些通过普通链式调用无法实现或难以实现的高级特性。

接下来,我们将深入探讨几个最常用、也最具代表性的 db.Clauses() 用法。


冲突处理 (Upsert) - clause.OnConflict

这是 db.Clauses 最常用的场景之一,用于实现 "如果存在则更新,否则插入" 的操作(Upsert 或 Merge)。非常适合用在 幂等写入唯一约束更新 场景。

  • 更新指定字段
 db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "email"}},                  // 冲突字段 email 是唯一索引
  DoUpdates: clause.AssignmentColumns([]string{"name""age"}), // 冲突后只更新 name 和 age 字段
 }).Create(&user)
    
 // INSERT INTO `user` (`name`,`email`,`age`) VALUES ('Go','go@example.com',15) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`age`=VALUES(`age`)
  • 更新所有字段
 db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "email"}}, // 冲突字段 email 是唯一索引
  UpdateAll: true,                             // 冲突后更新所有字段
 }).Create(&user)

 // INSERT INTO `user` (`name`,`email`,`age`) VALUES ('Go','go@example.com',15) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`email`=VALUES(`email`),`age`=VALUES(`age`)
  • 自定义更新值
 db.Clauses(clause.OnConflict{
  Columns: []clause.Column{{Name: "email"}}, // 冲突字段 email 是唯一索引
  DoUpdates: clause.Assignments(map[string]interface{}{
   "name""Go-updated",
   "age":  gorm.Expr("age + 1"), // gorm.Expr("VALUES(age)")  可以使用表达式
  }),
 }).Create(&user)

 // INSERT INTO `user` (`name`,`email`,`age`) VALUES ('Go','go@example.com',15) ON DUPLICATE KEY UPDATE `age`=age + 1,`name`='Go-updated'
  • 忽略冲突
 db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

 // INSERT INTO `user` (`name`,`email`,`age`) VALUES ('Go','go@example.com',16ON DUPLICATE KEY UPDATE `id`=`id`

数据锁 - clause.Locking

在并发读写场景中,为了保证数据的一致性,我们通常需要使用悲观锁。GORM 的 db.Clauses() 可以轻松地在事务中实现 FOR UPDATE 和 FOR SHARE。

  • FOR UPDATE

FOR UPDATE 会在查询到的行上施加一个排他锁,阻止其他事务读或写,直到当前事务提交。

 db.Clauses(clause.Locking{Strength: clause.LockingStrengthUpdate}).Where("id = ?", 1).Find(&user)
 
 // SELECT * FROM `userWHERE id = 1 FOR UPDATE
  • FOR SHARE

FOR SHARE 会施加一个共享锁,允许多个事务同时读取,但会阻止任何 UPDATE 或 DELETE 操作。

 db.Clauses(clause.Locking{Strength: clause.LockingStrengthShare}).Where("id = ?", 1).Find(&user)

 // SELECT * FROM `userWHERE id = 1 FOR SHARE

索引提示 - Index Hints

hints.UseIndex/hints.ForceIndex/hints.IgnoreIndex 明确指定/忽略某些索引。

 import "gorm.io/hints"
 
 db.Clauses(hints.ForceIndex("idx_name")).Where("name = ?", "Go").Find(&user// 强制使用名为 `idx_name` 的索引

 // SELECT * FROM `user` FORCE INDEX (`idx_name`) WHERE name = 'Go'

注释提示 - Comment Hints

Comment Hints 允许你在 SQL 语句的特定位置添加注释。这通常用于在数据库慢查询日志中追踪查询来源。

 db.Clauses(hints.Comment("select", "hint")).Where("id = ?", 1).Find(&user)

 // SELECT /* hint */ * FROM `userWHERE id = 1

 db.Clauses(hints.CommentBefore("select", "hint")).Where("id = ?", 1).Find(&user)

 // /* hint */ SELECT * FROM `userWHERE id = 1 AND `user`.`id` = 1

 db.Clauses(hints.CommentAfter("where", "hint")).Where("id = ?", 1).Find(&user)
 
 // SELECT * FROM `userWHERE id = 1 AND `user`.`id` = 1 /* hint */

References
github.com/go-gorm/gor…

推荐阅读