大家好,比我聪明的人!我创建了一个队列表系统,但它似乎太简单,无法避免竞争条件。我是否遗漏了什么或者以下竞争条件安全吗?
模式
我有一张桌子,我们称之为ProductQueue
:
CREATE TABLE dbo.ProductQueue
(
SerialId BIGINT PRIMARY KEY,
QueuedDateTime DATETIME NOT NULL -- Only using this for reference, no functionality is tied to it
);
我有添加到队列的过程,称为AddToProductQueue
:
CREATE PROCEDURE dbo.AddToProductQueue (@SerialId BIGINT)
AS
BEGIN
INSERT INTO dbo.ProductQueue (SerialId, QueuedDateTime)
OUTPUT Inserted.SerialId
SELECT @SerialId, GETDATE();
END
我还有一个从队列中删除的过程,称为RemoveFromProductQueue
:
CREATE PROCEDURE dbo.RemoveFromProductQueue (@SerialId BIGINT)
AS
BEGIN
DELETE FROM dbo.ProductQueue
OUTPUT Deleted.SerialId
WHERE SerialId = @SerialId;
END
注意,对于源数据库/系统中的SerialId
a 来说是全局唯一的。Product
即,a 的两个实例Product
不可能具有相同的SerialId
。这就是数据库方面的范围。
工作流程
- 我有一个每小时运行的申请流程。
SerialIds
该进程从源系统获取变量列表。- 它迭代地调用其列表中
AddToProductQueue
每个的过程SerialId
。 - 如果该过程尝试插入表
SerialId
中ProductQueue
已存在的 ,则会引发主键冲突错误,并且应用程序进程会捕获该错误并跳过该错误SerialId
。 - 否则,该过程会成功地将其添加
SerialId
到ProductQueue
表中并将其返回给应用程序进程。 - 然后,应用程序进程将成功排队的添加
SerialId
到单独的列表中。 - 应用程序进程完成迭代所有要
SerialIds
入队的候选者列表后,它会迭代其成功排队的新列表,并在每个的单独线程SerialIds
中对它们进行外部工作。(这项工作与数据库无关。)SerialId
- 最后,当每个线程完成其外部工作时,该异步线程中的最后一步是通过调用该过程将其
SerialId
从表中删除。(请注意,会实例化一个新的数据库上下文对象,并为每个异步调用此过程创建一个新连接,因此它在应用程序端是线程安全的。)ProductQueue
RemoveFromProductQueue
附加信息
- 表上没有任何索引
ProductQueue
,并且表中的行数永远不会超过 1,000 行。(实际上,大多数时候它实际上只有几行。) - 相同的
SerialId
可以再次成为在应用程序进程的未来执行时被重新添加到队列表中的候选者。 - 没有安全措施可以阻止应用程序进程的第二个实例同时运行,无论是意外还是第一个实例运行时间超过 1 小时等。(这是我最关心的并发部分。)
- 队列表和过程所在的数据库(以及正在建立的连接)的事务隔离级别是默认隔离级别
Read Committed
。
潜在问题
- 应用程序进程的运行实例以未处理的方式崩溃,卡
SerialIds
在队列表中。这对于业务需求来说是可以接受的,我们计划提供异常报告来帮助我们手动修复这种情况。 - 应用程序进程同时执行多次,并
SerialIds
在其初始源列表中的实例之间获取一些相同的内容。我还无法想到这种情况的任何负面影响,因为排队过程是原子的,并且SerialIds
由于该原子排队过程,应用程序进程将处理的实际列表应该是独立的。我们并不关心应用程序进程的哪个实例实际处理每个进程SerialId
,只要SerialId
两个进程实例不同时处理相同的进程即可。