在mysql的并发控制中,乐观锁和悲观锁是两种截然不同的思路。它们并不是mysql内置的某一种特定锁类型,而是基于mysql提供的底层能力,如事务、行锁、版本字段等,在应用层或sql层面构建出的并发控制策略。明确二者的本质差异,可以帮助我们针对不同的业务场景选择合适的方案。

什么是悲观锁
悲观锁的核心假设是:数据在并发访问时极有可能被其他事务修改,因此必须在操作开始前就先锁定资源,防止别人动它。在mysql里,悲观锁通常依赖innodb存储引擎的行级锁来实现,最典型的用法就是在事务中通过select ... for update语句给目标行加排他锁。
当一个事务执行了select ... for update,其他事务如果也想对这些行加锁或者修改它们,就会被阻塞,直到前一个事务提交或回滚释放锁。这种方式能有效避免丢失更新问题,但代价是并发性能下降,且在锁等待超时或死锁时需要进行额外处理。下面的示例展示了在扣减库存场景中如何使用悲观锁:
start transaction; -- 对商品id为1001的行加排他锁 select stock from product where id = 1001 for update; -- 假设查到stock为10,进行扣减 update product set stock = stock - 1 where id = 1001; commit;
上面的代码在事务内先锁定行,再读取并修改,保证同一时间只有一个事务能操作这条记录。如果另一个事务也来执行同样的select ... for update,它会被阻塞直到当前事务结束。
悲观锁的优点是逻辑直观、数据一致性最强,适合写冲突频繁的核心业务,比如账户余额变更。缺点也很明显:长事务会导致锁持有时间过久,拖垮系统吞吐量;另外使用不当容易引发死锁,需要配合合理的索引和事务设计。
什么是乐观锁
乐观锁的假设正好相反:认为大部分情况下不会发生并发冲突,所以不在读取时加锁,而是在提交更新时检查数据在此期间有没有被别人改过。在mysql中,最常见的是通过增加版本号字段或时间戳字段来实现,也可以在更新时用业务条件充当校验。
典型的实现是在表里加一个version字段,每次更新时要求version等于之前读到的值,同时把version加一。如果更新返回影响行数为零,就说明版本不匹配,被人抢先修改了,此时应用层可以重试或报错。示例如下:
-- 先查询,假设读到id=1001, stock=10, version=3 select stock, version from product where id = 1001; -- 尝试扣减,仅当版本未变时才成功 update product set stock = stock - 1, version = version + 1 where id = 1001 and version = 3;
如果另外一个事务已经把version改成了4,那么这句update就匹配不到行,影响行数为零,应用就能感知到冲突。相比悲观锁,它完全不阻塞读,也不会产生数据库层面的行锁等待。
除了版本号,还可以直接用库存数作为条件,例如where id=1001 and stock>=1,但这只防超卖,不能防止基于旧值的复杂计算被覆盖。乐观锁适合读多写少、冲突概率低的场景,如商品详情浏览量统计、配置项修改。其缺点是如果冲突频繁,重试会带来额外开销,且编写重试逻辑时要注意幂等性。
二者核心差异对比
从实现机制上看,悲观锁依赖数据库锁机制,在读取阶段就排他;乐观锁依赖应用层版本校验,在写入阶段才冲突检测。这导致它们在性能特征上完全不同。我们可以通过一张表来直观比较:
| 对比维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 加锁时机 | 读取时 | 更新时校验 |
| 是否阻塞其他事务 | 是 | 否 |
| 适用冲突频率 | 高 | 低 |
| 实现复杂度 | 低,靠sql | 中,需版本字段与重试 |
| 典型语句 | select for update | update where version |
从表中可以看出,选哪种并不是绝对的好坏问题。如果业务是抢购秒杀且库存极少,用悲观锁更稳妥;如果是后台配置编辑,一天改不了几次,乐观锁明显更轻量。
还要注意,乐观锁在mysql中并不是真的无锁,它只是把并发控制从数据库锁转移到了where条件与版本比对上。在高并发写同一个热点行时,大量update失败重试也会给数据库造成压力,此时可能需要结合队列或分桶扣减等架构手段。
在项目中如何选择
做技术选型时,先评估写冲突概率和一致性要求。若每次操作都必须基于最新且独占的数据,比如转账、充值,优先用悲观锁,并尽量缩小事务范围,避免锁住无关行。若只是偶尔修改且可以接受重试,比如用户修改昵称、文章点赞数,乐观锁更合适。
实际开发中也可以混合使用。例如先在应用层用redis做库存预扣(乐观计数),真正落库时再用mysql悲观锁兜底,既抗住流量又保证最终准确。无论哪种方式,都要通过压测验证,在日志中记录锁等待或更新失败次数,方便后续调优。
理解mysql的乐观锁与悲观锁,重点不在于背诵定义,而在于看清它们背后对并发冲突的假设,以及这种假设如何转化为具体的sql写法和系统表现。把业务特征与锁特征对齐,才能写出既安全又高效的数据库代码。
mysqloptimistic_lockpessimistic_lock修改时间:2026-08-09 21:51:35