FOR SHARE加共享锁(S锁),允许多事务并发读但阻塞写;FOR UPDATE加排他锁(X锁),独占读写,阻塞其他所有锁及DML操作。

FOR SHARE 和 FOR UPDATE 的锁类型完全不同
根本区别在于: FOR SHARE 加的是共享锁(S 锁),FOR UPDATE 加的是排他锁(X 锁)。共享锁允许多个事务同时持有,但会阻塞其他事务的 X 锁;排他锁是独占的,一旦持有,其他事务连 S 锁和 X 锁都拿不到。
这意味着:
-
FOR SHARE允许并发读,但禁止任何写(UPDATE/DELETE)和进一步加FOR UPDATE或FOR SHARE(注意:MySQL 8.0 中FOR SHARE默认允许其他事务再加FOR SHARE,但不允许加FOR UPDATE) -
FOR UPDATE连读都不让——其他事务执行SELECT ... FOR SHARE或普通SELECT(在 RC 隔离级别下可能看到旧版本,但在 RR 下会被阻塞)都会被挂起,直到锁释放
什么时候该用 FOR SHARE 而不是 FOR UPDATE
典型场景是「只读校验 + 后续由其他逻辑写入」,且你不想阻塞别人读。比如检查库存是否充足、验证用户余额是否足够、确认父记录存在但不马上扣减。
常见误用是:以为 FOR SHARE 能防止丢失更新——它不能。因为两个事务都可以拿到 S 锁并各自执行 UPDATE,最终仍可能覆盖对方结果。真正防丢失更新必须用 FOR UPDATE 或应用层重试 + 版本号。
示例对比:
-- 场景:检查订单状态是否为 'pending',然后由另一服务触发发货 -- ✅ 推荐:用 FOR SHARE,避免阻塞其他查询订单状态的操作 SELECT status FROM orders WHERE order_id = 123 FOR SHARE; <p>-- ❌ 不必要:用 FOR UPDATE,会让所有查这个订单的请求排队 SELECT status FROM orders WHERE order_id = 123 FOR UPDATE;
FOR SHARE 在 MySQL 8.0 中多了哪些实用选项
FOR SHARE 是 MySQL 8.0 引入的语法,替代了旧的 LOCK IN SHARE MODE,但兼容保留。它支持三个关键扩展,LOCK IN SHARE MODE 不支持:
-
NOWAIT:不等待锁,直接报错Lock wait timeout exceeded→ 避免线程卡死 -
SKIP LOCKED:跳过已被锁定的行,常用于任务队列分发(如多个 worker 并发取未处理任务) -
OF table_name:只对指定表加锁,子查询中其他表不受影响 → 精确控制锁范围
例如:
SELECT * FROM tasks WHERE status = 'pending' ORDER BY id LIMIT 1 FOR SHARE SKIP LOCKED;
这条语句能安全用于高并发任务消费,不会出现重复分配。
容易忽略的坑:autocommit 关闭和隔离级别影响
这两个锁语句只在显式事务中生效,即必须 START TRANSACTION 或已将 autocommit=0。开启 autocommit 时,每条语句自动提交,锁瞬间释放,等于没锁。
另外,RR(可重复读)和 RC(读已提交)下行为不同:
- 在 RR 下,
FOR UPDATE不仅锁住匹配行,还可能锁住间隙(gap lock),防止幻读 - 在 RC 下,
FOR UPDATE只锁匹配到的行,不锁间隙;FOR SHARE同理 —— 这意味着 RC 下更容易发生幻读或并发修改冲突 - 如果业务依赖强一致性(比如金融扣款),别只靠锁,还得配合唯一约束或应用层幂等控制
最常被漏掉的一点:锁只作用于当前 SELECT 涉及的表。子查询里没写 FOR SHARE 或 FOR UPDATE,那张表就完全没锁 —— 即使外层加了锁也无效。


















