乐观锁与悲观锁
乐观锁和悲观锁是两种常见的并发控制机制,主要用于解决多线程或分布式系统中数据竞争的问题。它们在实现方式、适用场景和性能特点上有显著差异,以下是详细对比和实际应用示例:
一、悲观锁(Pessimistic Locking)
核心思想
-
假设并发冲突一定会发生,因此在操作数据前直接加锁,确保操作期间数据不被其他事务修改。
-
实现方式:通过数据库的锁机制(如行锁、表锁)或代码层的互斥锁(如
Mutex)实现。
典型应用场景
-
强一致性要求高:如银行转账、库存扣减。
-
写操作频繁:并发环境下频繁修改同一数据的场景。
-
长事务:需要长时间占用资源的操作。
实现示例(MySQL)
-- 开启事务,使用SELECT ... FOR UPDATE锁定记录
BEGIN;
SELECT * FROM products WHERE id = 123 FOR UPDATE;
-- 执行库存扣减
UPDATE products SET stock = stock - 1 WHERE id = 123;
COMMIT;
-
说明:
FOR UPDATE会对查询结果加排他锁,其他事务无法修改该记录,直到当前事务提交。
优点
-
保证操作的强一致性,避免脏读、幻读。
-
适合写操作频繁的场景。
缺点
-
加锁会导致性能下降(高并发时可能形成锁竞争)。
-
可能引发死锁问题(需设置合理的锁超时时间)。
二、乐观锁(Optimistic Locking)
核心思想
-
假设并发冲突很少发生,允许无锁操作,仅在提交时检查数据是否被修改。
-
实现方式:通过版本号(
version)或时间戳(timestamp)标记数据版本。
典型应用场景
-
读多写少:如用户信息更新、文章点赞。
-
高并发轻量级操作:如秒杀、抢购。
-
分布式系统:避免跨服务锁的复杂性。
实现示例(MySQL)
-
基于版本号的乐观锁:
-- 查询当前数据和版本号 SELECT stock, version FROM products WHERE id = 123; -- 更新时校验版本号 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 123 AND version = 1; -- 假设查询到的version是1-
如果返回的影响行数为0,说明版本号已变更,需重试或提示失败。
-
-
基于条件的乐观锁(无需版本字段):
UPDATE products SET stock = stock - 1 WHERE id = 123 AND stock > 0; -- 直接通过业务条件判断
优点
-
无锁设计,性能高(适合高并发场景)。
-
避免死锁问题。
缺点
-
冲突时需要重试或回滚(需代码层处理重试逻辑)。
-
不保证绝对一致性(可能出现短暂的数据不一致)。
三、两者的关键区别
| 对比维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 加锁时机 | 操作前加锁 | 提交时检查 |
| 性能开销 | 高(频繁加锁/释放) | 低(无锁) |
| 适用场景 | 写多读少、强一致性 | 读多写少、高并发 |
| 实现复杂度 | 简单(依赖数据库锁) | 复杂(需处理版本号、重试逻辑) |
| 数据一致性 | 强一致性 | 最终一致性 |
| 典型应用 | 银行转账、库存扣减 | 秒杀、用户信息更新 |
四、实际场景中的选择建议
-
悲观锁适用场景:
-
金融交易:如转账操作必须保证金额绝对正确。
-
库存管理:当库存量极少且不允许超卖时(如限量商品)。
-
示例代码(PHP中的悲观锁):
$pdo->beginTransaction(); $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ? FOR UPDATE"); $stmt->execute([$productId]); // 检查库存并更新... $pdo->commit();
-
-
乐观锁适用场景:
-
秒杀系统:高并发下扣减库存(结合Redis + Lua脚本更优)。
-
用户信息更新:如修改个人资料(冲突概率低)。
-
示例代码(PHP中的乐观锁重试):
$maxRetries = 3; for ($i = 0; $i < $maxRetries; $i++) { $row = $pdo->query("SELECT stock, version FROM products WHERE id = 123")->fetch(); if ($row['stock'] <= 0) break; $result = $pdo->exec("UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 123 AND version = {$row['version']}"); if ($result > 0) { // 更新成功,退出循环 break; } }
-
五、总结
-
悲观锁:以“防患于未然”为核心,适合写操作频繁、强一致性要求的场景,但性能开销大。
-
乐观锁:以“事后补救”为核心,适合高并发、读多写少的场景,需配合重试机制。
实际开发中:
-
在秒杀系统中,通常结合乐观锁(Redis原子操作) + 消息队列实现高性能和高一致性。
-
在传统业务系统(如订单支付)中,可能选择悲观锁保证强事务。
-

浙公网安备 33010602011771号