select ... for update 必须在显式事务中使用,否则因autocommit=1导致锁瞬时释放;其行锁生效依赖where条件命中索引,否则升级为表锁;且update必须携带原子条件(如and stock > 0)并校验影响行数,才能防止超卖。

SELECT ... FOR UPDATE 必须在事务中执行
单独写一句 SELECT stock FROM goods WHERE id = 123 FOR UPDATE 没用,它只会立刻报错或被自动提交——MySQL 默认 autocommit=1,语句一执行完就释放锁。真正起作用的前提是:显式开启事务,且整个“查+判+改”都在同一个事务内完成。
常见错误现象:SELECT ... FOR UPDATE 返回了 stock=1,但后续 UPDATE 却扣成了 -1;或者并发压测时仍有超卖。根本原因往往是事务没包住、中间加了 COMMIT、或用了 MyBatis 的 @Select + @Update 分离调用(默认各自为独立事务)。
- 必须用
BEGIN或START TRANSACTION显式开启 - 应用层不能在
SELECT FOR UPDATE后立即COMMIT,否则锁立刻释放 - MyBatis 中避免把查询和更新拆成两个带
@Transactional的方法,容易造成事务嵌套或隔离失效 - Spring 的
@Transactional注解要作用在整个服务方法上,且传播行为保持默认(REQUIRED)
FOR UPDATE 锁不住行?先看 WHERE 条件有没有走索引
如果 SELECT ... FOR UPDATE 的 WHERE 条件没命中索引,InnoDB 会退化为锁表,甚至锁全表——这不是你想要的“行锁”,而是性能雪崩的起点。
典型踩坑场景:用 WHERE status = 1 加锁,但 status 列没建索引;或用 WHERE name LIKE '%xxx%' 这类无法使用索引的模糊查询。此时 SHOW ENGINE INNODB STATUS 会显示大量 waiting for table metadata lock 或锁升级警告。
- 检查执行计划:
EXPLAIN SELECT ... FOR UPDATE,确认type是const或ref,且key显示用了主键或唯一索引 - 商品 ID 必须是主键或带唯一索引,否则
WHERE id = ?也可能不走索引(比如字段类型不匹配:传 String 给 INT 列) - 避免在
FOR UPDATE查询里 JOIN 多张表,尤其关联字段无索引时,极易锁扩大
UPDATE 语句仍需带库存判断条件
只靠 SELECT ... FOR UPDATE 锁住一行,并不等于安全。如果两个事务都成功锁住了同一行,再各自在应用层判断 stock > 0 → 都为真 → 都执行 UPDATE stock = stock - 1,照样超卖。锁只是串行化了“读”,没串行化“读之后的业务逻辑”。
所以,哪怕用了悲观锁,UPDATE 也必须带上原子条件:
UPDATE goods SET stock = stock - 1 WHERE id = 123 AND stock > 0;
然后严格依赖返回的受影响行数(ROW_COUNT() 或 JDBC 的 executeUpdate() 返回值)判断是否真正扣减成功。返回 0 表示库存已耗尽,不是失败,而是预期结果。
- 不能只靠
SELECT FOR UPDATE后的if (stock > 0)就认为能扣,这是最常被忽略的一步 - 即使锁住了,也要防“锁住时 stock=1,但另一个刚提交的事务已把它扣到 0”——这种窗口极小,但高并发下必然发生
- MyBatis XML 中,
<update></update>标签里的 SQL 必须包含AND stock > 0,不能只写SET stock = stock - 1
高并发下悲观锁的实际瓶颈在哪
悲观锁本质是串行化,QPS 上限由数据库单行处理能力决定。实测中,当并发请求超过 300–500,MySQL CPU 就会持续 80%+,大量事务卡在 waiting for lock 状态,响应时间从几十毫秒飙到几秒。
这不是代码写错了,而是架构层面的硬限制:所有请求争抢同一行记录的 X 锁,而 InnoDB 的聚簇索引让这行物理位置固定,无法分散压力。
- 不要指望加更多数据库连接就能解决——连接池打满后,新请求直接拒绝或超时
- 死锁风险真实存在:比如事务 A 先锁商品 1 再锁商品 2,事务 B 反过来,就会触发 InnoDB 自动回滚
- 真正适合悲观锁的场景是低并发(
- 如果业务已出现明显延迟,说明你该考虑分片(shard)、Redis 预减、或最终一致性方案了,而不是继续优化 SQL











