两个线程同时添加新行 - 如何防止?

时间:2009-07-21 09:51:14

标签: database multithreading

在我的应用程序中,我有几个执行某些逻辑的线程。 最后,他们在某个表格中添加了新行。

在添加新行之前,它们会检查先前具有相同详细信息的条目是否尚不存在。如果找到了 - 他们会更新而不是添加。

问题是当一些线程A进行检查时,看到没有先前具有相同细节的实体,并且在他添加新行之前,线程B在DB中搜索同一实体。线程B看到没有这样的实体存在,所以他也添加了新的行。

结果是表中有两行具有相同的数据。

注意:没有违反表键,因为线程在添加行之前得到下一个序列,而表键是一些与数据无关的ID。

即使我将更改表键,因此它将是数据的组合,它将阻止具有相同数据的两行,但是当第二个线程将尝试添加行时将导致DB错误。

提前感谢你的帮助,罗伊。

8 个答案:

答案 0 :(得分:5)

你说的是“行”,所以这可能是一个SQL数据库?

如果是这样,为什么不直接使用交易?

(除非线程共享数据库连接,在这种情况下,互斥锁可能有所帮助,但我更愿意为每个线程分别提供一个连接。)

答案 1 :(得分:4)

您应该使用队列,可能阻塞队列。线程A和B(生成器)会将对象添加到队列中,而另一个线程C(使用者)将轮询队列并从队列中删除最旧的对象,将其持久保存到DB。当A和B同时想要持久存在相等的对象时,这将防止出现问题

答案 2 :(得分:3)

我建议避免在客户端层锁定。同步仅在一个进程内工作,稍后您可以扩展,以便您的线程跨多个JVM或实际上是机器。

我会在数据库中强制执行唯一性,因为您建议这会导致第二个插入器出现异常。如果这是您需要的业务逻辑,请抓住该异常并进行更新。

但请考虑这个论点:

有时可能出现以下任一序列:

插入值V A ,B更新为值V B

B插入V B ,A更新为V A

如果两个线程正在竞争这两个结果中的任何一个,则V A 或V B 同样有效。所以你无法区分第二种情况与A插入V A 而B只是失败!

因此实际上可能不需要“失败然后更新”的情况。

答案 3 :(得分:2)

我认为这是SQL约束的工作,即具有数据+相应错误处理的列集上的“UNIQUE”。

答案 4 :(得分:1)

大多数数据库框架(Java中的Hibernate,Ruby中的ActiveRecord等)都具有optimistic locking的形式。这意味着您执行每个操作的前提是它可以正常工作而不会发生冲突。在存在冲突的特殊情况下,您可以在执行数据库操作,抛出异常或错误返回代码的位置以原子方式检查此内容,并在重新查询后重试客户端代码中的操作。

这通常使用每条记录上的版本号来实现。完成数据库操作后,将读取该行(包括版本号),客户端代码将更新数据,然后使用where子句将其保存回数据库,并指定主键ID和版本号为与阅读时一样。如果它不同 - 这意味着另一个进程已更新该行,并且应该重试该操作。通常这意味着重新读取记录,并使用来自其他进程的新数据再次对其执行该操作。

在添加的情况下,您还需要表上的唯一索引,因此数据库拒绝该操作,您可以使用相同的代码处理该操作。

伪代码看起来像

do {
  read row from database
  if no row {
     result_code = insert new row with data
  } else {
     result_code = update row with data
  }
} while result_code != conflict_code

这样做的好处是您不需要在客户端代码中进行复杂的同步/锁定 - 每个线程只是单独执行,并使用数据库作为一致性检查(它非常快速,擅长)。因为您没有为每个操作锁定某些共享资源,所以代码可以更快地运行。

这也意味着您可以运行多个单独的操作系统进程来分割负载和/或在多个服务器上扩展操作,而无需更改任何代码来处理冲突。

答案 5 :(得分:0)

您需要包含调用以检查并在临界区或互斥锁中写入行。

使用临界区时,在执行检查和写入时会禁用中断和线程切换,因此两个线程都不能立即写入。

使用互斥锁,第一个线程将锁定互斥锁,执行其操作,然后解锁互斥锁。第二个线程会尝试执行相同操作,但互斥锁会阻塞,直到第一个线程释放互斥锁。

关键部分或互斥功能的具体实现取决于您的平台。

答案 6 :(得分:0)

您需要执行检查现有行的操作,然后在单个事务中更新/添加行。

当您执行检查时,您还应该获取这些记录的更新锁定,以指示您将根据您刚刚阅读的信息写入数据库,并且不允许任何其他人使用改变它。

在伪T-SQL(对于Microsoft SQL Server)中:

BEGIN TRANSACTION
SELECT id FROM MyTable WHERE SomeColumn = @SomeValue WITH UPDLOCK
-- Perform your update here
END TRANSACTION

更新锁定不会阻止人们从这些记录中读取,但它会阻止人们编写任何可能会改变SELECT输出的内容

答案 7 :(得分:-3)

多线程始终是令人费解的^^。

主要做的是划分关键资源和关键操作。

  • 关键资源:你的桌子。
  • 关键操作:添加是,但是 整个程序

您需要从检查开始到添加结束锁定对表的访问权限。 如果一个线程试图做同样的事情,而另一个线程正在添加/检查,那么他会等待线程完成其操作。就这么简单。