SQL - 将密钥并发插入表中

时间:2011-02-17 07:44:04

标签: sql delphi concurrency firebird auto-increment

可能是一个微不足道的问题,但我希望得到最好的解决方案。

问题:

我有两个或更多工作人员将密钥插入一个或多个表中。当两个或多个工作人员试图同时将同一个密钥插入其中一个密钥表时,就会出现问题。 典型的问题。

  1. 如果存在密钥(SELECT),Worker A将读取该表。没有钥匙。
  2. 如果存在密钥(SELECT),Worker B将读取该表。没有钥匙。
  3. 工人A插入密钥。
  4. 工人B插入密钥。
  5. 工人A提交。
  6. 工人B提交。违反唯一约束时抛出异常
  7. 关键表是简单的对。第一列是自动递增整数,第二列是varchar键。

    这种并发问题的最佳解决方案是什么?我认为这是一个常见的问题。一种可靠的方法是处理抛出的异常,但我不相信这是解决这个问题的最佳方法。

    我使用的数据库,如果Firebird 2.5

    修改

    一些其他信息可以说清楚。

    1. 客户端同步不是一种好方法,因为插入来自不同的进程(工作者)。有一天,我可以让不同机器上的工作人员,所以即使互斥也是不行的。
    2. 此类表的主键和第一列是自动增量字段。没问题。 varchar字段是问题,因为它是客户端插入的内容。
    3. 典型的此类表是用户表。例如:

      1  2056
      2  1044
      3  1896
      4  5966
      ...
      

      每个工作人员检查用户“xxxx”是否存在,如果不存在,则

      编辑2

      仅供参考,如果有人会走相同的路线。 IB / FB返回错误代码对(我正在使用InterBase Express组件)。检查重复值违规是这样的:

      except
        on E: EIBInterBaseError do
        begin
          if (E.SQLCode = -803) and (E.IBErrorCode = 335544349) then
          begin
            FKeysConnection.IBT.Rollback;
            EnteredKeys := False;
          end;
        end;
      end;
      

6 个答案:

答案 0 :(得分:6)

使用Firebird,您可以使用以下语句:

UPDATE OR INSERT INTO MY_TABLE (MY_KEY) VALUES (:MY_KEY) MATCHING (MY_KEY) RETURNING MY_ID
  • 假设有一个BEFORE INSERT触发器,如果​​插入了NULL值,将生成MY_ID。

这是documentation

更新:上述语句将避免异常并导致每个语句成功。但是,如果有许多重复的键值,它也会导致许多不必要的更新。 这可以通过另一种方法来避免:只需处理客户端上的唯一约束异常并忽略它。详细信息取决于您使用哪个Delphi库来处理Firebird,但是应该可以检查服务器返回的SQLCode,并且只忽略唯一约束违规的特定情况。

答案 1 :(得分:2)

我不知道Firebird中是否有这样的东西,但在SQL Server中你可以在插入密钥时检查。

insert into Table1 (KeyValue) 
select 'NewKey'
where not exists (select *
                  from Table1
                  where KeyValue = 'NewKey')

答案 2 :(得分:2)

第一个选项 - 不要这样做。

不要这样做;除非工人正在做大量的工作(我们谈论的是计算机,因此每个记录需要1秒才有资格作为“特殊工作量”),所以只需使用一个线程;更好的是,在存储过程中完成所有工作,您会惊讶于不通过任何协议将数据传输到您的应用程序中所获得的加速。

第二个选项 - 使用队列

确保您的工作线程并非全部使用相同的ID。设置队列,将需要处理的所有ID推送到该队列,让每个工作线程从该队列中取出ID。这样你就可以保证没有两个工人同时在同一个记录上工作。如果您的员工不是同一个流程的所有成员,那么这可能很难实施。

最后的手段

设置基于数据库的“预订”系统,以便工作人员线程可以标记“正在进行中”的密钥,这样就不会有两个工作人员在同一个密钥上工作。我建立了一个这样的表:

CREATE TABLE KEY_RESERVATIONS (
  KEY INTEGER NOT NULL, /* This is the KEY you'd be reserving */
  RESERVED_UNTIL TIMESTAMP NOT NULL /* We don't want to keep reservations for ever in case of failure */
);

您的每个工作人员都会使用简短的交易来处理该表:选择一个候选键,一个不在KEY_RESERVATIONS表中的键。尝试INSERT。失败?尝试其他KEY。使用旧的RESERVED_UNTIL时间戳定期删除所有保留密钥。确保使用KEY_RESERVATIONS的事务尽可能短,这样两个尝试同时保留相同密钥的线程都会很快失败。

答案 3 :(得分:1)

这是你必须在乐观(或没有)锁定方案中处理的事情。

避免它的一种方法是在整个select,insert,commit序列周围对表进行悲观锁定。

但是,这意味着您将不得不处理无法访问该表(处理表锁定异常)。

如果工作者是指同一应用程序实例中的线程而不是不同的用户(应用程序实例),则需要线程同步,如kubal5003在select-insert-commit序列周围说。

如果您有多个具有多个线程的用户/应用程序实例,则需要两者的组合。

答案 4 :(得分:0)

同步你的线程使得无法插入相同的值或使用数据库侧密钥生成方法(我不知道Firebird所以我甚至不知道它是否存在,例如在MsSQL Server中有标识列或GUID也解决了这个问题,因为它不太可能产生两个相同的问题)

答案 5 :(得分:0)

如果有重复的可能性,您不应该依赖客户端生成唯一密钥。

使用触发器和生成器(可能借助存储过程)创建始终唯一的键。

有关Firebird中正确实现autoinc的更多信息,请访问:http://www.firebirdfaq.org/faq29/