【问题标题】:Select just one row from a table and lock it从表中仅选择一行并将其锁定
【发布时间】:2018-05-19 20:44:05
【问题描述】:

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

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

截至目前,我将这一行解读为:

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

然后,使用 ID 更新行:

UPDATE Inbox SET Status = 2 WHERE ID = X;

我希望查询锁定一行,因为它选择了它的 ID 并返回它,这样其他线程就无法读取它。

【问题讨论】:

  • 您是否尝试过使用rowversion 列?
  • 您的应用程序是用什么语言编写的?
  • @DavidG 我将如何使用它?
  • @Jaques 我正在使用 Java。

标签: sql sql-server concurrency


【解决方案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

【讨论】:

  • 我用 status = status + 1 运行了多个线程,并在作业完成后检查是否有超过 2 个线程。完美运行,不需要更改应用程序或数据库。
  • 当 2 个查询同时运行时,您可能仍会遇到竞争条件,还是 SQL 会正确处理它?
  • @Jaques 我在一个实时信用卡项目中使用这个解决方案,除了索引之外它从未出现任何问题(请查看我的答案的第二个链接)。事务边界内的readpast, uplock 确保来自不同线程的两个或多个事务不应锁定同一行,即使该查询同时被它们触发。依稀记得这在技术上叫concurrency control...
【解决方案2】:

一种可能的解决方案是将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

【讨论】:

    【解决方案3】:

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

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

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

    希望对您有所帮助。

    【讨论】:

    • 这本来可以,但是我也将运行同一个应用程序的多个实例,并发问题再次出现
    【解决方案4】:

    如果数据库在(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;
    

    Mysqlinnodb-locking-reads

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-03
      • 1970-01-01
      • 1970-01-01
      • 2021-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多