直接回答:悲观锁假设冲突必然发生,操作数据前先加锁,典型实现是数据库的行锁(SELECT ... FOR UPDATE);乐观锁假设冲突很少,操作时不加锁,提交时通过版本号或 CAS 校验数据是否被他人改过,典型实现是给表加 version 字段,由应用层完成。
展开解析:悲观锁由数据库锁机制保证互斥,持锁期间其他事务阻塞,逻辑简单、成功率高,但并发度低且有死锁风险,需要依赖 innodb_lock_wait_timeout(默认 50 秒)和死锁检测兜底,适合写多、冲突激烈的场景,如库存扣减、账户余额变更。乐观锁不占用数据库锁,冲突时以更新行数为 0 的方式感知并由应用重试,吞吐高,适合读多写少的场景,如配置更新、点赞计数。纯 CAS 存在 ABA 问题(值被改走又改回),版本号单调递增可解决。注意高冲突下乐观锁的重试风暴反而比排队更差——每次重试都是一次完整事务开销,要按实际冲突率选型,冲突率高时悲观锁的确定性更好。
-- 乐观锁:版本号 CAS,affected rows = 0 表示冲突需重试
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 100 AND version = 3 AND stock > 0;
-- 悲观锁:先锁住这一行再改
BEGIN;
SELECT stock FROM products WHERE id = 100 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;
实践要点:MySQL 没有内置的“乐观锁”语法,都是应用层用 version 或时间戳实现;Redis 的 WATCH/MULTI 也是乐观锁思路。
追问方向:乐观锁的 ABA 问题如何解决?高并发秒杀扣库存选哪种?MVCC 与乐观锁有什么关系? (约 391 字)