2014-11-05 76 views
2

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

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

看起来,通过在更新和插入触发器中保留“updated_at”列的时间戳设置为NOW(),然后查询更新的行并传递NOW()值,可行。

的问题是,如果有未提交的事务,这些事务将设置的updated_at到事务的开始时间,而不是提交时间

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

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

可能的解决方案:

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

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

  3. 不知何故已经PostgreSQL的,在提交时,遍历所有更新的行和设置的updated_at为单调时间戳

  4. 不知怎的,有PostgreSQL的本身维护事务的表提交时间,这似乎不这样做此刻

使用内置的xmin列似乎也是不可能的,因为VACUUM可以垃圾它。

如果不修改应用程序中的所有更新,就可以在数据库中执行此操作,这将是非常好的。

这是做什么通常的方式?

与天真的解决方案

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

  1. 在时间1,我们现在运行( )或CLOCK_TIMESTAMP()在交易中给出1,并且我们更新行设置时间1作为更新时间
  2. 在时间2,客户端获取所有行,并且我们告诉他我们给它所有行,直到时间2
  3. 在时间3,事务在updated_at字段中提交“时间1”
  4. 客户机要求自从时间2(他从前一个完全读取请求获得的时间)后更新的行,我们查询updated_at> = 2,并且我们返回任何结果,而不是返回,这只是增加
  5. 该行损失绝不会为
+0

那仍​​然不是提交时间,所以它不会工作。即在调用clock_timestamp()后,事务可能会提交一秒,如果客户端在此期间更新,则更新将丢失。 – 2014-11-05 08:30:26

+0

由于相同的原因,这不起作用(除非您在锁定下进行)。 – 2014-11-05 08:44:54

+0

遇到这个确切的问题,寻找答案。 – 2015-01-25 07:22:40

回答

1

你的整个主张违背了一些兼容ACID的RDBMS如PostgreSQL的基本面的客户端看到的行。交易开始时间(例如current_timestamp())和其他基于时间的度量标准作为衡量特定客户端是否收到的内容没有意义。放弃整个想法。

假设你的客户,通过持续性会话连接数据库,你可以遵循以下步骤:

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

如果你想每个查询后,持续在每个客户端的多个会话或客户端断开连接检索到的记录,那么你需要一个普通表,但后来我会建议还补充,使得所有用户的OID用户可以使用单个表来跟踪检索到的记录。在后一种情况下,您可以使用您的数据在表格上创建一个AFTER UPDATE触发器,以便在一次扫描中为所有用户删除具有提取记录的表中的PK。在他们的下一次民意调查中,客户将获得更新的记录。

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); 

的在这之后的关键元素是你的客户需要处理每一块“数据”,以确定它最后得到的是哪个最大(除非你用f.ex.window函数将它包含在结果中,但是如果你打算处理结果 - 这似乎很有可能 - 你不需要)。始终将其用于后续调用。

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

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

相关问题