Skip to content

悲观锁 (Pessimistic Lock) ​

📋 概述 ​

悲观锁(Pessimistic Lock)是一种并发控制策略,它基于"悲观"的假设:认为数据冲突必然会发生。因此,在操作数据之前,先获取锁,确保在整个操作过程中数据不会被其他事务修改。

核心特点 ​

✅ 优点:

  • 数据安全: 完全避免数据冲突
  • 实现简单: 逻辑直观,易于理解
  • 强一致性: 确保数据的严格一致性
  • 适用高冲突: 在冲突频繁的场景表现好

❌ 缺点:

  • 性能较低: 加锁和释放锁有开销
  • 并发度低: 阻塞其他事务的访问
  • 可能死锁: 多个事务互相等待
  • 资源浪费: 即使没有冲突也加锁

适用场景 ​

  • 高冲突场景: 多个事务频繁修改同一数据
  • 金融交易: 银行转账、支付等关键业务
  • 库存扣减: 电商秒杀、库存管理
  • 临界区保护: 需要独占访问的代码段
  • 短事务: 操作时间短,锁持有时间短

🔧 工作原理 ​

基本流程 ​

1. 开始事务
   ↓
2. 获取锁(SELECT ... FOR UPDATE)
   ↓
3. 读取数据
   ↓
4. 处理业务逻辑
   ↓
5. 更新数据
   ↓
6. 提交事务,释放锁

代码示例 ​

sql
-- 银行转账示例
BEGIN;

-- 步骤1: 锁定转出账户
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 检查余额是否充足

-- 步骤2: 锁定转入账户
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;

-- 步骤3: 执行转账
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 步骤4: 记录日志
INSERT INTO transfer_logs VALUES (...);

-- 步骤5: 提交事务,释放锁
COMMIT;

💻 各数据库实现 ​

MySQL ​

sql
-- 显式使用悲观锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 不等待立即返回
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- 跳过被锁定的行
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;

PostgreSQL ​

sql
-- 基本用法
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 更细粒度的控制
SELECT * FROM users WHERE id = 1 FOR NO KEY UPDATE;
SELECT * FROM users WHERE id = 1 FOR SHARE;

SQL Server ​

sql
BEGIN TRANSACTION;
    SELECT * FROM users WITH (UPDLOCK, ROWLOCK) WHERE id = 1;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT TRANSACTION;

Oracle ​

sql
BEGIN
    SELECT * INTO user_record FROM users WHERE id = 1 FOR UPDATE;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
    COMMIT;
END;

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 缩短锁持有时间 ​

sql
-- ❌ 不推荐
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 调用外部 API(耗时)
CALL process_order(1);
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;

-- ✅ 推荐
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;
-- 之后再调用外部 API
CALL process_order(1);

2. 固定加锁顺序 ​

sql
-- ✅ 总是按相同顺序加锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 2 FOR UPDATE;

3. 设置超时 ​

sql
-- MySQL
SET innodb_lock_wait_timeout = 10;

-- PostgreSQL
SET lock_timeout = '5s';

❌ 避免陷阱 ​

1. 避免死锁 ​

python
# ✅ 重试机制
import time

max_retries = 3
for attempt in range(max_retries):
    try:
        cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")
        break
    except DeadlockException:
        if attempt == max_retries - 1:
            raise
        time.sleep(0.1 * (2 ** attempt))

2. 避免大事务 ​

sql
-- ❌ 不推荐:长时间持有锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 大量无关操作
COMMIT;

-- ✅ 推荐:快速完成
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET name = 'test' WHERE id = 1;
COMMIT;

📊 性能影响 ​

适用场景评估 ​

场景适合度说明
高冲突⭐⭐⭐⭐⭐完美适用
低冲突⭐⭐过度设计
短事务⭐⭐⭐⭐⭐性能好
长事务⭐⭐阻塞严重
高并发读⭐不适合
高并发写⭐⭐⭐⭐需要优化

🚨 常见问题 ​

Q1: 悲观锁和乐观锁如何选择? ​

答:

因素选择悲观锁选择乐观锁
冲突频率高低
事务长度短长
并发度要求低高
实现复杂度简单复杂

Q2: 如何预防死锁? ​

答:

  1. 固定加锁顺序
  2. 缩短事务时间
  3. 设置超时
  4. 使用重试机制

📚 相关资源 ​

内部链接 ​

外部资源 ​


最后更新: 2026-04-12
维护状态: ✅ 完整

Released under MIT License.