如何“锁定”正在处理的数据库行

How to 'lock' database rows being processed

提问人:anemaria20 提问时间:1/17/2017 更新时间:1/17/2017 访问量:164

问:

我有一个数据库,里面装满了行和多个线程,这些线程正在访问这些行,在函数中输入其中的一些数据,生成输出,然后用输出填充行中缺失的列。

问题来了:每行都有一个标志,默认情况下,该标志为 true。因此,每个线程都在寻找带有此标志的行。但是每个线程都得到相同的行,事实证明......因为在线程的作业完成后,该行被标记为已处理,这可能会在几秒钟后发生。unprocessed

我避免这种情况的一种方法是为每一行插入一个标志,将其标记为 false,一旦线程访问该行,将其更改为 true。然后,当线程完成时,只需将 if 改回 false。这样做的问题是我必须使用某种锁定,并且在发生这种情况之前不允许任何其他线程执行任何操作。我想知道是否有另一种方法,我不必进行线程锁定(通过互斥锁或其他东西),从而减慢整个过程。currently_processed

如果有帮助,代码是用 Ruby 编写的,但这个问题与语言无关,但这里有代码来演示我正在使用的线程类型。所以没什么特别的,像几乎所有语言一样,在最低级别进行线程处理:

3.times do
  Thread.new do
   row = get_database_row
   result = do_some_processing(row)
   insert_results_into_row(result)
  end
end.each(&:join)
Ruby 数据库 多线程 算法 语言无关

评论


答:

2赞 GhostCat 1/17/2017 #1

这里的“真正”答案是您需要一个数据库事务。当一个线程获取该行时,数据库需要知道该行当前正在处理。

您无法在应用程序中解决此问题!你看,当两个线程同时查看同一行时,它们都可以尝试编写该标志......是的,它肯定会更改为“当前正在处理”;然后两个线程都将更新行数据并将其写回。如果任何处理导致相同的最终结果,也许这不是问题;但如果没有,那么就会出现各种数据完整性问题。

因此,真正的答案是,你退后一步,看看你的特定数据库是如何设计的,以处理这些事情。

评论

0赞 anemaria20 1/17/2017
在 trasanction 中,其他线程将不得不等待,对吧?我看不出这与互斥锁有什么不同。Ofc,它是在数据库级别,但有什么功能差异吗?
1赞 GhostCat 1/17/2017
互斥锁是存在于应用程序中的东西。如前所述:您不能假设两次读取读取而不是将该字段写入数据库会导致正确的结果。是的,你不需要使用数据库,但关键是:这是使用数据库的要点之一 - 具有这种级别的控制。你看,你只是建议在你的应用程序中重新发明轮子。你很有可能:你会弄错的。
1赞 slebetman 1/20/2017
@anemaria20:真正的互斥锁可以工作,但你对标志的原始描述是不起作用的。该标志可能看起来像互斥锁,但互斥锁有一个额外的属性:有低级保证,即没有两个线程可以同时设置互斥锁。同样,事务是提供此类保证的数据库功能。因此,事务基本上是数据库提供类似互斥锁的功能的方式currently_processedcurrently_processed
1赞 slebetman 1/20/2017
@anemaria20:附加说明:互斥锁只能在一台机器内工作。如果有几台不同的计算机与数据库通信(例如,如果使用负载平衡器),则事务将起作用
0赞 Alexander Anikin 1/17/2017 #2

我想知道是否有另一种方法,我不必进行线程锁定(通过互斥锁或其他东西),从而减慢整个过程。

有一些方法可以做到这一点:

1) 所有线程使用一个通用调度程序。它应该读取所有行并将它们放入共享队列中,处理 thead 将从中获取行。

2)更深入地研究数据库,找出它是否支持类似预言机的“select for update skip locking”语法并利用它。对于预言机,你需要在游标中使用他的语法,并进行一些繁琐的交互,但至少它可以以这种方式工作。

3)通过工作线程的索引对输入进行分区。因此,3 个工作线程中的第 1 个工作线程将只处理第 1、4、7 行等,第二个工作线程将仅处理第 2、5、8 行等。