从表中只选择一行并锁定它

时间:2014-12-01 09:08:02

标签: sql sql-server concurrency

我有一个包含多行的表格。我还有一个多线程应用程序,它读取status = 1的行,然后在读取后将其更改为status = 2.

但是,由于应用程序的多线程特性,它会继续读取同一行两次(通过不同的线程)。我知道这是一个并发问题,但我无法解决它。

截至目前,我正在阅读该行:

SELECT TOP 1 * FROM Inbox WHERE Status = 1 ORDER BY ID DESC;

然后,使用ID,我更新行:

UPDATE Inbox SET Status = 2 WHERE ID = X;

我想要一个查询来锁定行,因为它选择了它的ID并返回它,因此没有其他线程能够读取它。

4 个答案:

答案 0 :(得分:1)

一种可能的解决方案是在表格中添加ROWVERSION列。这会创建一个列,只要您将UPDATE运行到一行,该列就会自动更新。在查询中使用它意味着您可以检查另一个进程是否已触及同一行。首先添加列:

ALTER TABLE Inbox
    ADD RowVersion ROWVERSION

现在,您需要更改UPDATE查询以将其纳入帐户:

UPDATE Inbox SET Status = 2 WHERE ID = X AND RowVersion = @RowVersion

检查更新的行数,您知道自己是否是第一个尝试的人。

SELECT @@ROWCOUNT

或者,使用ROWVERSION的MSDN文档,您可以执行以下操作:

DECLARE @t TABLE (myKey int);

UPDATE Inbox
SET Status = 2
    OUTPUT inserted.myKey INTO @t(X) 
WHERE ID = X 
AND [RowVersion] = @RowVersion

IF (SELECT COUNT(*) FROM @t) = 0
BEGIN
    RAISERROR ('Error changing row with ID = %d'
        ,16 -- Severity.
        ,1 -- State 
        ,X) -- Key that was changed 
END

答案 1 :(得分:1)

多线程应用程序从同一个表中读取并确保没有其他线程获得相同记录的最佳解决方案是创建一个静态类,该类将处理表中Status = 1的选择并让线程从班上得到一个记录。这将解决您的问题。在你的线程中,你可以相应地处理记录。

因此,创建一个Refresh表中数据的静态类,添加一个方法GetNextRecord,该方法将返回DataRow并具有适当的锁定。在启动线程之前执行Refresh方法,然后启动线程。每个线程将执行GetNextRecord,直到结果为null然后终止。完成所有线程后,重新开始。

这在类似的解决方案中对我有用。

希望它有所帮助。

答案 2 :(得分:1)

以下是如何实现这一目标的说明:

创建表

create table Inbox  ( id int  primary key clustered, stts int )

insert into Inbox values
(1,1),  (2,1),  (3,1),  (4,1)

现在,在SMSS中打开两个标签,并将其写入:

begin tran
select top 1 * from Inbox with(readpast,updlock) where stts = 1 order by id desc
--rollback tran

现在,运行第一个并检查它返回的内容。然后,转到第二个并运行它并检查结果。两者都给出了不同的结果。因此,假设两个选项卡都是不同的线程,您将了解如何实现它。现在取消注释rollback tran并执行它。结论是你需要创建一个事务边界,在事务边界内选择你的数据,并提供锁定提示" readpast,updlock'用这些数据完成你的工作,最后commit the transaction

注意:这是我在C++中实现多线程作业处理器的方式,因此它可能对您不起作用。如果查询第二个标签卡住并且没有给出结果,那么您需要创建一个索引。

检查类似问题here和有用信息here

答案 3 :(得分:0)

如果数据库在(postgresql,oracle,mysql)中,也许你可以使用SELECT ... FOR UPDATE

它将锁定行直到事务结束。

START TRANSACTION;
# reading the row as
SELECT TOP 1 * FROM Inbox WHERE Status = 1 ORDER BY ID DESC;
# Then, using the ID and update the row
UPDATE Inbox SET Status = 2 WHERE ID = X;
COMMIT;

Mysql innodb-locking-reads