代码之家  ›  专栏  ›  技术社区  ›  sventevit

将记录标记为“正在使用”以支持多线程

  •  2
  • sventevit  · 技术社区  · 14 年前

    我的(简化的)表格由

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

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

    现在,可能会发生这样的情况:第一个线程获取id为1的记录,而在处理它时,另一个线程获取id为1的相同记录(因为它不是按处理后的市场价格)。

    在这个例子中,支持弥尔顿阅读的最好方法是什么?

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

    5 回复  |  直到 14 年前
        1
  •  3
  •   Damien_The_Unbeliever    14 年前

    其他人已经提到添加一个额外的列-您也可以考虑将fileprocessed列更改为一个名为status的列-在这里您可以对未处理、处理、处理、出错进行建模?(例如,如果无法处理文件会发生什么情况)。

    另外,如果处理失败,是否要立即重试处理该文件。如果处理器意外死亡,您将如何处理(例如,您可能需要另一个表来描述每次处理尝试的开始时间—如果最后一次尝试是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执行任何需要执行的操作。

        2
  •  2
  •   Oded    14 年前

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

    要确定调用实际上是锁定的,请使用 TransactionScopeOption ,特别是 Required 选项,因此事务将始终存在。

    这很可能会影响性能,所以您需要测试它。

    您不能仅仅依赖于更改数据库中的字段,因为您可能会得到脏读(一个线程读取到记录未“使用中”,另一个线程将其标记为“使用中”,第一个线程仍尝试使用它)。

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

        3
  •  1
  •   Jorge Córdoba    14 年前

    将表结构更改为:

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

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

        4
  •  1
  •   Community Dan Abramov    7 年前

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

    我想 Damien_The_Unbeliever's answer 当使用支持它的rdms(比如sql server)时,这是最好的通用方法,除了(正如我在给他的评论中提到的那样),我会包含一个列来标识处理该行的实例。

    如果您正在使用的rdbms或环境使上述操作变得困难,并且您为每个实例提供了自己的唯一id,那么您可以通过设置一个通常为空的列来获得类似的效果,该列可以设置为处理行的实例。那么(即使没有事务处理)您也可以

    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个,不管你做什么都有意义。)

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

        5
  •  0
  •   cjk    14 年前

    根据您的需求,添加一个名为processing的新标志,或者jst在第一个线程接收记录时将其标记为processed。

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