首页 > 解决方案 > 使用 txid 获取最新的未处理、更新的行

问题描述

我的 PostgreSQL 中有一个表(实际上是它的多个表,但为了简单起见,我们假设它只有一个)和多个需要定期查询表以查找更改项的客户端。这些是更新或插入的项目(已删除项目的处理方式是首先将它们标记为删除,然后在宽限期后实际删除它们)。

现在显而易见的解决方案是只为每一行保留一个“修改过的”时间戳列,为每个客户端记住它,然后简单地获取更改的时间戳列

SELECT * FROM the_table WHERE modified > saved_modified_timestamp;

然后,修改后的列将使用以下触发器保持最新:

CREATE FUNCTION update_timestamp()
    RETURNS trigger
    LANGUAGE ‘plpgsql’
AS $$
    BEGIN
        NEW.modified = NOW();
        RETURN NEW;
    END;
$$;

CREATE TRIGGER update_timestamp_update
    BEFORE UPDATE ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_timestamp();

CREATE TRIGGER update_timestamp_insert
    BEFORE INSERT ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_timestamp();

这里明显的问题是NOW()交易开始的时间。因此,可能会发生事务在获取更新的行时尚未提交,并且在提交时,时间戳低于 saved_modified_timestamp,因此永远不会注册更新。

我想我找到了一个可行的解决方案,我想看看你是否能找到这种方法的任何缺陷。

基本思想是使用 xmin (或者更确切地说txid_current())而不是时间戳,然后在获取更改时,将它们包装在显式事务中,REPEATABLE READ并从事务中读取txid_snapshot()(或更确切地说是它包含的三个值txid_snapshot_xmin(), txid_snapshot_xmax(), txid_snapshot_xip())。

如果我正确阅读了 postgres 文档,那么所有更改为 <txid_snapshot_xmax()而不是 in 的事务都txid_snapshot_xip()应该在该 fetch 事务中返回。然后,此信息应该是再次获取时获取所有更新行所需的全部信息。然后选择将如下所示,xmin_version替换modified列:

SELECT * FROM the_table WHERE
   xmin_version >= last_fetch_txid_snapshot_xmax OR xmin_version IN last_fetch_txid_snapshot_xip;

触发器将如下所示:

CREATE FUNCTION update_xmin_version()
    RETURNS trigger
    LANGUAGE ‘plpgsql’
AS $$
    BEGIN
        NEW.xmin_version = txid_current();
        RETURN NEW;
    END;
$$;

CREATE TRIGGER update_timestamp_update
    BEFORE UPDATE ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_xmin_version();

CREATE TRIGGER update_timestamp_update_insert
    BEFORE INSERT ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_xmin_version();

这行得通吗?还是我错过了什么?

标签: sqlpostgresqlmvcc

解决方案


感谢您对 64 位返回txid_current()以及时代如何翻转的澄清。很抱歉,我将那个时代计数器与时间时代混淆了。

我看不出您的方法有任何缺陷,但会通过实验验证,在获取txid_snapshot_xip()快照的可重复读取事务中同时具有多个客户端会话不会导致任何问题。

我不会在实践中使用这种方法,因为我假设客户端代码需要解决处理相同更改的重复读取(插入/更新/删除)以及数据库内容和客户端工作集之间的定期协调来处理由于通信故障或客户端崩溃而漂移。一旦编写了该代码,然后now()在客户端跟踪表、clock_timestamp()触发器中使用,以及客户端拉取变更集时的宽限间隔重叠将适用于我遇到的用例。

如果要求比这更强大的实时完整性,那么我会推荐分布式提交策略。


推荐阅读