shared lock(s锁)是mysql中允许多事务并发读同一行但阻止写操作的悲观锁;适用于需“只读且防并发修改”的场景,如订单校验、优惠券核销前幂等检查,必须在事务中显式通过lock in share mode或gorm的clauses声明使用,并确保where条件走索引以避免锁升级。

Shared Lock 是什么,什么时候该用
Shared Lock(LOCK IN SHARE MODE)是 MySQL 提供的一种悲观锁机制,允许多个事务同时读取同一行,但阻止任何事务对该行执行写操作(包括 UPDATE、DELETE、SELECT FOR UPDATE)。它适合「只读但需防并发修改」的场景,比如订单状态校验、优惠券核销前的幂等检查、配置项安全读取等。
注意:GORM 默认所有查询都是快照读(MVCC),不加锁;必须显式声明才能触发 Shared Lock。
GORM 中正确写法:Clauses + Strength: "SHARE"
不能用字符串拼接或自定义 SQL 模拟,必须通过 Clauses(clause.Locking{Strength: clause.LockingStrengthShared}) 声明。否则 GORM 不会生成 LOCK IN SHARE MODE,且可能在不同数据库驱动下行为不一致。
-
Strength: "SHARE"是唯一有效值,"SHARED"或"READ"会静默失效 - 必须在事务中使用,否则 MySQL 会忽略锁(自动提交模式下锁立即释放,无意义)
- 锁定范围仅限于
WHERE条件命中行,不加WHERE会锁全表(极危险) - 若查询走不到索引,MySQL 可能升级为表级锁,务必确认执行计划
示例:
err := db.Transaction(func(tx *gorm.DB) error {
var user User
if err := tx.Clauses(clause.Locking{
Strength: clause.LockingStrengthShared,
}).Where("id = ? AND status = ?", 123, "active").First(&user).Error; err != nil {
return err
}
// 此时其他事务无法 UPDATE/DELETE 这条 user 记录
return nil
})
常见错误:WHERE 条件没走索引 or 锁被绕过
Shared Lock 生效的前提是 MySQL 能精确锁定目标行。如果 WHERE 条件未命中索引(如对非索引字段模糊匹配、函数包裹字段),InnoDB 会退化为扫描并加锁,甚至锁住整个聚簇索引范围。
- 查
WHERE email LIKE '%@gmail.com'→ 可能锁全表 - 查
WHERE DATE(created_at) = '2026-09-20'→ 索引失效,锁范围扩大 - 联合索引
(status, id),但只写WHERE id = 123→ 索引无法使用,锁失效风险高
验证方式:在事务内执行 EXPLAIN 对应查询,确认 type 是 const 或 ref,且 key 显示用了哪个索引。
和 FOR UPDATE 的关键区别与选型建议
Shared Lock 和 FOR UPDATE 都是行级悲观锁,但语义和阻塞行为完全不同:
-
FOR UPDATE:当前事务可读可写,其他事务对该行的读(加锁读)和写全部阻塞 -
LOCK IN SHARE MODE:当前事务只读,其他事务可读(普通 SELECT),但不可写、不可加FOR UPDATE或再加SHARE
选型看业务意图:需要「自己改,别人不准动」→ 用 FOR UPDATE;只需要「自己读,别人别改」→ 用 SHARE。混用时注意死锁风险,比如事务 A 先 SHARE 再 FOR UPDATE,事务 B 反过来,就可能卡住。
真正容易被忽略的是隔离级别影响:在 READ COMMITTED 下,Shared Lock 只在语句执行期间持有;但在 REPEATABLE READ(MySQL 默认)下,它会持续到事务结束——这意味着长时间事务会拖住写入,必须控制事务粒度。











