仅将更新的行发送到客户端

时间:2014-11-05 08:23:34

标签: postgresql time transactions

我想创建一个允许客户端获取表中所有行的Web服务,然后允许客户端仅获取新行或更新的行。

最简单的实现似乎是将当前时间戳发送到客户端,然后让客户端请求比以下请求中的时间戳更新的行。

通过保持" updated_at"这似乎是可行的。在更新和插入触发器中将时间戳设置为NOW()的列,然后查询较新的行,并传递NOW()的值。

问题是如果有未提交的交易,这些交易会将updated_at设置为交易的开始时间,而不是提交时间

因此,这个简单的实现不起作用,因为行可能会丢失,因为它们可能会出现过去的时间戳。

我一直无法找到解决这个问题的简单方法,尽管这似乎是一个非常普遍的需求:任何想法?

可能的解决方案:

  1. 在表中保留单调时间戳,在每个事务开始时将其更新为MAX(NOW(),last_timestamp + 1)并将其用作行时间戳。问题:这实际上意味着所有写入事务都是完全序列化的并锁定整个数据库,因为它们在更新时间表上冲突。

  2. 在事务结束时,将NOW()的映射添加到更新表中的时间,如上述解决方案。这似乎需要采用显式锁定并使用序列来生成非时间"时间戳"因为在单行上使用UPDATE会导致SERIALIZABLE模式下的回滚。

  3. 以某种方式让PostgreSQL在提交时迭代所有更新的行并将updated_at设置为单调时间戳

  4. 以某种方式让PostgreSQL本身维护一个事务提交时间表,目前它似乎没有这样做

  5. 使用内置的xmin列似乎也不可能,因为VACUUM可以删除它。

    能够在数据库中执行此操作而不修改应用程序中的所有更新将是很好的。

    通常的做法是什么?

    天真解决方案的问题

    如果不明显,这就是使用NOW()或CLOCK_TIMESTAMP()的问题:

    1. 在时间1,我们在一个事务中运行NOW()或CLOCK_TIMESTAMP(),它给出1并且我们将行设置时间1更新为更新时间
    2. 在时间2,客户端获取所有行,我们告诉他我们已将所有行都提供给时间2
    3. 在时间3,交易提交"时间1"在updated_at字段中
    4. 客户端要求更新的行自时间2(他从上一次完整提取请求获得的时间),我们查询updated_at> = 2并且我们不返回任何内容,而不是返回刚刚添加的行
    5. 该行丢失,客户端永远不会看到

2 个答案:

答案 0 :(得分:1)

你的整个命题违背了一些符合ACID标准的RDBMS(如PostgreSQL)的基本原理。交易开始时间(例如current_timestamp())和其他基于时间的指标作为特定客户接收或不接收的指标毫无意义。放弃整个想法。

假设您的客户端通过持久会话连接到数据库,您可以按照以下步骤操作:

  • 会话开始时,会话用户CREATE TEMP UNLOGGED TABLE。此表只包含PK以及要从中获取数据的表的上次更新时间。
  • 客户端轮询新数据,并仅接收临时表中尚未存在PK或现有PK但较新的上次更新时间的记录。当前未提交的事务是不可见的,但将在下次轮询时检索新的或更新的记录。更新时间是必需的,因为无法从所有并发客户端的临时表中删除记录。
  • 检索记录的PK和上次更新时间存储在临时表中。
  • 当用户关闭会话时,临时表将被删除。

如果您希望在每个客户端的多个会话中保留检索到的记录,或者客户端在每次查询后断开连接,那么您需要一个常规表但是我建议还添加用户的oid以便所有用户都可以使用用于跟踪检索到的记录的单个表。在后一种情况下,您可以在表上创建一个AFTER UPDATE触发器,其中包含您的数据,该数据会从一个扫描中的所有用户中删除带有已获取记录的表中的PK。在他们的下一轮调查中,客户将获得更新的记录。

答案 1 :(得分:1)

添加一列,用于跟踪已发送给客户的记录:

alter table table_under_view
  add column access_order int null;

create sequence table_under_view_access_order_seq
  owned by table_under_view.access_order;

create function table_under_view_reset_access_order()
  returns trigger
  language plpgsql
as $func$
  new.access_order := null;
$func$;

create trigger table_under_view_reset_access_order_before_update
  before update on table_under_view
  for each row execute procedure table_under_view_reset_access_order();

create index table_under_view_access_order_idx
  on table_under_view (access_order);

create index table_under_view_access_order_where_null_idx
  on table_under_view (access_order)
  where (access_order is null);

(您也可以使用before insert on table_under_view触发器,以确保只将NULL值插入access_order)。

您需要在与INSERT s&的交易后更新此列。此表上的UPDATE已完成,但在任何客户端查询您的数据之前。在事务完成后,只能执行任何操作,所以让我们在查询发生之前执行此操作。您可以使用函数f.ex执行此操作:

create function table_under_access(from_access int)
  returns setof table_under_view
  language sql
as $func$
  update table_under_view
  set    access_order = nextval('table_under_view_access_order_seq'::regclass)
  where  access_order is null;

  select *
  from   table_under_view
  where  access_order > from_access;
$func$;

现在,您的第一个“数据块”(将获取表中的所有行),如下所示:

select *
from   table_under_access(0);

此后的关键元素是您的客户端需要处理数据的每个“块”以确定它最后得到的最大access_order(除非您将其包含在具有f.ex.窗口函数的结果中) ,但如果你要处理结果 - 这似乎很有可能 - 你不需要那样做。始终将其用于后续呼叫。

如果您愿意,也可以添加updated_at列来订购结果。

您还可以使用视图+ rule(s)作为最后一部分(而不是函数),以使其更透明。