select ... for update 能防超卖但需满足事务、索引、及时提交等条件,否则引发锁表、死锁或连接耗尽;正确用法包括显式事务、命中唯一索引、原子更新、移出非db操作。

直接结论:用 SELECT ... FOR UPDATE 能防止超卖,但必须在事务中、走索引、及时提交,否则不是防超卖,是制造数据库瓶颈。
为什么 SELECT ... FOR UPDATE 在高并发下容易失效
常见错误是把 SELECT ... FOR UPDATE 当成“万能锁”,但实际它只在满足特定条件时才真正加行锁:
- 没开事务(
BEGIN或START TRANSACTION)——语句执行完立刻释放锁,形同虚设 - 查询条件没走主键或唯一索引(比如用
WHERE name = 'iPhone')——InnoDB 升级为表锁,所有商品操作互相阻塞 - 事务长时间不提交(比如中间调用外部 HTTP、睡眠、复杂计算)——锁一直占着,连接堆积,MySQL 报
Too many connections - 多个
FOR UPDATE语句顺序不一致(如事务 A 先锁 id=1 再锁 id=2,事务 B 反过来)——极易触发死锁,MySQL 回滚其中一方
SELECT ... FOR UPDATE 的正确写法和关键参数
不是写对语法就行,得控制住锁的粒度、生命周期和执行路径:
- 必须显式开启事务:
BEGIN;不能依赖框架自动事务(比如 Spring 的@Transactional若传播行为是NOT_SUPPORTED,照样不生效) - 查询必须命中索引:
SELECT stock FROM products WHERE id = 123 FOR UPDATE✅;SELECT stock FROM products WHERE sku = 'ABC' FOR UPDATE❌(除非sku是唯一索引) - UPDATE 必须复用同一行数据:不要先查出
stock值再拼 SQL 更新,而要用原子表达式:UPDATE products SET stock = stock - 1 WHERE id = 123 AND stock >= 1 - 避免在事务内做非 DB 操作:日志、RPC、文件读写等一律移出事务块,否则锁持有时间不可控
和乐观锁比,FOR UPDATE 什么时候真该用
别被“强一致性”带偏,它适合的是小范围、低频、高确定性的写场景:
- 库存极低(比如只剩 1~5 件),且业务能接受用户等待(如后台管理扣减)
- 订单生成前的最终校验环节(已通过 Redis 预减、MQ 异步削峰后),仅剩最后几笔要落库
- 非秒杀类业务,QPS 稳定在 100 以下,DB 连接池充足(比如
max_connections > 300) - 你明确知道不会出现跨表、跨索引、多条件组合查询的
FOR UPDATE(否则锁范围失控)
最容易被忽略的三个细节
这些点不写进代码注释,90% 的人上线后才发现问题:
-
FOR UPDATE对NULL值行无效:如果id = 123这条记录还不存在,SELECT ... FOR UPDATE不会加锁,后续INSERT可能冲突,得配合INSERT ... ON DUPLICATE KEY UPDATE - MySQL 8.0+ 默认隔离级别是
REPEATABLE READ,但若应用连的是READ COMMITTED,FOR UPDATE行为会变化(比如不锁间隙),务必确认SELECT @@transaction_isolation - PyMySQL/MySQL Connector 等驱动默认自动提交(
autocommit=True),必须显式设为False,否则BEGIN后的FOR UPDATE立刻提交释放锁











