概念定义
乐观锁假定冲突很少,因此不在客户端读取和决策期间持锁。相反,每一行携带版本或时间戳;更新在 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;