mysql中事务处理中的乐观锁与悲观锁

来源:这里教程网 时间:2026-02-28 20:50:35 作者:

MySQL 事务里怎么选乐观锁还是悲观锁

没有“该用哪个”的标准答案,只看业务是否允许重试、数据冲突频率高低、以及能否接受锁等待。乐观锁适合读多写少、冲突概率低的场景(比如文章点赞数更新);悲观锁适合写密集、必须强一致的场景(比如库存扣减)。选错会直接导致超时、死锁或大量重试失败。

SELECT ... FOR UPDATE
实现悲观锁要注意什么

它会在匹配行上加排他锁(X 锁),但具体锁住哪些行,取决于 WHERE 条件是否命中索引:

WHERE 条件走主键或唯一索引 → 只锁命中行 WHERE 条件走普通索引或全表扫描 → 可能锁住间隙(Gap Lock)甚至整个范围,引发不必要的锁等待 没加
ORDER BY
LIMIT
时,InnoDB 可能锁住所有扫描过的记录,哪怕最终只更新一行
事务不提交,锁就一直持有——忘记
COMMIT
ROLLBACK
是最常见的死锁/阻塞源头
START TRANSACTION;
SELECT stock FROM products WHERE id = 123 FOR UPDATE;
-- 此时其他事务对 id=123 的 UPDATE/SELECT FOR UPDATE 会被阻塞
UPDATE products SET stock = stock - 1 WHERE id = 123;
COMMIT;

乐观锁靠
version
字段实现时的典型陷阱

本质是“先查后判再更新”,靠数据库原子性保证并发安全,但容易在应用层漏掉校验逻辑:

UPDATE 语句必须包含
WHERE version = ?
,且 WHERE 中不能只依赖业务字段(如
WHERE status = 'pending'
),否则可能覆盖他人修改
执行
UPDATE
后必须检查
ROW_COUNT()
(MySQL)或影响行数是否为 1,为 0 就说明版本已变,需重试或报错
如果业务逻辑中有多次 UPDATE(比如先改状态再改时间),每次都要带 version 判断,否则中间状态可能被覆盖 不要把
version
设为
TIMESTAMP
类型——毫秒级并发下可能重复,推荐用
INT
BIGINT
自增
UPDATE orders 
SET status = 'shipped', version = version + 1 
WHERE id = 456 AND version = 12;

什么时候两种锁都解决不了问题

当业务需要跨多张表、多个服务、或涉及外部系统(比如调用支付接口后再扣库存),单靠 MySQL 的锁机制无法保证整体一致性。这时候乐观锁和悲观锁都只是局部手段:

分布式事务(如 Seata、XA)开销大,且 MySQL XA 对高并发不友好 真正可靠的做法是引入补偿事务(Saga)、幂等设计、或用消息队列解耦+最终一致性 最容易被忽略的一点:锁只管“数据库写”,不管“业务含义”。比如两个事务都成功扣了库存,但其中一个后续因地址校验失败要回滚——这时库存已经错了,锁本身不负责业务回滚逻辑

相关推荐