事务的实现原理可以解读为 RDBMS 采取何种技术确保事务的 ACID 特性。

事务的实现原理可以解读为 RDBMS 采取何种技术确保事务的 ACID 特性。PostgreSQL 主要使用 MVCC 和 WAL 两项技术实现 ACID 特性。

实际上,MVCC 和 WAL 这两项技术都比较成熟,主流关系型数据库中都有相应的实现,但每个数据库中具体的实现方式往往存在较大的差异。

在 PostgreSQL 中,每个事务都有一个唯一的事务 ID,被称为 XID。除了被 BEGIN - COMMIT/ROLLBACK 包裹的一组语句会被当作一个事务对待外,不显示指定 BEGIN - COMMIT/ROLLBACK 的单条语句也是一个事务。

数据库中的事务 ID 递增。可通过 txid_current() 函数获取当前事务的 ID。

PostgreSQL 中,对于每一行数据(称为一个 tuple),包含有 4 个隐藏字段。这四个字段是隐藏的,但可直接访问。

下面通过实验具体看看这些标记如何工作。在此之前,先创建测试表

CREATE TABLE test
(
  id INTEGER,
  value TEXT
);

开启一个事务,查询当前事务 ID(值为 3277),并插入一条数据,xmin 为 3277,与当前事务 ID 相等。符合上文所述——插入 tuple 时记录 xmin,记录未被删除时 xmax 为 0

postgres=> BEGIN;
BEGIN
postgres=> SELECT TXID_CURRENT();
 txid_current
--------------
         3277
(1 row)

postgres=> INSERT INTO test VALUES(1, 'a');
INSERT 0 1
postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;
 id | value | xmin | xmax | cmin | cmax
----+-------+------+------+------+------
  1 | a     | 3277 |    0 |    0 |    0
(1 row)

继续通过一条语句插入 2 条记录,xmin 仍然为当前事务 ID,即 3277,xmax 仍然为 0,同时 cmin 和 cmax 为 1,符合上文所述 cmin/cmax 在事务内随着所执行的语句递增。虽然此步骤插入了两条数据,但因为是在同一条语句中插入,故其 cmin/cmax 都为 1,在上一条语句的基础上加一。

INSERT INTO test VALUES(2, 'b'), (3, 'c');
INSERT 0 2
postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;
 id | value | xmin | xmax | cmin | cmax
----+-------+------+------+------+------
  1 | a     | 3277 |    0 |    0 |    0
  2 | b     | 3277 |    0 |    1 |    1
  3 | c     | 3277 |    0 |    1 |    1
(3 rows)

将 id 为 1 的记录的 value 字段更新为’d’,其 xmin 和 xmax 均未变,而 cmin 和 cmax 变为 2,在上一条语句的基础之上增加一。此时提交事务。

UPDATE test SET value = 'd' WHERE id = 1;
UPDATE 1
postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;
 id | value | xmin | xmax | cmin | cmax
----+-------+------+------+------+------
  2 | b     | 3277 |    0 |    1 |    1
  3 | c     | 3277 |    0 |    1 |    1
  1 | d     | 3277 |    0 |    2 |    2
(3 rows)

postgres=> COMMIT;
COMMIT

开启一个新事务,通过 2 条语句分别插入 2 条 id 为 4 和 5 的 tuple。

BEGIN;
BEGIN
postgres=> INSERT INTO test VALUES (4, 'x');
INSERT 0 1
postgres=> INSERT INTO test VALUES (5, 'y');
INSERT 0 1
postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;
 id | value | xmin | xmax | cmin | cmax
----+-------+------+------+------+------
  2 | b     | 3277 |    0 |    1 |    1
  3 | c     | 3277 |    0 |    1 |    1
  1 | d     | 3277 |    0 |    2 |    2
  4 | x     | 3278 |    0 |    0 |    0
  5 | y     | 3278 |    0 |    1 |    1
(5 rows)

此时,将 id 为 2 的 tuple 的 value 更新为’e’,其对应的 cmin/cmax 被设置为 2,且其 xmin 被设置为当前事务 ID,即 3278