共享锁(S锁)的加锁场景

一、显式加锁(主动请求)

1. SELECT ... FOR SHARE(最常用)

-- 事务A
BEGIN;
SELECT * FROM products WHERE id = 10 FOR SHARE;
-- 对 id=10 这条记录加 S 锁
-- 自己可以读,别人可以读,但谁都不能改

适用场景:

  • 需要确保读取的数据在事务期间不被修改

  • 允许其他事务并发读取

  • 典型应用:报表生成、数据核对

2. LOCK IN SHARE MODE(MySQL 8.0 之前的写法)

-- MySQL 5.7 及更早版本
SELECT * FROM products WHERE id = 10 LOCK IN SHARE MODE;
-- 8.0 中仍兼容,但推荐用 FOR SHARE

二、隐式加锁(自动加)

1. 唯一键冲突检测(我们深入讨论过的场景)

-- 表中已存在 id=25
事务A: INSERT INTO products (id, name) VALUES (25, '新品');
-- 发现 id=25 已存在,对这条记录加 S 锁
-- 目的:防止在检测冲突到返回错误的窗口期内,这条记录被别人删掉

为什么加 S 锁:确保冲突检测的准确性。如果不加锁,可能出现:

  1. 事务A检测到 id=25 存在(准备报错)

  2. 事务B删除 id=25

  3. 事务A返回"Duplicate entry",但记录已经不在了 → 数据不一致

2. 外键约束检查

-- 父表 parent (id PRIMARY KEY)
-- 子表 child (pid FOREIGN KEY REFERENCES parent(id))

事务A: INSERT INTO child (id, pid) VALUES (1, 100);
-- 需要检查父表是否存在 id=100
-- 对父表 id=100 的记录加 S 锁

为什么加 S 锁:防止在检查过程中,父表的这条记录被删除或修改,导致外键约束失效。

3. INSERT ... SELECT 中的源表

-- RR 隔离级别下
INSERT INTO t1 SELECT * FROM t2 WHERE price > 100;
-- 对 t2 中扫描到的记录加 S 锁(实际上是 Next-Key Lock,包含 S 锁)

为什么加 S 锁:确保源表数据在复制过程中不被修改,保证数据一致性。

4. UPDATE 中的读取操作

UPDATE products SET price = price * 1.1 
WHERE category_id = 5 AND price > 100;
-- 在找到要更新的记录时,需要先读取判断条件
-- 这些读取操作会加 S 锁(然后立即升级为 X 锁)

注意:这不是独立的 S 锁阶段,而是 UPDATE 内部流程的一部分。

一、为什么不直接加X锁?

UPDATE products SET price = price * 1.1 
WHERE category_id = 5 AND price > 100;

这个UPDATE的执行过程是:

  1. 第一阶段:查找(需要读数据)

    • 通过索引找到所有满足 category_id = 5 AND price > 100 的记录

    • 这个过程是"读"操作,必须先知道哪些记录符合条件

  2. 第二阶段:修改(需要写数据)

    • 对找到的记录进行修改

    • 这个过程才是"写"操作

如果一开始就加X锁:

  • 事务A要扫描全表找符合条件的记录

  • 扫描到某行时,直接加X锁

  • 其他事务连读都不能读

  • 并发性能急剧下降


三、特殊隔离级别下的隐式加锁

1. SERIALIZABLE 隔离级别

-- 设置隔离级别为 SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM products WHERE price = 30;
-- 普通 SELECT 也会加 S 锁(Next-Key Lock)

为什么:SERIALIZABLE 要求所有读都是加锁读,防止任何并发修改。


四、不同场景的 S 锁持续时间

 
场景S 锁持有时间说明
SELECT ... FOR SHARE 直到事务结束(COMMIT/ROLLBACK) 显式加锁,必须手动结束事务
唯一键冲突的 INSERT 直到事务结束 即使报错,S 锁还留着
外键约束检查 通常很快释放 检查完成后可释放(取决于隔离级别)
INSERT ... SELECT 源表 直到 SELECT 完成 RR 下持续到语句结束
SERIALIZABLE 的 SELECT 直到事务结束 和 FOR SHARE 类似

五、总结:什么时候用 S 锁?

 
场景分类具体场景S 锁的作用
显式保护 FOR SHARE 确保读取的数据不被修改
冲突检测 唯一键冲突 防止冲突记录被删除
约束维护 外键检查 确保父记录存在且不变
数据复制 INSERT...SELECT 保证源数据一致性
隔离级别 SERIALIZABLE 强制所有读加锁

一句话总结

S 锁用在"我要读,但不改,同时确保别人也不能改"的所有场景——无论是主动请求(FOR SHARE),还是被动保护(唯一键冲突、外键检查)。

posted @ 2026-02-24 13:07  looyee  阅读(0)  评论(0)    收藏  举报