作为 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',16) ON 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 `user` WHERE id = 1 FOR UPDATE
FOR SHARE
FOR SHARE 会施加一个共享锁,允许多个事务同时读取,但会阻止任何 UPDATE 或 DELETE 操作。
db.Clauses(clause.Locking{Strength: clause.LockingStrengthShare}).Where("id = ?", 1).Find(&user)
// SELECT * FROM `user` WHERE 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 `user` WHERE id = 1
db.Clauses(hints.CommentBefore("select", "hint")).Where("id = ?", 1).Find(&user)
// /* hint */ SELECT * FROM `user` WHERE id = 1 AND `user`.`id` = 1
db.Clauses(hints.CommentAfter("where", "hint")).Where("id = ?", 1).Find(&user)
// SELECT * FROM `user` WHERE id = 1 AND `user`.`id` = 1 /* hint */
References
github.com/go-gorm/gor…
推荐阅读