最近我不得不处理一个我认为很常见的问题:给定一个包含大量(百万+)行要处理的数据库表,以及在各种机器/线程中运行的各种处理器,如何安全地允许每个处理器实例在不互相干扰的情况下完成一大块工作(比如 100 个项目)?
我一次获得一大块的原因是出于性能原因 - 我不想为每个项目访问数据库。
最近我不得不处理一个我认为很常见的问题:给定一个包含大量(百万+)行要处理的数据库表,以及在各种机器/线程中运行的各种处理器,如何安全地允许每个处理器实例在不互相干扰的情况下完成一大块工作(比如 100 个项目)?
我一次获得一大块的原因是出于性能原因 - 我不想为每个项目访问数据库。
有几种方法 - 您可以将每个处理器关联一个令牌,并有一个 SPROC 将该令牌设置为下一个 [n] 可用项目;也许是这样的:
(注意 - 需要合适的隔离级别;也许可以序列化SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
:)
(编辑以修复 TSQL)
UPDATE TOP (1000) WORK
SET [Owner] = @processor, Expiry = @expiry
OUTPUT INSERTED.Id -- etc
WHERE [Owner] IS NULL
您还需要对此设置超时 ( @expiry
),这样当处理器出现故障时,您就不会失去工作。您还需要一项任务来清除所有者对他们过去的事情Expiry
。
您可以有一个特殊的表来排队工作,消费者在其中删除(或标记)正在处理的工作,或者使用中间件排队解决方案,如 MSMQ 或 ActiveMQ。
中间件有它自己的一系列问题,所以,如果可能的话,我会坚持使用一个特殊的表(让它尽可能小,希望只有一个 id 以便工作人员可以自己获取其余信息数据库并且不要将队列表锁定太久)。
你会定期填满这张表,让处理器从顶部获取他们需要的东西。
SQL表队列相关问题:
队列中间件的相关问题:
您没有说您正在使用哪个数据库服务器,但有几个选项。
MySQL 包括对 SQL99 的扩展,INSERT
以限制更新的行数。您可以为每个工人分配一个唯一的令牌,更新一些行,然后查询以获取该工人的批次。Marc 使用了UPDATE TOP
语法,但没有指定数据库服务器。
另一种选择是指定一个用于锁定的表。不要对数据使用同一个表,因为您不想锁定它以供读取。您的锁表可能只需要一行,而下一个 ID 需要工作。工作人员锁定表,获取当前 ID,按批次大小递增,更新表,然后释放锁。然后它可以去查询数据表并提取它保留的行。此选项假定数据表具有单调递增的 ID,并且如果工作人员死亡或无法完成批处理,则其容错性不强。
非常类似于这个问题:SQL Server Process Queue Race Condition
您运行查询以将 100 行分配给给定的处理器 ID。如果您使用这些锁定提示,那么它在并发意义上是“安全的”。它是一条不需要 SET 语句的单一 SQL 语句。
这取自另一个问题:
UPDATE TOP (100)
foo
SET
ProcessorID = @PROCID
FROM
OrderTable foo WITH (ROWLOCK, READPAST, UPDLOCK)
WHERE
ProcessorID = 0 --Or whatever unassigned is