基于PostGreSQL探究 MVCC | 豆包MarsCode AI刷题

2 阅读1分钟

什么是 MVCC

MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。PostgreSQL 引入了 MVCC 机制来保证事务的隔离性,实现数据库的隔离级别。

MVCC 的关键特点

  1. 多版本存储

    1. 数据库中同一个数据的不同版本会存储在不同的时间点上,每个版本对应一次修改。

    2. 每次写操作不会直接覆盖原来的数据,而是生成一个新的版本,旧版本仍然可用。

  2. 快照读取

    1. 每个事务在开始时获取一个“时间快照”,基于这个快照读取数据。

    2. 即使其他事务在进行修改,也不会影响当前事务的读取视图。

  3. 版本控制

    1. 每个数据版本带有两个元信息:创建时间戳删除时间戳

    2. 事务在读取数据时,会通过比较时间戳判断当前事务是否能看到某个版本的数据。

PostgreSQL 中 MVCC 的具体实现

Tuple 数据的存储结构

struct HeapTupleHeaderData
{
    union
    {
       HeapTupleFields t_heap;
       DatumTupleFields t_datum;
    }        t_choice;

    ItemPointerData t_ctid;       /* current TID of this or newer tuple (or a
                          * speculative insertion token) */

    /* Fields below here must match MinimalTupleData! */

#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK2 2
    uint16    t_infomask2;   /* number of attributes + various flags */

#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK 3
    uint16    t_infomask;       /* various flag bits, see below */

#define FIELDNO_HEAPTUPLEHEADERDATA_HOFF 4
    uint8     t_hoff;          /* sizeof header incl. bitmap, padding */

    /* ^ - 23 bytes - ^ */

#define FIELDNO_HEAPTUPLEHEADERDATA_BITS 5
    bits8     t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */

    /* MORE DATA FOLLOWS AT END OF STRUCT */
};

typedef struct HeapTupleFields
{
    TransactionId t_xmin;     /* inserting xact ID */
    TransactionId t_xmax;     /* deleting or locking xact ID */

    union
    {
       CommandId  t_cid;    /* inserting or deleting command ID, or both */
       TransactionId t_xvac;  /* old-style VACUUM FULL xact ID */
    }        t_field3;
} HeapTupleFields;

从中可看出 tuple 的存储结构:

t_xmin

t_xmax

t_ctid

其它字段

具体数据

其中 MVCC 主要需要用到的字段有 t_xmin, t_xmax, t_ctid。

  • t_xmin 存储的是产生这个元组的事务 ID,可能是 insert 或者 update 语句。

  • t_xmax 存储的是删除或者锁定这个元组的事务 ID

  • t_ctid 存储用来记录当前元组或新元组的物理位置

    • 由块号和块内偏移组成

    • 如果这个元组被更新,则该字段指向更新后的新元组

    • 这个字段指向自己,且后面 t_heap 中的 xmax 字段为空,就说明该元组为最新版本。

MVCC 的工作流程

数据库中目前有 A 一条数据:

当事务 T1BEGIN 时事务调度器会分配一个时间戳

当事务 T1 读取 A 时发现 t_xmax=0,t_xmin<TS(T1),于是读取 A1。

当事务 T2BEGIN 是分配时间戳 TS2。

当事务 T2 写入 A 时会新建一个版本 A2,并将 A2 的 t_xmin 和 A1 的 t_xmax 设为 TS2,并让 A1 的 t_ctid 指针指向新版本。

当 T1 进行到 R(A)时查看 A1 发现 t_xmin < TS1 < t_xmax,符合要求,即读取 A 的值为 123。

当 T2 要读取 A 时会先查看 A1,发现 TS2 不在 t_xmin 和 t_xmax 范围中。于是会沿着 t_ctid 指针查看下一版本 A2,发现 A2 的 t_xmax=0,且 A2 的 t_xmin<=TS2 可,于是读取 A2 的值 256。

总结

从上面例子中可看出使用 MVCC 可以解决不可重复读、脏读的问题。此外 PostGreSQL 引入 MVCC 还可以保证事务的隔离性,实现数据库的不同隔离级别,以及提高读写并发性能,并在保证事务隔离性的同时最大化系统吞吐量。这使得 MVCC 成为许多现代数据库的核心机制之一。