从数据库中并发任务选取

本文关键字:任务 选取 并发 数据库 | 更新日期: 2023-09-27 18:33:00

在数据库表中,我保留了一个任务列表。为简单起见,假设任务是一个整数,并让表中有 100 个按递增顺序排列的整数。(增加顺序意味着如果有人正在处理任务 N,那么 N <的所有任务都已经在处理中(>

我还有 5 个客户端,它们连接并从数据库中选取任务,并在任务完成后更新数据库。

我不希望任何两个客户端选择相同的任务。

每当选择任务时,我都会将其添加到另一个表"tasksTable"中。选择新任务时,我在"tasksTable"中找到max_int,然后选择任务 = max_int+1

为了避免工作重复,我序列化了拣选任务的过程,即

getlock
read max_int
pick task
update tasksTable
releaselock

由于我只有大约 10 名工人,因此序列化问题不大。如果我有 1000 个客户怎么办?如何并行化任务拣选?

从数据库中并发任务选取

假设有两个表,一个用于未选取的任务(WaitingTasks(,一个用于选取的"正在进行"任务(WorkingTasks(:

CREATE TABLE WaitingTasks (WaitingTaskID INT IDENTITY(1,1), ColA, ColB, ...)

CREATE TABLE WorkingTasks (WorkingTaskID INT IDENTITY(1,1), WaitingTaskID INT, ColA, ...)

并发任务选取可以像这样完成:

INSERT INTO WorkingTasks (WaitingTaskID)
SELECT TOP 1 WaitingTaskID
FROM WaitingTasks
WHERE WaitingTaskID NOT IN (SELECT WaitingTaskID FROM WorkingTasks);
SELECT SCOPE_IDENTITY();

这将创建一个具有自己唯一 ID 的新"工作任务",SCOPE_IDENTITY(( 会将该唯一 ID 返回给您。

您可以尝试使用锁定,但您可以通过更新/选择来解决它:

BEGIN TRAN
UPDATE Task
SET clientId = MyClientId
WHERE [PrimaryKey] = (
   SELECT TOP 1 [PrimaryKey]
   FROM Tasks
   WHERE clientId IS NULL
   ORDER BY CreationDateTime)
SELECT * FROM Tasks
WHERE [PrimaryKey] = MyClientId
COMMIT TRAN

像这样的东西?


或者您可以使用 OUPUT 子句:

DECLARE @TaskId AS INT
UPDATE TOP(1) Task
SET clientId = MyClientId
OUTPUT Task.[PrimaryKey] INTO @TaskId
WHERE clientId IS NULL
ORDER BY CreationDateTime
SELECT @TaskId

(未测试(

来源: http://blog.sqlauthority.com/2007/10/01/sql-server-2005-output-clause-example-and-explanation-with-insert-update-delete/

不太了解你在这里做的事情。在任务完成之前是否锁定数据库表中的记录?如果是这样,我认为这不是一个好主意。不如改为向任务数据库表添加一列来提供任务的状态?如果已签出,请将其设置为 true,否则将其设置为 false。或者,您可以添加一个列,指示签出给谁(他的 personID(。如果为 null,则不会将其签出给任何人。请注意,在此解决方案中,您只有一个任务数据库表,而不是两个(一个单独的表用于已完成的任务(。我认为这是一个更好的解决方案(更好的规范化(。

仅向尚未分配的用户显示任务。如果他选了一个,有人在他有机会点击更新按钮之前击败他注册了该任务,您可以在更新时通知他为时已晚。示例:此 SQL 中的"where"子句可以解决问题:"更新任务集 personID=233421其中 personID==null"。如果更新返回时更新了零条记录,您就知道发生了什么。

我认为您的数据库/程序在支持数千个用户时不会有任何问题,因为只有当多个用户在相同的几毫秒内更新任务表时,才会有轻微的延迟,直到第一次更新完成。即便如此,对于 1000 个用户,我怀疑超过 10 个左右的用户会同时点击更新(几毫秒乘以 10 仍然不多的时间(。

另外,我认为您应该让数据库生成一个新的任务编号,而不是使用 task = max_int+1 以编程方式执行此操作。这样您就不必锁定表以确保它在更新之前不会更改。附带说明:如果您需要每次更新更新多个表,请设置事务。您无需为一次表更新执行此操作。

最后注意:处理更新的一个常规解决方案是向表添加一个"版本"列(整数或长整型(,并在每次有人更新记录时添加它。然后,对于每次更新,查看版本号是否与上次读取时的版本号相同(SQL:更新任务设置 personID=233421其中版本=4(。如果不一样,请通知用户。

我认为您的问题主要是关于增加并发性。您已经有一个安全的算法。现在你想要规模。

一些明显的解决方案存在通过锁定表的相同部分来序列化对表的访问的问题。在这里要小心并进行测试。

任务队列通常使用 READPAST 实现。这允许查询读取过去已锁定的行,从而允许在当前将先前的任务标记为正在处理时取消下一个任务的排队。

您可以通过更新特定行或使用锁定提示(如 XLOCK, ROWLOCK, HOLDLOCK (来实现对特定行的独占访问。

鲁萨努·莱姆斯(Rusanu Remus(在队列表上有一组文章,可以回答所有问题。请随时在这篇文章下面的评论中提出后续问题。