PostgreSQL Upsert使用系统列XMIN,XMAX等来区分插入和更新的行

Abe*_*sto 17 postgresql upsert postgresql-9.5

免责声明:理论问题.

这里有几个问题,询问如何在PostgreSQL upsert语句中区分插入和更新的行.

这是一个简单的例子:

nd@postgres=# create table t(i int primary key, x int);
nd@postgres=# insert into t values(1,1);
nd@postgres=# 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 ?
????????????????????????
Run Code Online (Sandbox Code Playgroud)

所以,xmax> 0(或xmax= xmin) - 行已更新; xmax= 0 - 插入了行.

国际海事组织它不是太清楚解释的含义xminxmax在这里.

是否可以将逻辑基于这些列?是否有关于系统列的更重要的解释(源代码除外)?

最后是我对更新/插入行的猜测是正确的吗?

Lau*_*lbe 35

我认为这是一个值得深入回答的有趣问题; 如果它有点长,请耐心等待.

简而言之:您的猜测是正确的,您可以使用以下RETURNING子句来确定是否插入了行而不是更新:

RETURNING (xmax = 0) AS inserted
Run Code Online (Sandbox Code Playgroud)

现在详细说明:

更新行时,PostgreSQL不会修改数据,但会创建该行的新版本 ; 当不再需要时,旧版本将被autovacuum删除.行的一个版本称为元组,因此在PostgreSQL中,每行可以有多个元组.

xmax 有两个不同的目的:

  1. 如文档中所述,它可以是删除(或更新)元组的事务的事务ID("元组"是"行"的另一个字).只有之间的交易ID交易xminxmax可以看到的元组.如果没有事务ID小于的事务,则可以安全地删除旧元组xmax.

  2. xmax也用于存储行锁.在PostgreSQL中,行锁不存储在锁表中,而是存储在元组中以避免锁表溢出.
    如果只有一个事务对该行具有锁定,xmax则将包含锁定事务的事务ID.如果多个事务对该行具有锁定,则xmax包含所谓的multixact的编号,这是一个数据结构,而该数据结构又包含锁定事务的事务ID.

文档xmax不完整,因为该字段的确切含义被视为实现细节,如果不知道t_infomask元组,则无法理解,而元组不能通过SQL立即看到.

您可以安装contrib模块pageinspect来查看元组的这个和其他字段.

我运行了你的例子,这是我在使用heap_page_items函数检查细节时所看到的(事务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)
Run Code Online (Sandbox Code Playgroud)

意义t_infomaskt_infomask2可以找到src/include/access/htup_details.h.lp_off是页面中元组数据的偏移量,t_ctid当前元组ID,它由页面中的页码和元组号组成.由于表是新创建的,因此所有数据都在第0页.

让我来讨论一下返回的三行heap_page_items.

  1. 线路指针(lp)1,我们发现旧的,更新的元组.它最初有ctid = (0,1),但在更新期间被修改为包含当前版本的元组ID.元组由事务102507创建,并由事务102508(发布事务的事务INSERT ... ON CONFLICT)无效.此元组不再可见,将在此期间删除VACUUM.

    t_infomask说明两者xminxmax属于提交的事务,因此在创建和删除的元组时显示.t_infomask2显示元组已使用HOT(仅堆元组)更新进行更新,这意味着更新的元组与原始元组位于同一页面中,并且未修改任何索引列(请参阅参考资料src/backend/access/heap/README.HOT).

  2. 在行指针2处,我们看到由事务INSERT ... ON CONFLICT(事务102508)创建的新的更新元组.

    t_infomask表明此元组是更新的结果,xmin是有效的,并且xmax包含KEY SHARE行锁(由于事务已完成,因此不再相关).在INSERT ... ON CONFLICT处理期间执行此行锁定.t_infomask2表明这是一个HOT元组.

  3. 在行指针3处,我们看到新插入的行.

    t_infomask显示xmin有效且xmax无效.xmax设置为0,因为此值始终用于新插入的元组.

因此,xmax更新行的非零是由行锁引起的实现工件.可以想象,INSERT ... ON CONFLICT有一天会重新实现这种行为,但我认为这不太可能.

  • 感谢您对系统列的详细解释。了解它在内部是如何工作的真的很有用。 (2认同)
  • 这是一个非常有趣的答案,值得更多的赞成. (2认同)