我的理解是更新锁定一个元组,将其标记为已删除,然后添加一个新元组。
换句话说,更新 = 删除 + 插入。
至少我是这么相信的。但它似乎与 MVCC 中的 delete+insert 的更新有根本的不同。
设置:
CREATE TABLE example (a int PRIMARY KEY, b int);
INSERT INTO example VALUES (1, 1);
方法一:更新
-- 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)
方法二:删除插入
-- 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)
因此
UPDATE example SET b = 2 WHERE a = 1;
不同于
DELETE FROM example WHERE a = 1;
INSERT INTO example VALUES (1, 2);
我如何理解更新的 MVCC 本质?元组是否具有在更新期间保留的某种 MVCC“身份”?它是什么?
UPDATE
是的,和DELETE
+之间有区别INSERT
。让我们使用
pageinspect
扩展来查看元组和元组标头。如果你想重复我的实验,你必须在两者之间删除并重新创建表。此外,如果您在检查行之前选择了行,则可能会有其他标志(提示位)。
infomask2
和的含义infomask
可以在 中找到src/include/access/htup_details.h
,请参阅答案末尾的引文。之后
UPDATE
:第一个元组是死元组。它
t_ctid
已更改为指向更新的版本。这是关键点之一,所以让我扩展一下:
ctid
元组的 是块号和“行指针”(lp
在查询结果中的组合。t_ctid
通常是多余的,但在这种情况下它用于指向新的行版本。这是原始元组和更新版本之间的链接。t_infomask2
是 2(列数)加上HEAP_HOT_UPDATED
,所以这一行收到了HOT 更新(块中有足够的空间,并且没有索引)。t_infomask
是HEAP_XMIN_COMMITTED
(提示位)。第二个元组是新版本。
t_infomask2
是 2 plusHEAP_ONLY_TUPLE
,所以这是“仅堆元组”,只能通过ctid
旧版本的更新访问。t_infomask
是HEAP_XMAX_INVALID
(真的,它是 0)加上HEAP_UPDATED
(这是更新版本)。DELETE
+之后INSERT
:同样,第一个元组是死元组。
t_infomask2
is 2 plusHEAP_KEYS_UPDATED
(这是一个删除或更新的元组),并且t_infomask
isHEAP_XMIN_COMMITTED
(元组在被删除之前是有效的)。第二个元组是插入的:
t_infomask2
is 2 plus,t_infomask
isHEAP_XMAX_INVALID
(it is 0), 所以这是一个新的元组。观察到的差异的解释:
在
READ COMMITTED
隔离级别,事务总是看到行的最新提交版本。会话 B 中的该DELETE
行必须锁定该行并被会话 A 中的UPDATE
or阻塞。DELETE
该文档解释了释放锁时会发生什么:
UPDATE
在新旧行版本之间有联系的情况下,PostgreSQL 锁定并删除新行版本,而在DELETE
+的情况下,INSERT
锁消失后该行没有有效版本,什么都没有被删除。因此,虽然在许多方面
UPDATE
和DELETE
+INSERT
在 PostgreSQL 中非常相似,但它们并不相同:在第二种情况下,删除的行和插入的行之间没有联系。附录:和的
infomask
含义infomask2
t_infomask
:t_infomask2
: