PostgreSQL PG的多版本并发控制

本文为云贝教育 刘峰 原创,请尊重知识产权,转发请注明出处,不接受任何抄袭、演绎和未经注明出处的转载。【PostgreSQL】PG的缓存管理器原理 - 课程体系 - 云贝教育

并发是一种当多个事务在数据库中并发运行时维护原子性和隔离性的机制,这是 ACID 的两个属性。

并发控制技术主要分为三种:多版本并发控制(MVCC)、严格两相锁(S2PL)和乐观并发控制(OCC)。每种技术都有许多变化。在 MVCC 中,每次写入操作都会创建数据项的新版本,同时保留旧版本。当事务读取数据项时,系统会选择其中一个版本以确保单个事务的隔离性。MVCC 的主要优点是“读取器不会阻止写入器,写入器也不会阻止读取器”,相比之下,例如,基于 S2PL 的系统在写入器写入项目时必须阻止读取器,因为写入器获得独占性锁定该项目。 PostgreSQL 和一些 RDBMS 使用 MVCC 的变体,称为快照隔离 (SI)。

为了实现 SI,一些 RDBMS(例如 Oracle)使用回滚段。当写入新数据项时,旧版本的数据项被写入回滚段,随后新数据项被覆盖到数据区。 PostgreSQL 使用更简单的方法。新的数据项将直接插入到相关的表页中。读取项目时,PostgreSQL 通过应用可见性检查规则来选择项目的适当版本来响应单个事务。

SI 不允许 ANSI SQL-92 标准中定义的三种异常:脏读、不可重复读和幻读。然而,SI 无法实现真正的可序列化,因为它允许序列化异常,例如 Write Skew 和 Read-only Transaction Skew。请注意,基于经典可串行性定义的 ANSI SQL-92 标准并不等同于现代理论中的定义。为了解决此问题,从版本 9.1 开始添加了可串行快照隔离 (SSI)。 SSI可以检测序列化异常并可以解决此类异常引起的冲突。因此,PostgreSQL 9.1 或更高版本提供了真正的 SERIALIZABLE隔离级别。 (此外,SQL Server 也使用 SSI;Oracle 仍然只使用 SI。)

PostgreSQL 中的事务隔离级别

下表描述了 PostgreSQL 实现的事务隔离级别:

*1:在版本 9.0 及更早版本中,此级别已用作“SERIALIZABLE”,因为它不允许 ANSI SQL-92 标准中定义的三种异常。然而,随着版本 9.1 中 SSI 的实现,该级别已更改为“REPEATABLE READ”,并引入了真正的 SERIALIZABLE 级别。

一. 事务ID

每当事务开始时,事务管理器都会分配一个唯一标识符,称为事务 ID (txid)。 PostgreSQL 的txid 是一个 32 位无符号整数,大约 42 亿(千万)。如果在交易开始后执行内置的txid_current() 函数,该函数将返回当前的 txid,如下所示:

testdb=# BEGIN;
BEGIN
testdb=# SELECT txid_current();
 txid_current
--------------
          825
(1 row)

PostgreSQL 保留以下三个特殊的 txid:

0 表示 txid 无效。

1表示Bootstrap txid,仅在数据库集群初始化时使用。

2表示Frozen txid。

Txids 可以相互比较。例如,从 txid 100 的角度来看,大于 100 的 txid 是“未来的”,并且从txid 100 中是不可见的;小于 100 的 txids 是“过去的”并且可见(图 1.1 a))。

图 1.1。 PostgreSQL 中的事务 ID。

由于实际系统中txid空间不足,PostgreSQL将txid空间视为一个圆。前面的 21 亿个 txid 是“过去的”,接下来的 21 亿个 txid 是“未来的”(图 1.1 b)。

请注意,BEGIN 命令不会分配 txid。在PostgreSQL中,执行BEGIN命令后执行第一个命令时,事务管理器会分配一个tixd,然后事务开始。

二. 行结构

表页中的堆元组分为两种类型:普通数据元组和TOAST元组。本节仅描述常用的元组。

堆元组由三部分组成:HeapTupleHeaderData 结构、NULL 位图和用户数据(图 1.2)。

HeapTupleHeaderData 结构包含七个字段,但后续部分只需要其中四个字段:

• t_xmin 保存插入该元组的事务的 txid。

• t_xmax 保存删除或更新此元组的事务的 txid。如果这个元组没有被删除或更新,t_xmax被设置为0,这意味着INVALID。

• t_cid 保存命令id(cid),它是当前事务内执行此命令之前执行的SQL命令的数量,从0开始。例如,假设我们在单个事务内执行三个INSERT命令:'BEGIN;INSERT;INSERT;INSERT;commit;'。如果第一个命令插入此元组,则 t_cid 设置为 0。如果第二个命令插入此元组,则 t_cid 设置为 1,依此类推。

• t_ctid 保存指向自身或新元组的元组标识符(tid)。 tid,如第 3. 节所述,用于标识表中的元组。当这个tuple更新的时候,这个tuple的t_ctid就指向新的tuple;否则,t_ctid 指向其自身。

PG插件pageinspect
1、安装
testdb=# create extension pageinspect;
CREATE EXTENSION


2、创建表
testdb=# create table t1 as select * from (select row_number() over() as row num,oid from pg_class) t where rownum<2; SELECT 1 3、查看数据 testdb=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('t1', 0)); tuple | t_xmin | t_xmax | t_cid | t_ctid -------+--------+--------+-------+-------- 1 | 850 | 871 | 0 | (0,1) 2 | 851 | 854 | 1 | (0,3) 3 | 854 | 863 | 0 | (0,4) 4 | 863 | 865 | 0 | (0,5) 5 | 865 | 869 | 0 | (0,7) 6 | 866 | 0 | 0 | (0,6) 7 | 869 | 0 | 0 | (0,7) (7 rows)
 

不用插件,查看pg_attribute

1、查询pg_attribute表,查看添加到表中的系统列以及两个列id和name:

postgres=# SELECT attname, format_type (atttypid,atttypmod) FROM pg_attribute
WHERE attrelid = 'foo.bar'::regclass::oid ORDER BY attnum;
attname | format_type
----------+----------------------
tableoid | oid
cmax | cid
xmax | xidCluster Management Techniques
cmin | cid
xmin | xid
ctid | tid
  id | integer
name | character varying(5)
 (8 rows)

ctid来查询每个元组的位置·

通过显式查询xmin,我们可以通过查找每个记录的xmin值来查看插入记录的事务ID。注意以下日志中所有记录的xmin值:

我们还可以通过显式地选择每条记录来找到它的xmax。如果xmax为0,说明它从未被删除,并且是可见的

• cmin和cmax介绍

• cmin:插入事务中的命令标识符(从0开始)。

• cmax:删除事务中的命令标识符(从0开始)。

• cmin和cmax都是表示tuple的command id,即cmin是产生该条tuple的command id,cmax是删除该tuple的command id。

• cmin表示插入数据的command id,cmax表示删除数据的。

那怎么通过一个字段就能识别是插入还是删除呢?想要解决这个问题,我们需要了解下combo cid。

当我们的事务中只是插入数据时,t_cid存储的就是cmin,因为此时也只有cmin是有效的。而当进行了update或者delete操作时,才会产生cmax。当这种既有cmin又有 cmax的情况,即在同一个事务中既有插入又有更新的时候,t_cid存储的就是combo cid,当事务中既有插入又有更新的时候,t_cid存储的便是combo cid。

三.增删改行

本节介绍如何插入、删除和更新元组。然后,简要描述用于插入和更新元组的自由空间映射(FSM)。

为了重点关注元组,页眉和行指针在下面不再表示。图 3. 显示了如何表示元组的示例。

图 1.3。元组的表示。

3.1. 插入

通过插入操作,一个新的元组被直接插入到目标表的一页中(图1.4)。

图 1.4。元组插入。

假设txid为99的事务在页面中插入了一个元组。此时,插入的元组的头字段设置如下。

元组_1:

t_xmin 设置为 99,因为该元组是由 txid 99 插入的。

t_xmax 设置为 0,因为该元组尚未被删除或更新。

t_cid 设置为 0,因为该元组是 txid 99 插入的第一个元组。

t_ctid 设置为 (0,1),它指向自身,因为这是最新的元组。

3.2. 删除

在删除操作中,目标元组被逻辑删除。执行DELETE命令的txid的值被设置为元组的t_xmax(图1.5)。

图 1.5。元组删除。

假设元组Tuple_1被txid 111删除。此时,Tuple_1的头字段设置如下:

元组_1:

t_xmax 设置为 111。

如果提交了 txid 111,则不再需要 Tuple_1。通常,不需要的元组在 PostgreSQL 中被称为死元组。

死元组最终应该从页面中删除。清理死元组称为 VACUUM 处理,这将在第 6 章中进行描述。

3.3.修改

在更新操作中,PostgreSQL逻辑上删除最新的元组并插入一个新的元组(图1.6)。

图 1.6。将该行更新两次。

假设由 txid 99 插入的行被 txid 100 更新了两次。

当执行第一个 UPDATE 命令时,通过将 t_xmax 设置 txid 100 来逻辑删除 Tuple_1,然后插入Tuple_2。然后,Tuple_1的t_ctid被重写为指向Tuple_2。 Tuple_1和Tuple_2的头字段如下:

元组_1:

t_xmax 设置为 100。

t_ctid 从 (0, 1) 重写为 (0, 2)。

元组_2:

t_xmin 设置为 100。

t_xmax 设置为 0。

t_cid 设置为 0。

t_ctid 设置为 (0,2)。

当执行第二个UPDATE命令时,与第一个UPDATE命令一样,Tuple_2被逻辑删除并插入Tuple_3。 Tuple_2和Tuple_3的头字段如下:

元组_2:

t_xmax 设置为 100。

t_ctid 从 (0, 2) 重写为 (0, 3)。

元组_3:

t_xmin 设置为 100。

t_xmax 设置为 0。

t_cid 设置为 1。

t_ctid 设置为 (0,3)。

与删除操作一样,如果 txid 100 被提交,Tuple_1 和 Tuple_2 将成为死元组,并且,如果 txid 100 被中止,Tuple_2 和 Tuple_3 将成为死元组。

3.4. 空闲空间地图

当插入堆或索引元组时,PostgreSQL使用相应表或索引的FSM来选择可以插入的页。

正如 1.2.3 节中提到的,所有表和索引都有各自的 FSM。每个 FSM 存储有关相应表或索引文件中每个页的可用空间容量的信息

所有 FSM 都以“fsm”后缀存储,如有必要,它们会加载到共享内存中。

pg_freespacemap

扩展 pg_freespacemap 提供指定表/索引的可用空间。以下查询显示指定表中每个页面的可用空间比率。

testdb=# CREATE EXTENSION pg_freespacemap;
CREATE EXTENSION

testdb=# SELECT *, round(100 * avail/8192 ,2) as "freespace ratio"
FROM pg_freespace('accounts');
 blkno | avail | freespace ratio
-------+-------+-----------------
     0 |  7904 |           96.00
     1 |  7520 |           91.00
     2 |  7136 |           87.00
     3 |  7136 |           87.00
     4 |  7136 |           87.00
     5 |  7136 |           87.00
 

四. 提交日志 (clog)

PostgreSQL 在提交日志中保存事务的状态。提交日志(通常称为 clog)被分配到共享内存并在整个事务处理过程中使用。

本节介绍 PostgreSQL 中事务的状态、clog 的运行方式以及 clog 的维护。

4.1 事务状态

PostgreSQL 定义了四种事务状态:IN_PROGRESS、COMMITTED、ABORTED 和SUB_COMMITTED。

前三种状态是不言自明的。例如,当事务正在进行时,其状态为 IN_PROGRESS。

SUB_COMMITTED是针对子事务的,本文档中省略其描述。

4.2. CLOG运作过程

该clog由共享内存中的一个或多个 8 KB 页组成。它在逻辑上形成一个数组,其中数组的索引对应于各自的事务id,数组中的每一项保存对应事务id的状态。图 1.7 显示了clog及其工作原理。

图 1.7。clog如何运作。

T1:txid 200 次提交; txid 200 的状态从 IN_PROGRESS 更改为 COMMITTED。

T2:txid 201 中止; txid 201 的状态从 IN_PROGRESS 更改为 ABORTED。

当当前的 txid 前进并且 clog 无法再存储它时,会附加一个新页面。

当需要事务状态时,将调用内部函数。这些函数读取clog并返回所请求事务的状态。

4.3. 维护CLOG

当 PostgreSQLE服务关闭或检查点进程运行时,clog 的数据将写入存储在 pg_xact 子目录中的文件中。 (请注意,pg_xact 在 9.6 或更早版本中称为 pg_clog。)这些文件被命名为0000、0001 等。最大文件大小为 256 KB。例如,如果clog使用8页(第一页到第八页,总大小为64 KB),则其数据写入0000(64 KB)。如果clog使用37页(296 KB),则其数据被写入0000和0001,其大小分别为256 KB和40 KB。

当 PostgreSQL 启动时,会加载 pg_xact 文件中存储的数据来初始化 clog。

clog的大小不断增加,因为每当clog被填满时就会附加新的页面。然而,并非clog中的所有数据都是必需的。vacuum处理会定期删除此类旧数据(clog页面和文件)。

五. 事务快照

事务快照是一个数据集,它存储有关单个事务的某个时间点的所有事务是否都处于活动状态的信息。这里的活动事务意味着它正在进行或尚未开始。

PostgreSQL内部定义事务快照的文本表示格式为“100:100:”。例如,“100:100:”表示“小于 99 的 txids 不活跃,等于或大于 100 的 txids 活跃”。

内置函数 pg_current_snapshot 及其文本表示格式

函数 pg_current_snapshot 显示当前事务的快照。

testdb=# SELECT pg_current_snapshot();
 pg_current_snapshot
---------------------
 100:104:100,102
(1 row)

txid_current_snapshot 的文本表示为“xmin:xmax:xip_list”,其组成部分描述如下:

• xmin

(仍处于活动状态的最早的 txid):所有较早的事务将要么提交并可见,要么回滚并终止。

• xmax

(第一个尚未分配的 txid):所有大于或等于该值的 txid 在快照时尚未启动,因此不可见。

• xip_list

(快照时的活动交易 ID 列表):该列表仅包含 xmin 和 xmax 之间的活动交易 ID。

例如,在快照“100:104:100,102”中,xmin 为“100”,xmax 为“104”,xip_list 为“100,102”。

下面是两个具体的例子:

图 1.8。事务快照表示的示例。

第一个示例是“100:100:”。该快照的含义如下(图 1.8(a)):

等于或小于 99 的 txids 不处于活动状态,因为 xmin 为 100。

等于或大于 100 的 txids 处于活动状态,因为 xmax 为 100。

第二个示例是“100:104:100,102”。该快照的含义如下(图 1.8(b)):

等于或小于 99 的 txids 不活跃。

等于或大于 104 的 txids 处于活动状态。

txid 100 和 102 处于活动状态,因为它们存在于 xip 列表中,而 txid 101 和 103 则处于不活动状态。

事务快照由事务管理器提供。在READ COMMITTED隔离级别下,每当执行SQL命令时,事务都会获取快照;否则(REPEATABLE READ 或 SERIALIZABLE),事务仅在执行第一个 SQL 命令时获取快照。获得的事务快照用于元组的可见性检查,这在1.7节中描述。

当使用获取的快照进行可见性检查时,快照中的活动事务必须被视为正在进行中,即使它们实际上已提交或中止。此规则很重要,因为它会导致 READ COMMITTED 和 REPEATABLE READ(或 SERIALIZABLE)之间的行为差异。我们在以下各节中反复引用此规则。

在本节的其余部分中,事务管理器和事务将使用特定场景图 1.9 进行描述。

事务管理器始终保存有关当前正在运行的事务的信息。假设三个事务相继启动,Transaction_A和Transaction_B的隔离级别为READ COMMITTED,Transaction_C的隔离级别为REPEATABLE READ。

T1:

Transaction_A 启动并执行第一个 SELECT 命令。当执行第一个命令时,Transaction_A 请求当前时刻的 txid 和快照。在这种情况下,事务管理器分配 txid 200,并返回事务快照“200:200:”。

T2:

Transaction_B 启动并执行第一个 SELECT 命令。事务管理器分配 txid 201,并返回事务快照“200:200:”,因为 Transaction_A (txid 200) 正在进行中。因此,从Transaction_B中看不到Transaction_A。

T3:

Transaction_C 启动并执行第一个 SELECT 命令。事务管理器分配txid 202,并返回事务快照'200:200:',因此从Transaction_C中看不到Transaction_A和Transaction_B。

T4:

Transaction_A 已提交。事务管理器删除有关该事务的信息。

T5:

Transaction_B 和 Transaction_C 执行各自的 SELECT 命令。

Transaction_B 需要事务快照,因为它处于 READ COMMITTED 级别。在这种情况下,Transaction_B 获得新快照“201:201:”,因为 Transaction_A (txid 200) 已提交。因此,Transaction_A 不再对 Transaction_B 不可见。Transaction_C 不需要事务快照,因为它处于 REPEATABLE READ 级别并使用获取的快照,即“200:200:”。因此,Transaction_A 对于 Transaction_C 仍然是不可见的。

六. 可见性检查规则

可见性检查规则是一组规则,用于使用元组的 t_xmin 和 t_xmax、clog 以及获取的事务快照来确定每个元组是否可见或不可见。这些规则太复杂,无法详细解释。因此,本文档显示了后续描述所需的最低规则。下面,我们省略与子事务相关的规则,并忽略关于 t_ctid 的讨论,即我们不考虑在事务内更新两次以上的元组。

所选规则的数量为十个,可以分为三种情况。

6.1. t_xmind状态为ABORT

t_xmin 状态为 ABORTED 的元组始终不可见(规则 1),因为插入该元组的事务已被中止。

/* t_xmin status == ABORTED */
Rule 1: IF t_xmin status is 'ABORTED' THEN
                  RETURN 'Invisible'
            END IF

该规则明确地表达为以下数学表达式。

• Rule 1: If Status(t_xmin) = ABORTED ⇒ Invisible

6.2. t_xmind状态为IN_PROGRESS

t_xmin 状态为 IN_PROGRESS 的元组本质上是不可见的(规则 3 和 4),除非在一种情况下。

/* t_xmin status == IN_PROGRESS */
IF t_xmin status is 'IN_PROGRESS' THEN
IF t_xmin = current_txid THEN
Rule 2: IF t_xmax = INVALID THEN
RETURN 'Visible'
Rule 3: ELSE /* this tuple has been deleted or updated by the cur
rent transaction itself. */
RETURN 'Invisible'
END IF
Rule 4: ELSE /* t_xmin ≠ current_txid */
RETURN 'Invisible'
END IF
END IF

如果这个元组是被另一个事务插入的,并且t_xmin的状态是IN_PROGRESS,那么这个元组显然是不可见的(规则4)。

如果 t_xmin 等于当前 txid(即该元组是由当前事务插入的)并且 t_xmax 不为 INVALID,则该元组是不可见的,因为它已被当前事务更新或删除(规则 3)。

异常情况是当前事务插入该元组且 t_xmax 为 INVALID 的情况。在这种情况下,这个元组必须对当前事务可见(规则2),因为这个元组是当前事务本身插入的元组。

• Rule 2: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax = INVAILD ⇒ Visible

• Rule 3: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax ≠ INVAILD ⇒ Invisible

• Rule 4: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin ≠ current_txid ⇒ Invisible

6.3. t_xmind状态为COMMITTED

t_xmin 状态为 COMMITTED 的元组是可见的(规则 6,8 和 9),但以下三种情况除外。

/* t_xmin status == COMMITTED */
IF t_xmin status is 'COMMITTED' THEN
Rule 5: IF t_xmin is active in the obtained transaction snapshot THEN
RETURN 'Invisible'
Rule 6: ELSE IF t_xmax = INVALID OR status of t_xmax is 'ABORTED' THEN
RETURN 'Visible'
ELSE IF t_xmax status is 'IN_PROGRESS' THEN
Rule 7: IF t_xmax = current_txid THEN
RETURN 'Invisible'
Rule 8: ELSE /* t_xmax ≠ current_txid */
RETURN 'Visible'
END IF
ELSE IF t_xmax status is 'COMMITTED' THEN
Rule 9: IF t_xmax is active in the obtained transaction snapshot THEN
RETURN 'Visible'
Rule 10: ELSE
RETURN 'Invisible'
END IF
END IF
END IF

规则 6 很明显,因为 t_xmax 无效或已中止。三种例外情况以及规则 8 和 9 描述如下:

第一个例外情况是t_xmin在获取的事务快照中处于活动状态(规则5)。在这种情况下,该元组是不可见的,因为 t_xmin 应被视为正在进行中。

第二个例外情况是 t_xmax 是当前 txid(规则 7)。在这种情况下,与规则 3 一样,该元组是不可见的,因为它已被该事务本身更新或删除。

相反,如果 t_xmax 的状态为 IN_PROGRESS 并且 t_xmax 不是当前 txid(规则 8),则该元组可见,因为它尚未被删除。

第三个例外情况是t_xmax的状态为COMMITTED并且t_xmax在获取的事务快照中不活跃(规则10)。在这种情况下,该元组是不可见的,因为它已被另一个事务更新或删除。

相反,如果 t_xmax 的状态为 COMMITTED,但 t_xmax 在获取的事务快照中处于活动状态(规则 9),则该元组可见,因为 t_xmax 应被视为正在进行中。

• Rule 5: If Status(t_xmin) = COMMITTED ∧ Snapshot(t_xmin) = active ⇒ Invisible

• Rule 6: If Status(t_xmin) = COMMITTED ∧ (t_xmax = INVALID ∨ Status(t_xmax) = ABORTED) ⇒ Visible

• Rule 7: If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax = current_txid ⇒ Invisible

• Rule 8: If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax ≠ current_txid ⇒ Visible

• Rule 9: If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) = active ⇒ Visible

• Rule 10: If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) ≠ active ⇒ Invisible

七. 可见性检查

本节描述 PostgreSQL 如何执行可见性检查,这是在给定事务中选择适当版本的堆元组的过程。本节还介绍 PostgreSQL 如何防止 ANSI SQL-92 标准中定义的异常:脏读、可重复读和幻读。

7.1. 可见性检查

图 1.10 显示了描述可见性检查的场景。

图 1.10。描述可见性检查的场景。

在图1.10所示的场景中,SQL命令按照以下时序执行。

T1:开始交易(txid 200)

T2:开始交易(txid 201)

T3:执行txid 200和201的SELECT命令

T4:执行txid 200的UPDATE命令T5:执行txid 200和201的SELECT命令

T6:提交 txid 200

T7:执行txid 201的SELECT命令

为了简化描述,假设只有两个事务,即txid 200和201。txid 200的隔离级别是READ COMMITTED,txid 201的隔离级别是READ COMMITTED 或 REPEATABLE READ。

我们探讨 SELECT 命令如何对每个元组执行可见性检查。

SELECT commands of T3:

在 T3 时,表 tbl 中只有 Tuple_1,并且根据规则 6 可见。因此,两个事务中的 SELECT 命令都返回“Jekyll”。

• Rule6(Tuple_1) ⇒ Status(t_xmin:199) = COMMITTED ∧ t_xmax = INVALID ⇒ Visible

testdb=# -- txid 200
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)

testdb=# -- txid 201
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)

SELECT commands of T5:

首先,我们探索 txid 200 执行的 SELECT 命令。根据规则 7,Tuple_1 不可见,而根据规则2,Tuple_2 可见。因此,该 SELECT 命令返回“Hyde”。

• Rule7(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) =IN_PROGRESS ∧ t_xmax:200 = current_txid:200 ⇒ Invisible

• Rule2(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 =current_txid:200 ∧ t_xmax = INVAILD ⇒ Visible

testdb=# -- txid 200
testdb=# SELECT * FROM tbl;
name
------
Hyde
(1 row)

另一方面,在 txid 201 执行的 SELECT 命令中,Tuple_1 根据规则 8 可见,而 Tuple_2 根据规则 4 不可见。因此,该 SELECT 命令返回“Jekyll”。

• Rule8(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = IN_PROGRESS ∧ t_xmax:200 ≠ current_txid:201 ⇒ Visible

• Rule4(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 ≠ current_txid:201 ⇒ Invisible

testdb=# -- txid 201
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)

如果更新的元组在提交之前从其他事务中可见,则这称为脏读,也称为 wr 冲突。然而,如上所示,在 PostgreSQL 的任何隔离级别下都不会发生脏读。

SELECT command of T7:

下面描述 T7 的 SELECT 命令在两个隔离级别下的行为。

当 txid 201 处于 READ COMMITTED 级别时,txid 200 被视为已提交,因为事务快照是“201:201:”。因此,根据规则 10,Tuple_1 不可见,而根据规则 6,Tuple_2 可见。SELECT命令返回“Hyde”。

• Rule10(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) ≠ active ⇒ Invisible

• Rule6(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ t_xmax = INVALID ⇒ Visible

testdb=# -- txid 201 (READ COMMITTED)
testdb=# SELECT * FROM tbl;
name
------
Hyde
 

请注意,在提交 txid 200 之前和之后执行的 SELECT 命令的结果是不同的。这通常称为不可重复读取。

相反,当 txid 201 处于 REPEATABLE READ 级别时,txid 200 必须被视为 IN_PROGRESS,因为事务快照是“200:200:”。因此,根据规则 9,Tuple_1 可见,根据规则 5,Tuple_2 不可见。SELECT 命令返回“Jekyll”。请注意,不可重复读取不会发生在可重复读取(和可串行化)级别中。

• Rule9(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) = active ⇒ Visible

• Rule5(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ Snapshot(t_xmin:200) = active ⇒ Invisible、

testdb=# -- txid 201 (REPEATABLE READ)
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)
7.2. PostgreSQL 的 REPEATABLE READ 级别中的幻

读ANSI SQL-92 标准中定义的可重复读取允许幻读。然而,PostgreSQL 的实现不允许它们。原则上,SI 不允许幻读。

假设两个事务(即 Tx_A 和 Tx_B)同时运行。它们的隔离级别是 READ COMMITTED 和REPEATABLE READ,它们的 txid 分别是 100 和 101。首先,Tx_A 插入一个元组。然后,就承诺了。插入的元组的t_xmin为100。接下来,Tx_B执行SELECT命令;然而,根据规则 5,Tx_A 插入的元组是不可见的。因此,不会发生幻读。

• Rule5(new tuple): Status(t_xmin:100) = COMMITTED ∧ Snapshot(t_xmin:100) = active ⇒ Invisible

事务A

testdb=# -- Tx_A: txid 100
testdb=# START TRANSACTION
testdb-# ISOLATION LEVEL READ COMMITTED;
START TRANSACTION


testdb=# INSERT tbl(id, data)
VALUES (1,'phantom');
INSERT 1

testdb=# COMMIT;
COMMIT

事务B

testdb=# -- Tx_B: txid 101
testdb=# START TRANSACTION
testdb-# ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION
testdb=# SELECT txid_current();
txid_current
--------------
101
(1 row)10
testdb=# SELECT * FROM tbl WHERE id=1;
id | data
----+------
(0 rows)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/263840.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于vue-advanced-chat组件自义定聊天(socket.io+vue2)

通过上一篇文章https://blog.csdn.net/beekim/article/details/134176752?spm=1001.2014.3001.5501, 我们已经在vue-advanced-chat中替换掉原有的firebase,用socket.io简单的实现了聊天功能。 现在需要自义定该组件,改造成我们想要的样子: 先将比较重要的几块提取出来 …

【C++】可变参数模板使用总结(简洁易懂,详细,含代码演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…

[微服务 ]微服务集成中的3个常见缺陷,以及如何避免它们

微服务风靡一时。他们有一个有趣的价值主张&#xff0c;即在与多个软件开发团队共同开发的同时&#xff0c;将软件快速推向市场。因此&#xff0c;微服务是在扩展您的开发力量的同时保持高敏捷性和快速的开发速度。 简而言之&#xff0c;您将系统分解为微服务。分解并不是什么新…

机器学习 | 聚类Clustering 算法

物以类聚人以群分。 什么是聚类呢&#xff1f; 1、核心思想和原理 聚类的目的 同簇高相似度 不同簇高相异度 同类尽量相聚 不同类尽量分离 聚类和分类的区别 分类 classification 监督学习 训练获得分类器 预测未知数据 聚类 clustering 无监督学习&#xff0c;不关心类别标签 …

『番外篇五』SwiftUI 进阶之如何动态获取任意视图的 tag 和 id 值

概览 在某些场景下,我们需要用代码动态去探查 SwiftUI 视图的信息。比如任意视图的 id 或 tag 值: 如上图所示:我们通过动态探查技术在运行时将 SwiftUI 特定视图的 tag 和 id 值显示在了屏幕上。 这是如何做到的呢? 在本篇博文,您将学到如下内容: 概览1. “如意如意,…

【C语言】指针详解(三)

1.指针运算 指针的基本运算有三种&#xff0c;分别是:⭐指针-整数 ⭐指针-指针 ⭐指针的关系运算 1.1指针 - 整数 因为数组在内存中是连续存放的&#xff0c;只要知道第一个元素的地址&#xff0c;顺藤摸瓜就能找到后面的所有元素。 int arr[10]{1,2,3,4,5,6,7,8,9,10} #inc…

Python to_numeric函数参数解读与最佳实践!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Python中的to_numeric函数是pandas库提供的一个强大而灵活的工具&#xff0c;用于将数据转换为数字类型。本文将深入探讨to_numeric函数的各种参数和用法&#xff0c;通过丰富的示例代码帮助大家更全面地理解和运…

Web自动化测试工具的优势分析

Web自动化测试工具在现代软件开发中扮演着关键的角色&#xff0c;帮助团队确保Web应用程序的质量和稳定性。然而&#xff0c;选择合适的Web自动化测试工具对项目的成功至关重要。本文将介绍Web自动化测试工具优势是什么! 1. 自动化执行 Web自动化测试工具能够模拟用户的行为&am…

算法通关村-番外篇排序算法

大家好我是苏麟 , 今天带来番外篇 . 冒泡排序 BubbleSort 最基本的排序算法&#xff0c;最常用的排序算法 . 我们以关键字序列{26,53,48,11,13,48,32,15}看一下排序过程: 代码如下 : (基础版) class Solution {public int[] sortArray(int[] nums) {for(int i 0;i < n…

接口测试学习笔记

文章目录 认识urlhttp协议接口规范Postman实现接口测试设计接口测试用例使用软件发送请求并查看响应结果Postman 自动关联Postman如何提交multipart/form-data请求数据Postman如何提交查询参数Postman 如何批量执行用例单接口测试Postman 断言Postman参数化 接口测试自动化requ…

【笔记】Spring是什么

什么是spring&#xff1f; Spring的基础知识铺垫 IOC AOP<-Spring->容器->生态 先说你的认知&#xff0c;总-分结构 spring是一个基础的框架&#xff0c;同时提供了Bean的容器&#xff0c;用来方便装载具体的Bean对象&#xff0c;之前在使用对象的时候必须自己new&…

C语言操作符详解+运算符优先级表格

目录 前言 一、操作符是什么&#xff1f; 二、操作符的分类 三、算术操作符 四、逻辑操作符 五、比较操作符 六、位操作符 七、赋值操作符 八、其他操作符 九、运算符优先级表格 总结 前言 在编写程序时&#xff0c;最常用到的就是操作符&#xff0c;本文将详细的介绍…

STM32的以太网外设+PHY(LAN8720)使用详解(7):以太网数据接收及发送测试

0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册 4.Wireshark1 以太网数据接收测试 1.1 以太网数据接收测试&#xff08;轮询&#xff09; 我们在主循环内轮询RX DMA描述符标志位查看是否接收到了数据&#xff0c;如果接收到了则将数据…

在nodejs中使用讯飞星火大模型3.0的demo

需求&#xff1a; 在nodejs引入讯飞星火大模型的api接口, 思路 看了一下官方文档 api连接为一个WebSocket Secure&#xff08;WSS&#xff09;连接&#xff0c;具体思路如下&#xff1a; 引入 crypto 和 ws 模块&#xff0c;分别用于生成加密签名和创建 WebSocket 连接。&am…

27. 过滤器

Filter(过滤器)简介 Filter 的基本功能是对 Servlet 容器调用 Servlet 的过程进行拦截&#xff0c;从而在 Servlet 进行响应处理的前后实现一些特殊的功能。在 Servlet API 中定义了三个接口类来开供开发人员编写 Filter 程序&#xff1a;Filter, FilterChain, FilterConfigFi…

PHP笔记

文章目录 PHP一、什么是PHP二、PHP集成环境的安装三、WampServer四、PHP基础PHP标准格式php注释变量的定义传值替换变量的作用域变量的检测与删除static静态变量进制转换响应头字符串边界定位符字符串函数常量的定义三元表达式 五、基础文件引入点函数参数类型约束以及严格模式…

OpenAI 官方 Prompt 工程指南:写好 Prompt 的六个策略

其实一直有很多人问我&#xff0c;Prompt 要怎么写效果才好&#xff0c;有没有模板。 我每次都会说&#xff0c;能清晰的表达你的想法&#xff0c;才是最重要的&#xff0c;各种技巧都是其次。但是&#xff0c;我还是希望发给他们一些靠谱的文档。 但是&#xff0c;网上各种所…

Postman接口测试之Post、Get请求方法

一、基础知识 1.HTTP的五种请求方法&#xff1a;GET&#xff0c; POST &#xff0c;HEAD&#xff0c;OPTIONS&#xff0c; PUT&#xff0c; DELETE&#xff0c; TRACE 和 CONNECT 方法。 GET请求&#xff1a;请求指定的页面信息&#xff0c;并返回实体主体。&#xff08;通常用…

“用户名不在 sudoers文件中,此事将被报告” 解决方法

原因 当普通用户需要安装文件时&#xff0c;无法用yum install ** -y直接安装时&#xff0c;采用sudo yum install **; 但是发现提示“用户名不在 sudoers文件中&#xff0c;此事将被报告” 解决方法。 这是因为该普通用户不在sudoers文件中&#xff0c;所以要找到该文件&am…

SpringIOC之BeanFactoryResolver

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…