【问题标题】:Mark a record as 'in use' to support multithreading将记录标记为“正在使用”以支持多线程
【发布时间】:2010-02-26 23:24:04
【问题描述】:

我的(简化的)表由一个

Id int identity(1,1),
File varchar(20),
FileProcessed bit

逻辑是这样的:我的应用程序首先获取(顺序不重要)记录,它的 FileProcessed 位设置为 false。然后它处理文件并将 FileProcessed 位设置为 true。

现在,可能会发生第一个线程获取 ID 为 1 的记录,而在处理它时,另一个线程获取 ID 为 1 的相同记录(因为它不是已处理的市场)。

在此示例中支持多线程的最佳方式是什么?

编辑:我使用 SQL Sever 2005 EDIT2:文件的处理可能需要很长时间,所以我不想同时锁定整个表

【问题讨论】:

    标签: .net sql multithreading


    【解决方案1】:

    其他人提到添加一个额外的列 - 您也可以考虑将 FileProcessed 列更改为名为例如的列状态 - 您可以在哪里建模未处理、处理、已处理、故障? (例如,如果无法处理文件会发生什么)。

    另外,如果处理失败,是否要立即重试处理文件。如果处理器意外死机,您将如何处理(例如,您可能需要另一个表来描述每次处理尝试何时开始 - 如果最后一次尝试在 20 分钟前开始(或任何看起来合理的),那么您可能会考虑一次失败的尝试。

    要正确进行选择/更新,您可能需要如下脚本:

    declare @FileID int
    
    BEGIN TRANSACTION
    
    select top 1 @FileID = FileID from FilesToDoStuffTo with (updlock,holdlock,readpast) where Status=Unprocessed
    
    update FilesToDoStuffTo set Status = Processing where FileID = @FileID
    
    COMMIT
    

    然后对您选择的@FileID 做任何您需要做的事情。

    【讨论】:

    • +1 迄今为止的最佳答案。建议存储对行进行处理的实例的唯一 ID。当出现问题时(当然,确实发生了),了解 哪个 实例应该处理该行可能会非常有用,因为该实例已经走动,所以可能会解锁它。 :-)
    • @OP:我已按要求将我的评论转移到答案中,但我真的会尽可能使用 Damien 的方法。 :-)
    • @KM - 同意,并且还添加了用于选择未处理文件的 WHERE 子句:-)
    【解决方案2】:

    您需要将应用程序逻辑包装在 TransactionScope 部分中。这样,对数据库的每次调用都在其自身的事务中。

    要确定调用实际上是锁定的,请使用采用TransactionScopeOption 的构造函数,特别是Required 选项,这样事务将始终到位。

    这可能会对性能产生影响,因此您需要对其进行测试。

    你不能仅仅依赖于改变数据库中的一个字段,因为你可能会得到脏读(一个线程读取记录不是“正在使用”,另一个将它标记为“正在使用”并且第一个仍然尝试使用它)。

    因此,事务支持和数据库中的字段的组合效果最好。

    【讨论】:

      【解决方案3】:

      将表结构改为:

      Id int identity(1,1),
      File varchar(20),
      FileOnProcess bit,
      FileProcessed bit
      

      现在,您可以锁定行并更新 FileOnProcess 位,以便您只选择尚未处理的文件。根据您的数据库引擎,用于锁定的实际 SQL 命令可能会有所不同。

      【讨论】:

      • 我强烈建议使用一个列,而不是一个标志,在该列中可以存储处理该行的实例的唯一 ID。当出现问题时(而且它们会出现问题),了解哪些记录正在由一个已经走动的实例处理是很有用的。
      【解决方案4】:

      (根据 OP 的要求,重新发布评论作为答案。)

      我认为Damien_The_Unbeliever's answer 是使用支持它的 RDMS(例如 SQL Server)时最好的通用方法,除了(正如我在对他的评论中提到的那样),我将包含一个列标识处理行。

      如果您使用的 RDBMS 或环境使上述操作变得困难,并且如果您为每个实例提供自己的唯一 ID,则可以通过设置一个通常为 NULL 的列来获得类似的效果,您可以将其设置为处理排。然后(即使没有交易)你可以做

      set rowcount 1
      update FilesToDoStuffTo
      set BeingProcessedBy = {theid}
      where FileProcessed = 0 and BeingProcessedBy is NULL
      

      ...然后

      select FileID from FilesToDoStuffTo where BeingProcessedBy = {theid}
      

      (通常您的数据库连接基础架构——ODBC、JDBC 等等——有一个 set rowcount 1 的包装器;基本上,您要确保只更新 一个 行,而不是所有他们!或者分批抓取 5、10 个,无论你在做什么都有意义。)

      如果可以的话,最好避免这种游戏(通过使用事务和/或存储过程来至少选择行、行锁等),但有时沼泽标准方法是最实用的。 :-)

      【讨论】:

        【解决方案5】:

        添加一个称为处理的新标志,或者在第一个线程拾取记录时将记录标记为已处理,具体取决于您的要求。

        还请注意,在拉取记录并将其标记为正在处理时,您应该锁定表,因为您可能会读取记录,然后在将其标记为已处理/正在处理之前,另一个线程可能会进入并获取也是。

        【讨论】:

          猜你喜欢
          • 2015-01-20
          • 2013-06-02
          • 1970-01-01
          • 2016-05-09
          • 2015-08-19
          • 1970-01-01
          • 1970-01-01
          • 2022-08-14
          • 1970-01-01
          相关资源
          最近更新 更多