我的 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();
这行得通吗?还是我错过了什么?