一、开篇直击痛点:为什么需要 select for update 锁?
做后端开发的同学,大概率遇到过这样的场景:
- 电商秒杀:100 件商品,1000 人抢购,如何避免超卖?
- 余额支付:用户账户余额 100 元,同时发起两笔 80 元支付,如何防止余额为负?
- 库存扣减:多线程同时操作同一商品库存,如何保证数据一致性?
这些并发场景的核心矛盾,是“读 - 改 - 写” 操作的原子性。普通 select 语句是 “快照读”,无法阻止其他事务修改数据,而 select for update 锁的核心作用,就是通过“当前读” 锁定目标数据,强制事务串行执行,避免并发冲突。
但实际开发中,很多人用不对这个锁:明明加了锁还出现超卖,甚至导致死锁;以为是行锁,结果变成全表锁拖垮性能…… 今天就彻底扒开它的底层逻辑,从原理到实战一次讲透!
二、基础认知:select for update 锁到底是什么?
1. 定义与核心特性
select for update 是悲观锁的一种实现,仅在事务(BEGIN/COMMIT)中生效:
- 执行该语句时,数据库会锁定查询结果集对应的行 / 表;
- 其他事务需等待当前事务提交 / 回滚后,才能修改锁定的数据;
- 支持 WHERE 条件过滤,锁的粒度由查询条件和索引决定(行锁 / 表锁)。
2. 与普通 select 的本质区别
特性 | 普通 select | select for update(事务内) |
读取类型 | 快照读(非阻塞) | 当前读(阻塞其他写操作) |
锁机制 | 无锁(MVCC 版本控制) | 悲观锁(行锁 / 表锁) |
适用场景 | 单纯查询,无后续修改操作 | 读 - 改 - 写原子操作(如扣减) |
并发冲突风险 | 高(可能出现脏读 / 幻读) | 低(强制串行执行) |
三、关键知识点:行锁 vs 表锁,select for update 怎么选?
这是最容易踩坑的点!select for update 到底加行锁还是表锁,不取决于语句本身,而取决于查询条件和索引。