使用标准SQL有效地检测并发插入

时间:2011-08-15 13:59:13

标签: sql sql-server oracle postgresql db2

要求

我有一个下表(伪DDL):

CREATE TABLE MESSAGE (
    MESSAGE_GUID GUID PRIMARY KEY,
    INSERT_TIME DATETIME
)

CREATE INDEX MESSAGE_IE1 ON MESSAGE (INSERT_TIME);

多个客户端同时在该表中插入行,可能每秒多次。我需要设计一个“Monitor”应用程序,它将:

  1. 最初,获取当前表中的所有行。
  2. 之后,定期检查是否插入了新行,然后进行提取 这些行
  3. 可能有多个监视器同时运行。所有监视器都需要查看所有行(即插入行时,所有当前正在运行的监视器都必须“检测到”。

    这个应用程序最初是为Oracle开发的,但是我们需要让它可以移植到每个主要的RDBMS,并且希望避免尽可能多的数据库特定的东西。

    问题

    天真的解决方案是简单地在步骤1中选择的行中找到最大的INSERT_TIME然后......

    SELECT * FROM MESSAGE WHERE INSERT_TIME >= :max_insert_time_from_previous_select
    

    ...在第2步中。

    然而,我担心这可能导致竞争条件。请考虑以下情形:

    1. 事务A插入一个新行,但 尚未提交。
    2. 事务B插入一个新行并提交。
    3. Monitor 选择行并查看最大INSERT_TIME 是由B插入的那个。
    4. 交易A提交。此时,实际上是A的INSERT_TIME 早于比B(之前实际执行过A的INSERT) B,在我们知道谁将首先承诺之前。
    5. Monitor 选择比B的INSERT_TIME更新的行(作为步骤3的结果)。由于A的INSERT_TIME早于B的插入时间,因此跳过A的行。
    6. 因此,事务A插入的行永远不会被提取。

      任何想法如何设计客户端SQL甚至更改数据库模式(只要它可以轻松移植),这样可以避免这些类型的并发问题,同时仍能保持良好的性能?

      感谢。

4 个答案:

答案 0 :(得分:2)

不使用任何特定于平台的变更数据捕获(CDC)技术,有几种方法。

选项1

每个 Monitor 都会注册MESSAGE表的一种订阅。写入消息的代码然后每 Monitor 写一次MESSAGE,即

CREATE TABLE message_subscription (
  message_subscription_id NUMBER PRIMARY KEY,
  message_id RAW(32) NOT NULLL,
  monitor_id NUMBER NOT NULL,
  CONSTRAINT uk_message_sub UNIQUE (message_id, monitor_id)
);

INSERT INTO message_subscription
  SELECT message_subscription_seq.nextval,
         sys_guid,
         monitor_id
    FROM monitor_subscribers;

每个 Monitor 然后在处理后从其订阅中删除该消息。

选项2

每个 Monitor 维护一个已处理的最近消息的缓存,该缓存至少与运行时间最长的事务一样长。例如,如果 Monitor 维护了它在过去5分钟内处理过的消息的缓存,它将在MESSAGE表中查询所有消息晚于LAST_MONITOR_TIME的消息。然后, Monitor 将负责注意它已选择的某些行已被处理。 Monitor 只会处理不在其缓存中的MESSAGE_ID值。

选项3

就像选项1一样,您为每个 Monitor 设置订阅,但您使用一些排队技术将消息传递到 Monitor 。这比其他两个选项的可移植性差,但大多数数据库可以通过某种类型的队列向应用程序传递消息(例如,如果 Monitor 是Java应用程序,则为JMS队列)。通过构建自己的队列表,您可以避免重新发明轮子,并在应用程序层中为您提供标准接口以进行编码。

答案 1 :(得分:0)

您需要能够识别自上次检查后添加的所有行(即监视器检查)。您有一个“插入时间”列。但是,当您拼写出来时,插入列的时间不能与“大于[最后检查]”逻辑一起使用,以可靠地识别随后插入的新项目。提交不会以(初始)插入的顺序出现。我不知道任何适用于所有主要RDBMS的东西,它们会在提交的实际时间清楚安全地应用这样的“as of”标记。 [ 而不是“大于最后检查”算法。

这会导致过滤。插入后,项目(行)被标记为“尚未检查”;当montior登录时,它会读取所有尚未检查的项目,返回该组,并将标志翻转为“已检查”(如果有多个监视器,则必须在其自己的事务中完成)。监视器的查询必须读取所有数据并挑选尚未检查的数据。然而,这意味着这将是一组相当小的数据,至少相对于整个数据集。从这里,我看到两个可能的选择:

  • 添加一列,也许是“已选中”。存储二进制1/0值以进行is / isnot检查。这个值的基数将是极端的 - 99.9s Checked,00,0s Unchecked,所以它应该是相当有效的。 (有些RDBMS提供过滤查询,这样Checked行甚至不会在索引中;一旦翻转检查,一行可能永远不会被翻转,所以支持这一行的开销不应该太大。)< / LI>
  • 添加一个单独的表,标识“主”表中尚未检查的那些行。当montior登录时,它会读取和删除该表中的项目。这似乎并不高效......但同样,如果涉及的数据集很小,整体性能上的痛苦可能是可以接受的。

答案 2 :(得分:0)

您应该将Oracle AQ与多用户队列一起使用。

这是特定于Oracle的,但您可以创建存储过程的抽象层(或者如果您愿意,可以使用Java抽象),这样您就可以使用通用API来排队新消息,并让每个订阅者(监视器)将任何待处理消息排队。在该API的背后,对于Oracle,您使用AQ。

我不确定是否有其他数据库的排队解决方案。

我认为您无法提出满足您要求的完全数据库不可知方法。您可以扩展上面包含“已检查”列的示例,以使第二个表名为monitor_checked,每个监视器每个消息包含一行。这基本上就是AQ在幕后所做的事情,所以它重新发挥了作用。

答案 3 :(得分:0)

使用PostgreSQL,使用PgQ。它为您准备了所有这些小细节。

我怀疑你会找到一个强大且可管理的数据库无关解决方案。