SELECT ... FOR UPDATE 是悲观锁,加行级排他锁(X锁),需在事务中显式关闭自动提交;UPDATE ... WHERE version = ? 是乐观锁,靠版本号校验一致性,失败返回0行,业务需捕获重试或报错。

SELECT ... FOR UPDATE 是悲观锁的典型用法,UPDATE ... WHERE version = ? 是乐观锁的典型用法。选哪种,不看理论,看业务是否允许“重试失败”和“读写比例”。
悲观锁:什么时候必须用 FOR UPDATE
当业务逻辑中「写操作不可逆」且「冲突概率高」时,悲观锁是更稳妥的选择。
- 典型场景:电商秒杀扣库存、银行账户转账、订单状态机推进(如从「待支付」→「已支付」)
- 必须在事务内使用,且需显式关闭自动提交(
SET autocommit = 0),否则锁立刻释放 -
FOR UPDATE加的是行级排他锁(X锁),会阻塞其他事务对同一行的FOR UPDATE和LOCK IN SHARE MODE,但不影响快照读(如普通SELECT) - 容易踩的坑:
WHERE条件没走索引 → 升级为表锁;多个FOR UPDATE顺序不一致 → 死锁风险上升
乐观锁:为什么 version 字段比 update_time 更可靠
版本号机制是业务层可控、数据库无歧义的校验方式,时间戳在毫秒级并发下可能重复,且受系统时钟漂移影响。
-
version必须是整型、自增、无业务含义;更新语句必须同时做两件事:修改业务字段 +version = version + 1 - 执行
UPDATE后必须检查affected rows:为 0 表示冲突,此时应抛异常或触发重试逻辑(注意重试次数上限,避免雪崩) - 不适合嵌套更新:比如先查出
stock=100, version=5,再执行UPDATE SET stock = stock - 1 WHERE id=1 AND version=5,但如果中间有其他事务把 stock 改成 99,你这次减 1 就变成 98,逻辑正确但 version 校验通过——这没问题,因为 version 只保“是否被改过”,不保“改成了什么”
实际开发中怎么判断该用哪个
别纠结“理论上哪个更好”,直接看三个硬指标:
- 写操作失败后,前端/用户能否接受“重试提示”?能 → 乐观锁;不能(比如支付已扣款但订单未生成)→ 悲观锁
- 单条记录每秒被并发更新次数是否 > 10?是 → 悲观锁更稳;否(如商品详情页编辑,一天才几次)→ 乐观锁省资源
- 有没有跨服务协作?比如库存由订单服务扣、风控服务也读写同一张表 → 悲观锁依赖数据库原生锁,外部系统也能感知;乐观锁只靠业务字段,外部系统若不遵守 version 规则就完全失效
混合用法:不是非此即彼
真实业务里,经常是“悲观读 + 乐观写”组合。
- 例如下单流程:先用
SELECT stock, version FROM inventory WHERE product_id = 1 FOR UPDATE锁住库存并读出当前值和版本;然后业务校验库存充足,再执行UPDATE inventory SET stock = stock - 1, version = version + 1 WHERE product_id = 1 AND version = ? - 这样既避免了纯乐观锁在高并发下大量 update 失败,又规避了纯悲观锁长时间持锁导致的阻塞
- 关键点:
FOR UPDATE要放在真正需要强一致性的那一刻,而不是一进来就锁;锁的范围越小、时间越短,系统吞吐越高
版本号字段一旦加了,就不能随便删;FOR UPDATE 的锁等待超时默认是 50 秒(innodb_lock_wait_timeout),线上务必根据业务响应要求调整。


















