首页/数据/数据库速查/SELECT ... FOR UPDATE / FOR SHARE 加锁读
SQL 语法52 条

SELECT ... FOR UPDATE / FOR SHARE 加锁读

显式加锁保证读-改-写的原子性:FOR UPDATE 排他锁、FOR SHARE 共享锁;加锁读会阻塞其他写。

数据库速查数据更新于 2026-10-13

加锁读是什么

加锁读用来显式控制并发:SELECT ... FOR UPDATE 对扫描到的行加排他锁,FOR SHARE 加共享锁。它们只在事务中才有意义,作用是把「读出来判断,再写回去」这段逻辑保护起来,避免两个并发请求读到同一份数据后互相覆盖。代价是加锁读会阻塞其它写,条件没走索引时影响范围会明显放大。

基本信息

用途悲观锁读取,保护读改写逻辑
语句SELECT … FOR UPDATE / FOR SHARE
前提必须在事务内,条件列最好有索引
8.0 增强FOR UPDATE SKIP LOCKED、NOWAIT
等待超时innodb_lock_wait_timeout,默认 50 秒

典型使用场景

  • 库存扣减、余额变更这类读改写操作
  • 订单状态流转时的并发保护
  • 任务队列取任务,用 SKIP LOCKED 避免互相等待
  • 对账、结账时锁定关键行
  • 排查锁等待与死锁时确认加锁范围

库存扣减范式

库存扣减范式
START TRANSACTION;
SELECT qty FROM stock WHERE sku='SKU-1' FOR UPDATE;   -- 排他锁
UPDATE stock SET qty = qty - 1 WHERE sku='SKU-1' AND qty > 0;
COMMIT;

-- 8.0:跳过已锁行,适合任务队列
SELECT id FROM jobs WHERE status='ready' ORDER BY id LIMIT 1
FOR UPDATE SKIP LOCKED;

-- 不想等待,锁不到就立刻报错
SELECT id FROM jobs WHERE status='ready' LIMIT 1 FOR UPDATE NOWAIT;

用法与影响

写法作用注意事项
FOR UPDATE排他锁阻塞其它事务读写同一批行
FOR SHARE共享锁多个读可以共存,写要等待
SKIP LOCKED跳过已被锁定的行队列场景避免互相等待
NOWAIT锁不到立即失败适合对响应时间敏感的逻辑
作用范围取决于扫描的行数无索引条件会锁住大量行
条件不走索引时 FOR UPDATE 可能锁住大量行甚至整表,把其它业务拖住;等待时长由 innodb_lock_wait_timeout 控制,默认 50 秒,排查时要一起看。
加锁读必须在事务里才有意义:autocommit=1 时单条语句执行完锁就释放,「读出来再写回去」的竞态依然存在,等于没加锁。用框架封装时尤其要注意,别让事务被提前提交,或者被拆成了两段。
死锁与加锁顺序直接相关:批量加锁的场景要固定访问顺序,例如按主键升序处理,否则两个事务交叉加锁必然互相等待。真的出现死锁后,用 SHOW ENGINE INNODB STATUS 查看最近一次死锁的详细信息。

易错点

  • 在 autocommit 下使用 FOR UPDATE:锁在语句结束就释放,等于没有加锁
  • 无索引条件做加锁读:锁范围被放大到扫描涉及的所有行
  • 批量操作加锁顺序不一致:并发时互相等待,直接产生死锁
  • 用加锁读替代合理的乐观锁:高并发下锁等待时间迅速上升
  • 加锁读之后在事务里做耗时业务逻辑:锁被长期持有,其它事务全部排队

数据来源与参考

  • MySQL 8.0 Reference Manual: Locking Reads

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌