免责声明:理论问题.

这里有几个问题是关于如何区分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-插入了行.

在国际海事组织看来,xminxmaxhere的含义解释得不太清楚.

有没有可能把逻辑建立在这些列的基础上?关于系统列(除了源代码)还有什么更重要的解释吗?

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

推荐答案

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

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

RETURNING (xmax = 0) AS inserted

现在详细解释如下:

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

xmax有两个不同的用途:

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

  2. xmax is also used to store row locks. In PostgreSQL, row locks are not stored in the lock table, but in the tuple to avoid overflow of the lock table.
    If only one transaction has a lock on the row, xmax will contain the transaction ID of the locking transaction. If more than one transaction has a lock on the row, xmax contains the number of a so-called multixact, which is a data structure that in turn contains the transaction IDs of the locking transactions.

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)

t_infomaskt_infomask2的含义可以在src/include/access/htup_details.h中找到.lp_off是页面中的元组数据的偏移量,t_ctid是由页面编号和页面内的元组编号组成的current tuple ID.由于该表是新创建的,所以所有数据都在第0页.

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

  1. line pointer(lp)1处,我们找到了旧的、更新的元组.它最初有ctid = (0,1)个,但在更新过程中被修改为包含当前版本的元组ID.该元组由事务INSERT ... ON CONFLICT507创建,并由事务INSERT ... ON CONFLICT508(发出INSERT ... ON CONFLICT的事务)失效.这个元组不再可见,将在VACUUM期间删除.

    t_infomask显示xminxmax都属于提交的事务,因此显示元组的创建和删除时间.t_infomask2显示元组更新为热(heap only tuple)更新,这意味着更新后的元组与原始元组位于同一页面,并且没有修改索引列(参见src/backend/access/heap/README.HOT).

  2. 在第2行,我们看到了事务INSERT ... ON CONFLICT(事务102508)创建的新的、更新的元组.

    t_infomask表示该元组是更新的结果,xmin表示有效,xmax表示包含KEY SHARE行锁(自事务完成以来,该锁不再相关).该行锁是在INSERT ... ON CONFLICT处理期间获取的.t_infomask2表明这是一个热元组.

  3. 在第3行,我们看到新插入的行.

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

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

Postgresql相关问答推荐

Postgs SQL用于比较两个表之间的数据并填充到目标中

org.postgresql. util.PSQLException:使用docker + docker—compose + kafka + springboot时try 连接失败

创建发布性能

无法在PostgreSQL中创建方案和表

在PostgreSQL中是否有与SQLite R*Tree类似功能?

如何在 postgres where 子句中使用 or 对条件进行组合或分组

对 Postgres 进行身份验证时如何使用自定义密码哈希算法?

PostgreSQL - 列出模式中的所有唯一约束

PostgreSql maintenance_work_mem 在索引创建期间增加

转换数组类型

是否可以在 Postgres 中存储一个 1 字节的数字?

查询仅属于特定部门的用户

plpgsql:使用 2 个 OUT 参数调用函数

安装了 Postgres.app 但它不起作用

PostgreSQL 9 在 Windows 上安装:Unable to write inside TEMP environment path.

处理用户发送的string contains null byte

GRANT SELECT 特权使用一个语句对所有序列

如何在不丢失openproject数据的情况下将postgresql数据库从10升级到12

重命名 Amazon RDS 主用户名

Rails 5 db 迁移:如何修复 ActiveRecord::ConcurrentMigrationError