PostgreSQL Upsert 使用系统列 XMIN、XMAX 和其他区分插入和更新的行
PostgreSQL Upsert differentiate inserted and updated rows using system columns XMIN, XMAX and others
免责声明:理论问题。
这里有几个问题是关于如何在 PostgreSQL upsert
语句中区分插入和更新的行。
这是一个简单的例子:
create table t(i int primary key, x int);
insert into t values(1,1);
insert into t values(1,11),(2,22)
on conflict(i) do update set x = excluded.i*11
returning *, xmin, xmax;
╔═══╤════╤══════╤══════╗
║ i │ x │ xmin │ xmax ║
╠═══╪════╪══════╪══════╣
║ 1 │ 11 │ 7696 │ 7696 ║
║ 2 │ 22 │ 7696 │ 0 ║
╚═══╧════╧══════╧══════╝
因此,xmax
> 0(或 xmax
= xmin
)- 行已更新; xmax
= 0 - 已插入行。
IMO xmin
和 xmax
列 here.
的含义解释得不是很清楚
是否可以将逻辑基于这些列?系统列有没有更有意义的解释(源代码除外)?
最后我对 updated/inserted 行的猜测是否正确?
我认为这是一个值得深入回答的有趣问题;文章有点长,请多多包涵
简而言之:您的猜测是正确的,您可以使用下面的RETURNING
子句来确定该行是否被插入并且没有被更新:
RETURNING (xmax = 0) AS inserted
现在详细解释:
更新一行时,PostgreSQL 不会修改数据,而是创建该行的新版本;当不再需要时,旧版本将被 autovacuum 删除。行的一个版本称为 元组,因此在 PostgreSQL 中每行可以有多个元组。
xmax
有两个不同的用途:
如文档中所述,可以是删除(或更新)元组的 t运行saction 的 t运行saction ID(“元组”是“行”的另一个词)。只有 t运行saction ID 在 xmin
和 xmax
之间的 t运行saction 可以看到元组。如果不存在 t运行saction ID 小于 xmax
.
的 t运行saction,则可以安全地删除旧元组
xmax
也用于存储行锁。在 PostgreSQL 中,行锁不存储在锁 table 中,而是存储在元组中以避免锁 table.
溢出
如果只有一个 t运行saction 对该行有锁,xmax
将包含锁定 t运行saction 的 t运行saction ID。如果不止一个 t运行saction 对行有锁,xmax
包含所谓的 multixact 的编号,这是一种数据结构,在turn 包含锁定 t运行sactions.
的 t运行saction ID
xmax
的文档不完整,因为这个字段的确切含义被认为是一个实现细节,如果不了解元组的 t_infomask
就无法理解,这不是通过SQL.
您可以安装 contrib 模块 pageinspect
来查看此字段和元组的其他字段。
我 运行 你的例子,这是我在使用 heap_page_items
函数检查细节时看到的(t运行saction ID 号当然与我的不同案例):
SELECT *, ctid, xmin, xmax FROM t;
┌───┬────┬───────┬────────┬────────┐
│ i │ x │ ctid │ xmin │ xmax │
├───┼────┼───────┼────────┼────────┤
│ 1 │ 11 │ (0,2) │ 102508 │ 102508 │
│ 2 │ 22 │ (0,3) │ 102508 │ 0 │
└───┴────┴───────┴────────┴────────┘
(2 rows)
SELECT lp, lp_off, t_xmin, t_xmax, t_ctid,
to_hex(t_infomask) AS t_infomask, to_hex(t_infomask2) AS t_infomask2
FROM heap_page_items(get_raw_page('laurenz.t', 0));
┌────┬────────┬────────┬────────┬────────┬────────────┬─────────────┐
│ lp │ lp_off │ t_xmin │ t_xmax │ t_ctid │ t_infomask │ t_infomask2 │
├────┼────────┼────────┼────────┼────────┼────────────┼─────────────┤
│ 1 │ 8160 │ 102507 │ 102508 │ (0,2) │ 500 │ 4002 │
│ 2 │ 8128 │ 102508 │ 102508 │ (0,2) │ 2190 │ 8002 │
│ 3 │ 8096 │ 102508 │ 0 │ (0,3) │ 900 │ 2 │
└────┴────────┴────────┴────────┴────────┴────────────┴─────────────┘
(3 rows)
t_infomask
和t_infomask2
的含义可以在src/include/access/htup_details.h
中找到。 lp_off
是元组数据在页内的偏移量,t_ctid
是当前元组ID,由页码和页内元组号组成.由于 table 是新创建的,所有数据都在第 0 页。
让我讨论一下 heap_page_items
返回的三行。
在line pointer (lp
) 1 我们找到了旧的、更新的元组。它最初有 ctid = (0,1)
,但在更新期间被修改为包含当前版本的元组 ID。元组由 t运行saction 102507 创建并由 t运行saction 102508(发出 INSERT ... ON CONFLICT
的 t运行saction)无效。此元组不再可见,将在 VACUUM
.
期间被删除
t_infomask
显示 xmin
和 xmax
都属于已提交的 t运行 操作,因此显示元组的创建和删除时间。 t_infomask2
表明元组是用 HOT (heap only tuple) update 更新的,这意味着更新后的元组与原始元组在同一页并且没有索引列已修改(参见 src/backend/access/heap/README.HOT
)。
在行指针 2 处,我们看到由 t运行saction 和 INSERT ... ON CONFLICT
(t运行saction 102508)创建的新更新元组。 =52=]
t_infomask
表明这个元组是更新的结果,xmin
有效,xmax
包含一个KEY SHARE
行锁(不再相关因为 t运行saction 已经完成)。此行锁是在 INSERT ... ON CONFLICT
处理期间获取的。 t_infomask2
表明这是一个热元组。
在行指针 3 处我们看到新插入的行。
t_infomask
表示xmin
有效,xmax
无效。 xmax
设置为 0,因为此值始终用于新插入的元组。
所以更新行的非零 xmax
是由行锁引起的实现工件。可以想象,INSERT ... ON CONFLICT
有一天会被重新实现,从而改变这种行为,但我认为这不太可能。
免责声明:理论问题。
这里有几个问题是关于如何在 PostgreSQL upsert
语句中区分插入和更新的行。
这是一个简单的例子:
create table t(i int primary key, x int);
insert into t values(1,1);
insert into t values(1,11),(2,22)
on conflict(i) do update set x = excluded.i*11
returning *, xmin, xmax;
╔═══╤════╤══════╤══════╗
║ i │ x │ xmin │ xmax ║
╠═══╪════╪══════╪══════╣
║ 1 │ 11 │ 7696 │ 7696 ║
║ 2 │ 22 │ 7696 │ 0 ║
╚═══╧════╧══════╧══════╝
因此,xmax
> 0(或 xmax
= xmin
)- 行已更新; xmax
= 0 - 已插入行。
IMO xmin
和 xmax
列 here.
是否可以将逻辑基于这些列?系统列有没有更有意义的解释(源代码除外)?
最后我对 updated/inserted 行的猜测是否正确?
我认为这是一个值得深入回答的有趣问题;文章有点长,请多多包涵
简而言之:您的猜测是正确的,您可以使用下面的RETURNING
子句来确定该行是否被插入并且没有被更新:
RETURNING (xmax = 0) AS inserted
现在详细解释:
更新一行时,PostgreSQL 不会修改数据,而是创建该行的新版本;当不再需要时,旧版本将被 autovacuum 删除。行的一个版本称为 元组,因此在 PostgreSQL 中每行可以有多个元组。
xmax
有两个不同的用途:
如文档中所述,可以是删除(或更新)元组的 t运行saction 的 t运行saction ID(“元组”是“行”的另一个词)。只有 t运行saction ID 在
的 t运行saction,则可以安全地删除旧元组xmin
和xmax
之间的 t运行saction 可以看到元组。如果不存在 t运行saction ID 小于xmax
.
的 t运行saction IDxmax
也用于存储行锁。在 PostgreSQL 中,行锁不存储在锁 table 中,而是存储在元组中以避免锁 table.
溢出 如果只有一个 t运行saction 对该行有锁,xmax
将包含锁定 t运行saction 的 t运行saction ID。如果不止一个 t运行saction 对行有锁,xmax
包含所谓的 multixact 的编号,这是一种数据结构,在turn 包含锁定 t运行sactions.
xmax
的文档不完整,因为这个字段的确切含义被认为是一个实现细节,如果不了解元组的 t_infomask
就无法理解,这不是通过SQL.
您可以安装 contrib 模块 pageinspect
来查看此字段和元组的其他字段。
我 运行 你的例子,这是我在使用 heap_page_items
函数检查细节时看到的(t运行saction ID 号当然与我的不同案例):
SELECT *, ctid, xmin, xmax FROM t;
┌───┬────┬───────┬────────┬────────┐
│ i │ x │ ctid │ xmin │ xmax │
├───┼────┼───────┼────────┼────────┤
│ 1 │ 11 │ (0,2) │ 102508 │ 102508 │
│ 2 │ 22 │ (0,3) │ 102508 │ 0 │
└───┴────┴───────┴────────┴────────┘
(2 rows)
SELECT lp, lp_off, t_xmin, t_xmax, t_ctid,
to_hex(t_infomask) AS t_infomask, to_hex(t_infomask2) AS t_infomask2
FROM heap_page_items(get_raw_page('laurenz.t', 0));
┌────┬────────┬────────┬────────┬────────┬────────────┬─────────────┐
│ lp │ lp_off │ t_xmin │ t_xmax │ t_ctid │ t_infomask │ t_infomask2 │
├────┼────────┼────────┼────────┼────────┼────────────┼─────────────┤
│ 1 │ 8160 │ 102507 │ 102508 │ (0,2) │ 500 │ 4002 │
│ 2 │ 8128 │ 102508 │ 102508 │ (0,2) │ 2190 │ 8002 │
│ 3 │ 8096 │ 102508 │ 0 │ (0,3) │ 900 │ 2 │
└────┴────────┴────────┴────────┴────────┴────────────┴─────────────┘
(3 rows)
t_infomask
和t_infomask2
的含义可以在src/include/access/htup_details.h
中找到。 lp_off
是元组数据在页内的偏移量,t_ctid
是当前元组ID,由页码和页内元组号组成.由于 table 是新创建的,所有数据都在第 0 页。
让我讨论一下 heap_page_items
返回的三行。
在line pointer (
期间被删除lp
) 1 我们找到了旧的、更新的元组。它最初有ctid = (0,1)
,但在更新期间被修改为包含当前版本的元组 ID。元组由 t运行saction 102507 创建并由 t运行saction 102508(发出INSERT ... ON CONFLICT
的 t运行saction)无效。此元组不再可见,将在VACUUM
.t_infomask
显示xmin
和xmax
都属于已提交的 t运行 操作,因此显示元组的创建和删除时间。t_infomask2
表明元组是用 HOT (heap only tuple) update 更新的,这意味着更新后的元组与原始元组在同一页并且没有索引列已修改(参见src/backend/access/heap/README.HOT
)。在行指针 2 处,我们看到由 t运行saction 和
INSERT ... ON CONFLICT
(t运行saction 102508)创建的新更新元组。 =52=]t_infomask
表明这个元组是更新的结果,xmin
有效,xmax
包含一个KEY SHARE
行锁(不再相关因为 t运行saction 已经完成)。此行锁是在INSERT ... ON CONFLICT
处理期间获取的。t_infomask2
表明这是一个热元组。在行指针 3 处我们看到新插入的行。
t_infomask
表示xmin
有效,xmax
无效。xmax
设置为 0,因为此值始终用于新插入的元组。
所以更新行的非零 xmax
是由行锁引起的实现工件。可以想象,INSERT ... ON CONFLICT
有一天会被重新实现,从而改变这种行为,但我认为这不太可能。