mysql中使用SELECT FOR UPDATE进行事务锁定

11次阅读

select for UPDATE 只在显式事务中生效,需禁用自动提交并用 BEGIN 开启事务;锁范围取决于 WHERE 条件是否命中索引,无索引将导致全表锁;避免死锁须统一加锁顺序、禁用混合锁类型、减少事务内耗时操作;UPDATE 自带 X 锁,无需先 SELECT FOR UPDATE。

mysql中使用SELECT FOR UPDATE进行事务锁定

SELECT FOR UPDATE 只在事务中生效

单独执行 SELECT FOR UPDATE 不会加锁,必须显式开启事务(BEGINSTART TRANSACTION),且不能在自动提交模式下运行。否则语句看似执行成功,实则退化为普通查询,锁根本不会产生。

  • 检查当前会话是否自动提交:SELECT @@autocommit;,返回 1 表示开启,需先执行 SET autocommit = 0;
  • 推荐写法:始终用 BEGIN; SELECT ... FOR UPDATE; [UPDATE/delete]; COMMIT; 显式控制边界
  • 如果用 ORM(如 SQLAlchemy、mybatis),确认其事务配置未覆盖底层隔离级别或提前 commit

锁定范围取决于 WHERE 条件和索引

SELECT FOR UPDATE 加的是行级锁,但“哪些行被锁”完全由查询条件是否命中索引决定。没走索引时会升级为表锁(InnoDB 中实际是所有索引记录的 next-key 锁,效果接近全表扫描锁)。

  • 主键或唯一索引查询(如 WHERE id = 100)→ 精确锁定单行
  • 普通索引 + 范围查询(如 WHERE status = 'pending')→ 锁定所有匹配索引项及其间隙(next-key lock)
  • 无索引字段查询(如 WHERE remark LIKE '%test%')→ 全表扫描,锁住所有聚簇索引记录,高并发下极易阻塞
  • 验证是否走索引:EXPLaiN SELECT ... FOR UPDATE,重点看 type(应为 const/ref/range)和 key 字段

避免死锁的关键操作习惯

死锁不是异常,而是 InnoDB 的正常检测行为。多数源于多个事务以不同顺序访问相同资源。只要加锁顺序一致,就能大幅降低概率。

  • 所有业务逻辑中,对多行加锁时,强制按主键升序排序后再查:SELECT ... FOR UPDATE ORDER BY id ASC
  • 不要在事务内混合使用 SELECT FOR UPDATESELECT LOCK IN SHARE MODE
  • 避免在事务中执行耗时操作(如 http 请求、文件读写),锁持有时间越长,冲突窗口越大
  • 监控死锁日志:SHOW ENGINE INNODB STATUSG,重点关注 LATEST DETECTED DEADLOCK 段落

UPDATE 语句本身也隐式加锁,别重复 FOR UPDATE

如果目标只是“查出来再更新”,直接用 UPDATE ... WHERE 更高效。它内部已对匹配行加 X 锁,无需先 SELECT FOR UPDATEUPDATE —— 多一次查询不仅浪费 I/O,还可能因中间状态变化导致业务逻辑错乱。

UPDATE orders SET status = 'shipped' WHERE id = 123 AND status = 'paid';

这种写法原子性强,还能防止并发修改覆盖(通过 status = 'paid' 做乐观校验)。只有需要基于查询结果做复杂判断(比如库存预占需先读取当前余量再决定是否扣减)时,才真正需要 SELECT FOR UPDATE

最容易被忽略的是:锁只在事务提交或回滚后释放,哪怕你只执行了 SELECT FOR UPDATE 并没后续操作,连接不关闭、事务不结束,锁就一直挂着——这会悄无声息拖垮整个库的并发能力。

text=ZqhQzanResources