← 中文资源

简体中文版本

乐观锁

乐观锁在写入时用版本号检测并发冲突,允许并发读取而无需加锁。

阅读英文原文

概念定义

乐观锁假定冲突很少,因此不在客户端读取和决策期间持锁。相反,每一行携带版本或时间戳;更新在 WHERE 子句里带上它读到的版本,只有当存储的版本仍然匹配时写入才成功。不匹配意味着他人改过该行,应用随即重试或报告冲突。它用少量重试概率换取无锁争用。

为什么重要

悲观锁从读到写一直持锁,会阻塞其他用户并有死锁风险,尤其在多步用户交互中。乐观锁完全不加锁、在写入时检测冲突,从而避免这些问题。对于读多写少的工作负载,它提升吞吐,并让客户端不必持有一把需要跨越缓慢往返的锁。

工程实践

始终读取版本并把它纳入更新谓词,并把“影响零行”的结果当作冲突而非错误。发生冲突时重新加载并重新施加变更,或把它呈现给用户;绝不要用旧值盲目重试。保持版本自增准确且单调,优先使用显式版本号而非宽松的时间戳,并在测试中覆盖冲突处理路径,因为该失效模式只在真实并发下才显现。

代码示例

export interface Record { id: string; value: string; version: number; }
export type UpdateResult = { ok: true; record: Record } | { ok: false; conflict: true; current: Record };

export async function updateWithVersion(
  store: { load(id: string): Promise<Record | undefined>; save(record: Record, expectedVersion: number): Promise<boolean> },
  id: string,
  mutate: (value: string) => string,
): Promise<UpdateResult> {
  const current = await store.load(id);
  if (!current) throw new Error('record not found: ' + id);
  const next: Record = { id, value: mutate(current.value), version: current.version + 1 };
  const saved = await store.save(next, current.version);
  if (!saved) {
    const fresh = await store.load(id);
    if (!fresh) throw new Error('record vanished during update: ' + id);
    return { ok: false, conflict: true, current: fresh };
  }
  return { ok: true, record: next };
}
-- 乐观锁更新:版本匹配才写入,且不允许透支。
UPDATE accounts
SET balance = balance - :amount,
    version = version + 1
WHERE id = :account_id
  AND version = :expected_version
  AND balance >= :amount;

-- 应用侧检查受影响行数:
--   1  -> 更新成功
--   0  -> 并发冲突,或余额不足(需再读一次以区分)

-- 取回最新版本用于重试:
SELECT id, balance, version FROM accounts WHERE id = :account_id;