全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-15 8 分钟 ✍️ juanwangdev

共享锁与排他锁

共享锁(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),锁定读加锁
  • 锁在事务提交/回滚时释放
  • 设置合理的锁等待超时避免长时间阻塞

📝 发现内容有误?点击此处直接编辑

← 上一篇 聚簇索引与二级索引
下一篇 → 行锁与表锁
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库