PostgreSQL 更新与删除 + 插入的“特别之处”是什么

Pau*_*per 11 postgresql concurrency mvcc

我的理解是更新锁定一个元组,将其标记为已删除,然后添加一个新元组。

换句话说,更新 = 删除 + 插入。

或者我曾经相信。但在 MVCC 中,更新与删除 + 插入之间似乎存在一些根本不同。


设置:

CREATE TABLE example (a int PRIMARY KEY, b int);
INSERT INTO example VALUES (1, 1);
Run Code Online (Sandbox Code Playgroud)

方法一:更新

CREATE TABLE example (a int PRIMARY KEY, b int);
INSERT INTO example VALUES (1, 1);
Run Code Online (Sandbox Code Playgroud)

方法二:删除插入

-- session A                          session B
BEGIN;
UPDATE example SET b = 2 WHERE a = 1;
                                      DELETE FROM example WHERE a = 1;
COMMIT;
-- now there are 0 rows in table example (1 row was deleted by session B)
Run Code Online (Sandbox Code Playgroud)

因此

-- session A                          session B
BEGIN;
DELETE FROM example WHERE a = 1;
INSERT INTO example VALUES (1, 2);
                                      DELETE FROM example WHERE a = 1;
COMMIT;
-- now there is 1 row in table example (0 rows deleted by session B)
Run Code Online (Sandbox Code Playgroud)

不同于

UPDATE example SET b = 2 WHERE a = 1;
Run Code Online (Sandbox Code Playgroud)

我如何理解更新的 MVCC 性质?元组是否具有某种在更新过程中保留的 MVCC“身份”?它是什么?

Lau*_*lbe 14

是的,UPDATEDELETE+之间是有区别的INSERT

让我们使用pageinspect扩展来查看元组和元组标题。

如果你想重复我的实验,你必须在两者之间删除并重新创建表。此外,如果您在检查行之前选择了行,则可能会有其他标志(提示位)。

的意义infomask2infomask可以发现src/include/access/htup_details.h,看到报价在回答结束。

之后UPDATE

SELECT lp, t_xmin, t_xmax, t_ctid, t_infomask2, t_infomask, t_attrs
FROM heap_page_item_attrs(get_raw_page('example', 0), 'example');

 lp | t_xmin | t_xmax | t_ctid | t_infomask2 | t_infomask |            t_attrs            
----+--------+--------+--------+-------------+------------+-------------------------------
  1 | 380943 | 380944 | (0,2)  |       16386 |        256 | {"\\x01000000","\\x02000000"}
  2 | 380944 |      0 | (0,2)  |       32770 |      10240 | {"\\x01000000","\\x02000000"}
(2 rows)
Run Code Online (Sandbox Code Playgroud)
  • 第一个元组是死元组。它t_ctid已更改为指向更新版本。

    这是关键点之一,所以让我扩展一下:ctid元组的组合是块号和“行指针”(lp在查询结果中。t_ctid通常是多余的,但在这种情况下它用于指向新的行版本。这是原始元组和更新版本之间的链接。

    t_infomask2是 2(列数)加上HEAP_HOT_UPDATED,因此该行收到了HOT 更新(块中有足够的空间,并且没有索引)。t_infomaskHEAP_XMIN_COMMITTED(提示位)。

  • 第二个元组是新版本。

    t_infomask2是 2 plus HEAP_ONLY_TUPLE,所以这是“仅堆元组”,只能通过ctid旧版本的更新才能访问。t_infomaskis HEAP_XMAX_INVALID(true, it is 0) plus HEAP_UPDATED(这是更新的版本)。

DELETE+ 之后INSERT

SELECT lp, t_xmin, t_xmax, t_ctid, t_infomask2, t_infomask, t_attrs
FROM heap_page_item_attrs(get_raw_page('example', 0), 'example');

 lp | t_xmin | t_xmax | t_ctid | t_infomask2 | t_infomask |            t_attrs            
----+--------+--------+--------+-------------+------------+-------------------------------
  1 | 380958 | 380961 | (0,1)  |        8194 |        256 | {"\\x01000000","\\x02000000"}
  2 | 380961 |      0 | (0,2)  |           2 |       2048 | {"\\x01000000","\\x02000000"}
(2 rows)
Run Code Online (Sandbox Code Playgroud)
  • 同样,第一个元组是死元组。

    t_infomask2是 2 plus HEAP_KEYS_UPDATED(这是一个删除或更新的元组),并且t_infomaskHEAP_XMIN_COMMITTED(元组在被删除之前是有效的)。

  • 第二个元组是插入的元组:

    t_infomask2是 2 加,并且t_infomaskHEAP_XMAX_INVALID(它是 0),所以这是一个新的元组。

观察差异的解释:

READ COMMITTED隔离级别,事务总是看到行的最新提交版本。的DELETE会话B具有锁定的行和由堵塞UPDATEDELETE在会话A.

文档解释了释放锁时会发生什么:

UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE命令的行为与SELECT在搜索目标行方面:他们只会找到在命令开始时提交的目标行。但是,这样的目标行在找到时可能已经被另一个并发事务更新(或删除或锁定)。在这种情况下,潜在的更新者将等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新程序回滚,那么它的效果就无效了,第二个更新程序可以继续更新最初找到的行。如果第一个更新程序提交,第二个更新程序将在第一个更新程序删除该行时忽略该行,否则它将尝试将其操作应用于该行的更新版本。命令的搜索条件(WHERE子句)被重新评估以查看该行的更新版本是否仍然匹配搜索条件。如果是,则第二个更新程序使用该行的更新版本继续其操作。

UPDATE旧行版本和新行版本之间存在链接的情况下,因此PostgreSQL锁定并删除新行版本,而在DELETE+的情况下,INSERT锁消失后没有行的有效版本,什么也没有被删除。

因此,虽然在许多方面UPDATE和PostgreSQL 中的DELETE+INSERT非常相似,但它们并不相同:在第二种情况下,删除行和插入行之间没有联系。

附录:的意义infomaskinfomask2

t_infomask

SELECT lp, t_xmin, t_xmax, t_ctid, t_infomask2, t_infomask, t_attrs
FROM heap_page_item_attrs(get_raw_page('example', 0), 'example');

 lp | t_xmin | t_xmax | t_ctid | t_infomask2 | t_infomask |            t_attrs            
----+--------+--------+--------+-------------+------------+-------------------------------
  1 | 380943 | 380944 | (0,2)  |       16386 |        256 | {"\\x01000000","\\x02000000"}
  2 | 380944 |      0 | (0,2)  |       32770 |      10240 | {"\\x01000000","\\x02000000"}
(2 rows)
Run Code Online (Sandbox Code Playgroud)

t_infomask2

SELECT lp, t_xmin, t_xmax, t_ctid, t_infomask2, t_infomask, t_attrs
FROM heap_page_item_attrs(get_raw_page('example', 0), 'example');

 lp | t_xmin | t_xmax | t_ctid | t_infomask2 | t_infomask |            t_attrs            
----+--------+--------+--------+-------------+------------+-------------------------------
  1 | 380958 | 380961 | (0,1)  |        8194 |        256 | {"\\x01000000","\\x02000000"}
  2 | 380961 |      0 | (0,2)  |           2 |       2048 | {"\\x01000000","\\x02000000"}
(2 rows)
Run Code Online (Sandbox Code Playgroud)