从数据库表上的多个线程进行查询

时间:2017-06-13 22:31:32

标签: sql-server database multithreading locking

我有一个包含数千个条目的数据库表。我有多个工作线程,一次拾取一行,做一些工作(每个大约需要一秒)。在拾取行时,每个线程都会更新数据库行上的标志(如时间戳),以便其他线程不会拾取它。但问题是我最终遇到了多个线程正在拾取同一行的情况。

我的一般问题是,我应该遵循什么样的通用设计方法,以确保每个线程获取唯一的行并独立完成其任务。

注意:多个线程并行运行以加速数据库行的处理。所以我希望有一个尽可能小的关键段或独占锁。

只是给出一些上下文,下面是存储过程,它在更新了行上的标志后从表中获取行。请注意,存储过程不可编译,因为我已从中删除了不必要的部分。但一般来说就是它的结构。

当多个线程并行执行存储过程时会发生此问题。除非事务已提交,否则更新语句(请注意,更新是在占用锁之后完成)在一个线程中所做的更改对另一个线程是不可见的。由于UPDATE和TRANSACTION COMMIT之间存在SELECT语句(大约需要50ms),因此在20%的情况下,线程中的UPDATE语句会获取已经处理过的行。

我希望我在这里足够清楚。

USE ['mydatabase']
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[GetRequest] 
AS
BEGIN

    -- some variable declaration here

    BEGIN TRANSACTION

    -- check if there are blocking rows in the request table
    -- FM: Remove records that don't qualify for operation.

    -- delete operation on the table to remove rows we don't want to process
    delete FROM request where somecondition = 1


    -- Identify the requests to process
    DECLARE @TmpTableVar table(TmpRequestId int NULL);

    UPDATE TOP(1) request 
        WITH (ROWLOCK)
           SET Lock = DateAdd(mi, 5, GETDATE()) 
        OUTPUT INSERTED.ID INTO @TmpTableVar
          FROM request tur
         WHERE (Lock IS NULL OR GETDATE() > Lock)   -- not locked or lock expired
           AND GETDATE() > NextRetry -- next in the queue   

        IF(@@RowCount = 0)
        BEGIN
            ROLLBACK TRANSACTION
            RETURN
        END

    select @RequestID = TmpRequestId from @TmpTableVar

    -- Get details about the request that has been just updated
    SELECT      somerows
    FROM        request
    WHERE       somecondition = 1

COMMIT TRANSACTION
END

enter image description here

2 个答案:

答案 0 :(得分:0)

SQL Server中关键部分的模拟是sp_getapplock,它易于使用。或者,您可以使用(UPDLOCK,READPAST,ROWLOCK)表提示选择要更新的行。这两个都需要多语句事务来控制独占锁定的持续时间。

答案 1 :(得分:0)

您需要在sql上启动transaction isolation level以隔离您的行,但这会影响您的效果。

查看示例:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

GO

BEGIN TRANSACTION

GO

SELECT ID, NAME, FLAG FROM SAMPLE_TABLE WHERE FLAG=0

GO

UPDATE SAMPLE_TABLE SET FLAG=1 WHERE ID=1

GO

COMMIT TRANSACTION 

整理,不存在使用隔离级别的更好方法。您需要分析每个级别隔离的正负点并测试您的系统性能。

更多信息:

https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql

http://www.besttechtools.com/articles/article/sql-server-isolation-levels-by-example

https://en.wikipedia.org/wiki/Isolation_(database_systems)