共享锁与排他锁
共享锁(S Lock)和排他锁(X Lock)是两种最基本的锁类型。
共享锁(Shared Lock)
定义
共享锁也称读锁,允许多个事务同时读取同一资源,但阻止其他事务修改。
加锁方式
SQL
-- 方式1:LOCK IN SHARE MODE
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;
-- 方式2:FOR SHARE(MySQL 8.0+)
SELECT * FROM orders WHERE id = 1 FOR SHARE;
使用场景
SQL
-- 读取数据并确保读取期间不被修改
-- 例如:检查库存后下单
-- 事务1:检查库存
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- stock = 10
-- 事务2:可同时检查(获取S锁)
SELECT stock FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- stock = 10
-- 事务3:无法修改(等待S锁释放)
UPDATE products SET stock = 5 WHERE id = 1;
-- 阻塞等待...
-- 事务1提交
COMMIT;
-- 事务3继续执行
共享锁特点
- 允许多个事务并发获取S锁
- 持有S锁期间禁止其他事务获取X锁
- 适合多读场景,保证读取一致性
排他锁(Exclusive Lock)
定义
排他锁也称写锁,获取后独占资源,阻止其他事务读取或修改。
加锁方式
SQL
-- 方式1:FOR UPDATE
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 方式2:DML自动加锁
UPDATE orders SET amount = 100 WHERE id = 1;
DELETE FROM orders WHERE id = 1;
INSERT INTO orders VALUES (...);
使用场景
SQL
-- 修改数据前锁定,防止并发修改
-- 例如:扣减库存
-- 事务1:扣减库存
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- stock = 10, 持有X锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 事务2:无法同时扣减(等待)
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- 阻塞等待事务1提交
-- 事务3:无法读取(等待)
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- 阻塞等待事务1提交
排他锁特点
- 只允许一个事务持有X锁
- 持有X锁期间禁止其他事务获取S锁或X锁
- DML操作(INSERT/UPDATE/DELETE)自动加X锁
- 适合写操作,保证数据一致性
锁兼容性
兼容矩阵
SQL
┌─────────────────────────────────────┐
│ 锁兼容性矩阵 │
├──────────┬───────────┬──────────────┤
│ 当前请求 │ 持有S锁 │ 持有X锁 │
├──────────┼───────────┼──────────────┤
│ 请求S锁 │ ✅ 允许 │ ❌ 阻塞等待 │
├──────────┼───────────┼──────────────┤
│ 请求X锁 │ ❌ 阻塞等待│ ❌ 阻塞等待 │
└──────────┴───────────┴──────────────┘
✅ 允许:立即获取锁
❌ 阻塞:等待持有锁释放
并发示例
SQL
-- 场景:多事务并发访问同一行
-- 事务A(先执行)
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE; -- S锁
-- 事务B(并发)
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE; -- ✅ S锁兼容
-- 事务C(并发)
SELECT * FROM orders WHERE id = 1 FOR UPDATE; -- ❌ 阻塞等待
-- 事务D(并发)
UPDATE orders SET amount = 100 WHERE id = 1; -- ❌ 阻塞等待
锁的释放时机
事务提交释放
SQL
-- 锁在事务提交或回滚时释放
START TRANSACTION;
SELECT * FROM orders WHERE id = 1 FOR UPDATE; -- 持有X锁
-- 执行其他操作...
COMMIT; -- 释放X锁
-- 或
ROLLBACK; -- 释放X锁
不在事务中执行锁定读,SQL执行完立即释放锁。
锁与MVCC的关系
普通SELECT不加锁
SQL
-- 普通SELECT使用MVCC快照读,不加锁
SELECT * FROM orders WHERE id = 1;
-- 即使其他事务持有X锁,也能读取
-- 读的是快照版本,不是当前版本
锁定读加锁
SQL
-- 锁定读获取实时数据,需要加锁
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 若其他事务持有锁,需等待
-- 获取锁后读取当前最新数据
行锁与表锁对比
行级S/X锁
SQL
-- 行级锁,只锁匹配的行
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 只锁id=1这一行
-- 其他行可正常访问
SELECT * FROM orders WHERE id = 2 FOR UPDATE; -- ✅ 不冲突
表级S/X锁
SQL
-- 表级锁,锁整张表
LOCK TABLE orders READ; -- 表级S锁
LOCK TABLE orders WRITE; -- 表级X锁
-- 整表阻塞
-- 表级X锁禁止其他事务访问任何行
锁等待超时
默认超时设置
SQL
-- 查看锁等待超时时间
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 默认50秒
-- 超时后抛出错误
ERROR 1205 (HY000): Lock wait timeout exceeded
设置超时时间
SQL
-- 会话级设置
SET innodb_lock_wait_timeout = 10; -- 10秒超时
-- 配置文件
[mysqld]
innodb_lock_wait_timeout = 30
实际应用场景
场景1:库存扣减
SQL
-- 排他锁保证库存一致性
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
场景2:数据校验后操作
SQL
-- 共享锁读取,确保校验期间数据不变
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 LOCK IN SHARE MODE;
-- 校验余额充足
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
场景3:悲观锁并发控制
text
-- 使用排他锁实现悲观锁
-- 确保同一时刻只有一个线程处理
START TRANSACTION;
SELECT * FROM tasks WHERE id = 1 AND status = 'pending' FOR UPDATE;
UPDATE tasks SET status = 'processing' WHERE id = 1;
COMMIT;
要点总结
- 共享锁(S)允许多事务并发读,阻止写
- 排他锁(X)独占资源,阻止其他读写
- S与S兼容,S与X/X与X不兼容
- DML操作自动加X锁
- 普通SELECT不加锁(MVCC),锁定读加锁
- 锁在事务提交/回滚时释放
- 设置合理的锁等待超时避免长时间阻塞
📝 发现内容有误?点击此处直接编辑