【问题标题】:Efficient transaction, record locking高效事务,记录锁定
【发布时间】:2009-02-22 07:57:03
【问题描述】:

我有一个存储过程,它选择了 1 条记录。可以从不同 PC 上的多个不同应用程序调用存储过程。想法是存储过程带回下一条需要处理的记录,如果两个应用程序同时调用存储过程,不应该带回相同的记录。我的查询如下,我正在尝试尽可能高效地编写查询(sql 2008)。能比这更高效吗?

CREATE PROCEDURE GetNextUnprocessedRecord
AS
BEGIN
    SET NOCOUNT ON;

    --ID of record we want to select back
    DECLARE @iID BIGINT     

    -- Find the next processable record, and mark it as dispatched
    -- Must be done in a transaction to ensure no other query can get
    -- this record between the read and update
    BEGIN TRAN

        SELECT TOP 1
            @iID = [ID]
        FROM
            --Don't read locked records, only lock the specific record
            [MyRecords] WITH (READPAST, ROWLOCK)
        WHERE
            [Dispatched] is null
        ORDER BY
            [Received]

        --Mark record as picked up for processing   
        UPDATE 
            [MyRecords]
        SET
            [Dispatched] = GETDATE()
        WHERE
            [ID] = @iID     

    COMMIT TRAN

    --Select back the specific record
    SELECT 
        [ID],
        [Data]
    FROM    
        [MyRecords] WITH (NOLOCK, READPAST)
    WHERE
        [ID] = @iID

END

【问题讨论】:

  • 我不相信这个 TSQL 在事务上是安全的......
  • 尝试在 SELECT 之后和 UPDATE 之前放置 WAITFOR DELAY '0:2:0',运行 SP 并从另一个连接执行相同的 SP ...
  • 其实我错了! HOLDLOCK 与 MyRecords 表上的 REPEATABLEREAD 具有相同的效果。
  • 默认情况下最好是安全的。否则,就会出现广泛的概念混乱。
  • 我仍然认为您需要使事务 SERIALIZABLE 而不是 REPEATABLEREAD

标签: sql-server transactions locking


【解决方案1】:

使用 READPAST 锁定提示是正确的,并且您的 SQL 看起来没问题。

我会添加使用 XLOCK 虽然它也是 HOLDLOCK/SERIALIZABLE

...
[MyRecords] WITH (READPAST, ROWLOCK, XLOCK)
...

这意味着您获得 ID,并在您继续更新该行时独占锁定该行。

编辑:在 Dispatched 和 Received 列上添加索引以使其更快。如果 [ID](我假设它是 PK)未聚类,则包括 [ID]。并过滤索引,因为它是 SQL 2008

您也可以使用这种结构,无需 XLOCK 或 HOLDLOCK 即可一次性完成所有操作

UPDATE
    MyRecords
SET
    --record the row ID
    @id = [ID],
    --flag doing stuff
    [Dispatched] = GETDATE()
WHERE
    [ID] = (SELECT TOP 1 [ID] FROM MyRecords WITH (ROWLOCK, READPAST) WHERE Dispatched IS NULL ORDER BY Received)

UPDATE, assign, set in one

【讨论】:

  • 精彩提示!我添加了 XLOCK 提示并在 Received 和 Dispatched 上创建了一个非聚集索引,并包含带有“Dispatched IS NULL”过滤器的 ID。这是对我的 ID 聚集索引的补充。我仍在试图弄清楚 Update assign set in one 是否更快。
  • 谢谢。我更担心交易的完整性而不是直接的性能。单个语句消除了对 XLOCK 的需要。
【解决方案2】:

您可以为每个选择器进程分配一个唯一的 ID,并将列 pickerproc 和 pickstate 添加到您的记录中。那么

更新我的记录
SET pickerproc = myproc,
pickstate = 'I' -- 用于'I'n 进程
WHERE Id = (SELECT MAX(Id) FROM MyRecords WHERE pickstate = 'A') -- 'A'vailable

这让您在一个原子步骤中获得记录,您可以在闲暇时完成其余的处理。然后,您可以将 pickstate 设置为 'C'omplete'、'E'rror 或其他任何问题。

我认为 Mitch 指的是另一种好的技术,您可以创建一个消息队列表并在其中插入 Id。有几个 SO 线程 - 搜索“消息队列表”。

【讨论】:

  • 为什么会这样?在您更改它之前,它不会使用pickstate 'A' 传递相同的 MAX(Id) 。至少根据我的经验,它可能没有 10^6+ 个实例。
  • ..而且效率也不高
  • @le dorfier:为了使事务保持一致,需要将其包装在 SERIALIZABLE 事务中。否则存在脏读/不可重复读/幻读/幻读的风险/
  • 除非我弄错了,否则 MAX 操作将扫描行。如果这是一张大桌子,它会在相对较长的时间内保持交易开放。
  • 我不想再次找出那个笨重的锁文档,但我开始假设 ACID 默认适用。你不同意?
【解决方案3】:

您可以将 MyRecords 保存在“MEMORY”表中以加快处理速度。

【讨论】:

  • 表可能会变得很大,另外,如果服务器崩溃会怎样?
猜你喜欢
  • 1970-01-01
  • 2012-09-09
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多