必须在显式开启的事务中使用clauses(clause.locking{strength: ...}),裸查或autocommit=1模式下for update无效;gorm中strength仅支持clause.lockingstrengthupdate和clause.lockingstrengthshared,字符串写法会静默失效;where未走索引将导致间隙锁甚至全表锁。

必须在显式开启的事务中用 Clauses(clause.Locking{Strength: ...}),裸查或自动提交模式下锁无效。
为什么 db.First() 加 FOR UPDATE 没起作用?
常见错误是直接在非事务 DB 实例上调用带锁查询,比如:db.Clauses(...).First(&user, 1)。MySQL 在 autocommit=1 模式下会忽略 FOR UPDATE,不报错但也不加锁;GORM 同样不会报错,锁形同虚设。
- 锁只在事务生命周期内有效,事务提交/回滚后立即释放
-
db.Transaction()或db.Begin()是唯一安全入口,不能绕过 - 若用
db.Exec("SELECT ... FOR UPDATE")手写 SQL,也必须确保该语句执行在事务对象上(如tx.Exec(...))
Clauses(clause.Locking{Strength: ...}) 的合法值有哪些?
Strength 字段只接受两个预定义常量,字符串字面量会静默失效:
-
clause.LockingStrengthUpdate→ 生成FOR UPDATE(排他锁) -
clause.LockingStrengthShared→ 生成LOCK IN SHARE MODE(共享锁) -
"UPDATE"或"SHARE"字符串写法不推荐,易拼错且无编译检查 -
"SHARED"、"READ"、"X"等均被 GORM 忽略,SQL 中不出现锁关键字
WHERE 条件没走索引会导致什么?
这是线上最隐蔽的锁失效原因:InnoDB 无法精确定位行时,会升级为间隙锁甚至全表扫描加锁。
- 例如
WHERE name LIKE "%admin%"或WHERE YEAR(created_at) = 2026,大概率触发表级锁 - 用
EXPLAIN确认type是ref/range,而非ALL或index - 复合索引要注意最左前缀,
WHERE status = ? AND id = ?若只有id索引,仍可能失效 - 没加 WHERE 的
First()或Find()会锁全表,严禁在生产环境使用
真正难的不是写对那几行代码,而是每次加锁前都得确认:事务是否真开启了、SQL 是否真发给了事务连接、执行计划是否真的只锁了那几行——少一个环节,锁就等于没加。











