必须用 lock in share mode 的场景是读取数据后需防止被修改但允许多个事务并发读,如“读-校验-后续可能更新”;它加共享锁,不阻塞其他共享锁,只阻塞排他锁。

什么时候必须用 LOCK IN SHARE MODE
当你需要读取一行数据、同时确保它不被其他事务修改(但允许别人也读),又不想升级成排他锁时,LOCK IN SHARE MODE 是唯一选择。典型场景是“读-校验-后续可能更新”,比如检查用户余额是否足够再扣款,但不立刻扣——这时不能让别人在你读完后立刻把钱转走。
- 普通
SELECT不加锁,读到的是快照,无法阻止并发修改 -
SELECT ... FOR UPDATE加的是排他锁,会阻塞其他所有读写,过度悲观 -
LOCK IN SHARE MODE允许多个事务同时持有共享锁,只阻塞排他锁请求(如UPDATE、DELETE、SELECT ... FOR UPDATE)
LOCK IN SHARE MODE 的实际写法和常见错法
它必须出现在 SELECT 语句末尾,且只能用于已加索引的字段(否则会锁整张表)。最常被忽略的是:它不会自动提交,必须显式 COMMIT 或 ROLLBACK 才能释放锁。
- ✅ 正确:
SELECT * FROM accounts WHERE id = 123 LOCK IN SHARE MODE;(id是主键) - ❌ 错误:
SELECT * FROM accounts WHERE name = 'alice' LOCK IN SHARE MODE;(name无索引 → 行锁退化为表锁) - ❌ 错误:执行完就直接断开连接,没
COMMIT→ 锁一直挂着,拖垮并发 - ⚠️ 注意:在
READ COMMITTED隔离级别下,共享锁在语句结束时就释放;REPEATABLE READ下会持续到事务结束
和 SELECT ... FOR UPDATE 混用时的锁冲突表现
共享锁和排他锁互斥,但共享锁之间不互斥。这意味着两个事务可以同时对同一行执行 LOCK IN SHARE MODE,但只要有一个事务尝试 FOR UPDATE,就会被卡住,直到所有共享锁释放。
- 事务 A:
SELECT * FROM orders WHERE order_id = 456 LOCK IN SHARE MODE; - 事务 B:同样执行上面那条语句 → 成功,拿到共享锁
- 事务 C:
SELECT * FROM orders WHERE order_id = 456 FOR UPDATE;→ 阻塞,等 A 和 B 都提交 - 如果 A 或 B 在锁住期间执行
UPDATE→ 触发锁升级失败,报错ERROR 1205 (40001): Deadlock found
容易被绕过的坑:MVCC + 共享锁的边界
共享锁只锁“当前读”命中的行,不锁间隙(gap),也不影响幻读。如果你靠 LOCK IN SHARE MODE 想防止新记录插入导致的业务逻辑错乱,它做不到。
- 例如:
SELECT * FROM products WHERE category = 'book' LOCK IN SHARE MODE;→ 只锁已有 book 记录,新插入一条category='book'的记录仍能成功 - 想锁住范围?得用
SELECT ... FOR UPDATE配合唯一索引或主键范围,或者在REPEATABLE READ下依赖间隙锁(但LOCK IN SHARE MODE不触发间隙锁) - 另一个隐形坑:MySQL 8.0+ 对二级索引加共享锁时,会额外在聚簇索引上加隐式锁,排查死锁日志时容易漏看这一层
真正要用好 LOCK IN SHARE MODE,关键不是记住语法,而是清楚自己到底在防什么——是防别人改这一行?还是防别人插新行?防错对象,锁就白加了。










