← 中文资源

简体中文版本

MVCC 快照隔离模拟器

模拟多版本并发控制,使一个事务读取一致快照,而其他事务同时提交新版本。

阅读英文原文

功能说明

该示例演示 MVCC 的核心:每次写入不覆盖旧值,而是追加一个带生效区间的新版本。事务开启时拿一个时间戳,之后所有读取都只看在该快照时刻可见的版本。由于读不阻塞写、写不阻塞读,多个事务可以并行,同时各自看到一致的数据视图。

关键代码

每个版本记录起始时间戳与结束时间戳,活跃版本的结束时间为正无穷。写入时先给同 key 的活跃版本封上结束时间戳,即令其对该时间点之后的快照不可见,再追加新版本。读取遍历所有版本,只接受起始不晚于快照且快照早于其结束的版本,并在可能重叠时取起始最新的那一个。时间戳由单调递增的时钟产生。

使用注意

这是用于讲解的简化模型,未处理写写冲突与写偏斜:两个事务读取同一快照再写不同 key 时可能都提交,从而违反业务不变量,真实系统需以冲突检测或可串行化隔离补充。读取为线性扫描,生产实现用索引与可见性映射来定位版本。快照隔离保证读一致却不保证串行等价,选型前应明确所用引擎实际提供的保证。

代码示例

interface VersionedRow {
  readonly key: string;
  readonly value: string;
  readonly startTs: number;
  readonly endTs: number;
}

export class MvccStore {
  private versions: VersionedRow[] = [];
  private clock = 0;

  beginTransaction(): number {
    return ++this.clock;
  }

  write(key: string, value: string, ts: number): void {
    for (const row of this.versions) {
      if (row.key === key && row.endTs === Number.POSITIVE_INFINITY) row.endTs = ts;
    }
    this.versions.push({ key, value, startTs: ts, endTs: Number.POSITIVE_INFINITY });
  }

  read(key: string, snapshotTs: number): string | undefined {
    let found: VersionedRow | undefined;
    for (const row of this.versions) {
      if (row.key !== key) continue;
      if (row.startTs <= snapshotTs && snapshotTs < row.endTs) {
        if (!found || row.startTs > found.startTs) found = row;
      }
    }
    return found?.value;
  }
}