【问题标题】:Concurrent insert of keys into a table将键同时插入到表中
【发布时间】:2011-06-28 22:00:41
【问题描述】:

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

问题:

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

  1. 如果存在键 (SELECT),则工作程序 A 读取表。没有钥匙。
  2. 如果存在键 (SELECT),则 Worker B 读取表。没有钥匙。
  3. 工人 A 插入密钥。
  4. 工人 B 插入密钥。
  5. 工人 A 提交。
  6. 工人 B 提交。违反唯一约束时抛出异常

键表是简单的对。第一列是自增整数,第二列是 varchar 键。

这种并发问题的最佳解决方案是什么?我相信这是一个普遍的问题。肯定的一种方法是处理抛出的异常,但不知何故,我认为这不是解决这个问题的最佳方法。

我使用的数据库是 Firebird 2.5

编辑

一些额外的信息让事情变得清晰。

  1. 客户端同步不是一个好方法,因为插入来自不同的进程(工作者)。有朝一日,我可以让工作人员跨不同的机器,所以即使是互斥锁也是不行的。
  2. 此类表的主键和第一列是自增字段。那里没问题。 varchar 字段是问题所在,因为它是客户端插入的内容。

典型的此类表是用户表。例如:

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;

【问题讨论】:

  • 阅读答案我刚刚有了一个想法。我可以有另一个工作人员,其唯一目的是插入关键数据。其他工作人员将通过 IPC 或 TCP 将数据发送给它。似乎是一个很好的解决方案,也适用于多台计算机。我还是想看看你们想出了什么。
  • 所以用户试图插入一个键的重复值,该键被限制为唯一。他们会得到一个错误,这就是它应该的样子。有什么问题?
  • 这是自动化工人,他们背后没有用户。数据来自工作人员操作的大型“记录”表。他们会做一些报告。如果发生异常,尝试重复选择/插入也许是正确的做法。我只是想就此事获得所有可能的意见解决方案。

标签: sql delphi concurrency firebird auto-increment


【解决方案1】:

使用 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 并仅忽略违反唯一约束的特定情况。

【讨论】:

  • 确保线程不在同一个记录上工作更好:如果你有 10 个线程,他们都选择第一个 Key,解决问题,然后运行这个查询会发生什么:他们都成功了。然后他们都选择下一个可用的记录,并且他们都再次在同一条记录上工作,重复直到所有工作完成。你最终会完成所有工作 10 次,而不是工作速度快 10 倍。
  • @Cosmin Prund:我认为目标是他们都成功了,即。以确保记录以正确的键值存在。 “确保线程不在同一记录上工作”恕我直言不起作用(它们可能在不同的计算机上运行)。
  • 目标是确保所有不同的键只插入一次到键表中。不同的工作人员可能会尝试插入相同的密钥。例如一个用户 ID(它是一个字符串)。它是一个记录不同 API 的日志系统。然后工人根据记录的原始数据制作报告数据表。并且事件工作者可能会尝试将用户插入到键表中,并且案例数据工作者也会尝试这样做。他们都将用户作为其内部报告数据的一部分。在连接之前,我希望所有字符串数据都是数字的,并且有用于描述的键表。这就是为什么这一切都完成了:)
  • 而且我无法预先知道所有可能的键。我即时生成它们。这就是它变得复杂的原因。
  • 也许您需要做的就是通过忽略它来处理客户端上的唯一约束异常?
【解决方案2】:

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

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

【讨论】:

  • 如果在每个worker都提交之前同时执行两条这样的语句,是否仍然存在问题。由于事务隔离,语句 B 不会看到语句 A 刚刚插入了一些东西。我错了吗?否则 Firebird 支持类似的声明。
  • 我无法确定 Firebird 会做什么,但在 SQL Server 中,这被视为一条语句,并且表上会有一个锁,因此没有人可以同时添加行.没有它,自动递增的 id 也不起作用。
  • 如果两个线程同时尝试插入同一个key会发生什么,一个会成功添加一个新行,另一个不会添加一个新行。唯一的区别是第一个将有(1 row(s) affected),第二个将有(0 row(s) affected)
【解决方案3】:

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

不要这样做;除非 WORKERS 正在做大量的工作(我们谈论的是计算机,所以每条记录需要 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 表中。尝试插入。失败的?尝试另一个 KEY。定期删除所有带有旧 RESERVED_UNTIL 时间戳的保留键。确保使用 KEY_RESERVATIONS 的事务尽可能短,以便同时尝试保留相同密钥的两个线程会很快失败。

【讨论】:

  • 这不是预订问题。当使用尚未在表中的键时,插入键。也许钥匙这个词是这里的混乱。我没有选择那么好。问题是当两个或更多工作人员尝试插入相同的“密钥”(ID 的字符串描述)时。
【解决方案4】:

这是您在乐观(或无)锁定方案中必须处理的问题。

避免它的一种方法是在整个选择、插入、提交序列周围的表上放置一个悲观锁。

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

如果工作人员指的是同一应用程序实例中的线程,而不是不同用户(应用程序实例)中的线程,那么您将需要线程同步,如 kubal5003 所说的 select-insert-commit 序列。

如果您有多个用户/应用程序实例,每个实例都有多个线程,则需要将两者结合起来。

【讨论】:

  • 是的,我有多个进程。 Mutex 解决了这样的问题,但我希望有一个 DB 端的解决方案。锁定对我来说看起来不太好 :) 查询需要那些键表。
  • 互斥体在这种情况下很好,除非进程在不同的机器上。 IIRC 互斥锁不能跨机器工作,至少不能“开箱即用”。根据您的服务器(我对 Firebird 不太熟悉),您可以(应该能够)在允许读取的同时锁定写入,有点像 SHARE_DENY_WRITE 文件锁定。
【解决方案5】:

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

【讨论】:

  • 我已经更新了答案,以便更清楚地了解设置是如何完成的
【解决方案6】:

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

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

有关在 Firebird 中正确实现 autoinc 的更多信息,请点击此处:http://www.firebirdfaq.org/faq29/

【讨论】:

  • 你没有正确理解我的问题。我有键表,因此在我的主要数据表中,我只使用整数值进行操作。例如,我有用户密钥表,其中存储了不同的用户。在我的主要数据表中,我只将用户作为整数进行操作,然后从键表中加入他们的用户名。我已经使用了 autoinrement 和存储过程。问题是两个操作两个不同数据表的工作人员都想用同一个用户更新用户键表。这与 autoinrement 无关。
猜你喜欢
  • 1970-01-01
  • 2021-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-26
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
相关资源
最近更新 更多