这是一个线程安全的'从队列中更新并返回SQL中的行的方法?

时间:2015-03-25 17:10:09

标签: c# sql-server-2012

我在SQL中实现了一个队列,其中包含一个具有“声明”权限的表格。柱。通过填充声明的字段,许多进程中的任何一个都可以从此队列中获取项目。

我想要做的是阻止两个进程抓取同一个项目。

我做了一些研究,发现了一些如何做到这一点的可能性。我不想使用经纪人,我确实想坚持使用简单的表格。

我正在使用C#处理检索到的项目。

我一直在使用SQL编写以下内容:

declare @test table (ID int);

UPDATE TOP (1) CatQueueItem SET ClaimedBy = 'Mittens'
OUTPUT inserted.CatQueueItemIdId into @test
WHERE ClaimedBy is null

这将通过添加参数作为输出参数等在C#中作为单个查询完成。

我想知道这段代码是否有效,或者我是否需要考虑锁定和转换以确保如果多个进程同时运行此查询它将按预期运行(只有一个进程会声明该项目,其他更新将完全跳过此行,因为它被第一行更新。

有什么想法吗?

2 个答案:

答案 0 :(得分:8)

虽然由于创建了隐式事务,给出的查询不会向多个线程提供一行,但它可能会导致一个线程设法占用队列的问题。如果要停止此操作,可能需要将READ PAST和ROW LOCK提示添加到查询中。这可以防止一个线程阻止其他线程获取行的锁定。

例如:

UPDATE TOP (1) CatQueueItem WITH (READPAST, ROWLOCK)
SET ClaimedBy = 'Mittens'
OUTPUT inserted.CatQueueItemIdId into @test
WHERE ClaimedBy is null

长解释

SQL Server中的所有语句都在Transaction的上下文中运行。如果运行了一个语句且没有指定事务,则SQL Server仅为该单个语句创建一个隐式事务。

SQL Server使用共享(S),更新(U)和独占(X)有三种主要类型的锁。锁是在交易范围内获得的。如果事务在一行上有S锁,则其他事务可以获得S或U锁,但不能在同一行上获得X锁。如果事务在一行上有U锁,则其他事务只能在该行上获得S锁。如果某个事务对某行有X锁定,则其他事务无法锁定该行。为了写入一行,一个事务需要有一个X锁,因为这会阻止所有其他事务在读取该行时进行更新。

这给出了以下兼容性表:

    S  U  X
  ----------
S | Y  Y  N
U | Y  N  N
X | N  N  N

问题中的更新包含两个部分。首先,它读取表以查找ClaimedBy中具有null的行。一旦找到该行,操作的第二部分就会更新找到的行。

通常,当从表中读取时,SQL Server使用S锁,因为这些不会阻止其他事务也读取行并提高读取性能,但它会阻止其他事务获取X锁以写入行。这样做的问题是,当更新查询的第二部分尝试升级到X锁时,它可以写入该行,这可能会导致死锁。原因是另一个事务中查询的第一部分可能已获得与您的事务相同的S锁,但可能尚未升级它。这可以防止您的事务将其锁定升级到X锁定,并且您的S锁定也会阻止其他事务升级。这两笔交易都无法成功,因此他们陷入僵局。在这种情况下,SQL Server选择一个事务并将其回滚。

要停止发生死锁,在执行更新语句的读取部分时SQL Server使用U锁。 U锁允许其他事务获取S锁,允许正在执行读取的那些成功,但它不允许其他U锁。通过使用U锁,你说你只是在阅读,但你打算在将来的某个时候写作。这可以防止您有两个尝试升级到X锁的事务的情况。因此,具有U锁的事务可以升级到X safe,因为它知道它不会通过这样做与另一个事务发生死锁。

所有这些与问题中给出的场景的相关性是,一个线程的事务使用U锁来锁定行,同时搜索可用行阻止所有其他线程的事务。这是因为当事务尝试获取已经具有不兼容锁的行上的锁时,它只是在队列中等待,直到阻塞锁被解锁。因为所有线程都在搜索相同的行,所以它们都尝试在同一行上获取U锁,并且所有线程都形成一个有序的队列,等待在同一行上获得U锁。换句话说,一次只允许一个线程的事务查找空闲行。

READPAST表提示的作用是停止排队的事务以读取表中的行。使用READPAST,当一个事务轮胎获取已锁定的行上的锁而不是加入锁的队列时,它会说出这个并继续尝试下一行。在这种情况下,它会说我不知道​​该行是否具有ClaimedBy值,我不准备等待发现,所以我将假设它确实存在并尝试下一行。这可能意味着它会跳过可用行但不会获得不可用的行。这将提高线程及其事务可以从队列中获取项目的速度,因为它们可以同时查找可用的行。

获取锁定可能非常昂贵。这需要时间和记忆。为了解决此问题,SQL Server具有几种锁定粒度。您可以锁定整个数据库,整个表,表的页面或表的一行。查询优化器将尝试使用统计信息来预测需要锁定的行数。如果有很多,它将选择页面或表锁而不是行锁。这具有总体上需要更少锁的效果。

ROWLOCK表提示告诉SQL Server不要使用这些粗粒度锁并且只使用行锁。在这种情况下,这是有利的,因为它会阻止正在查找可用行的事务跳过大块可用行。

答案 1 :(得分:3)

SQL Server中的每个查询都在隐式事务中运行。由于您在这里使用单个语句(而不是多个查询),引擎将为您处理锁定和阻止。

只要处理C#代码中没有更新记录的情况,就应该处理并发问题。