【问题标题】:ROWLOCK in Stored Procedure with Composite Key in SQL ServerSQL Server 中具有复合键的存储过程中的 ROWLOCK
【发布时间】:2016-03-21 16:04:58
【问题描述】:

已编辑:我有一个包含复合键的表,它被部署在多个服务器上的多个 Windows 服务使用。

列:

UserId (int) [CompositeKey],
CheckinTimestamp (bigint) [CompositeKey],
Status (tinyint)

此表中会不断插入。我希望我的 Windows 服务选择前 10000 行并进行一些处理,同时仅锁定这 10000 行。我正在使用以下存储过程为此使用 ROWLOCK:

ALTER PROCEDURE LockMonitoringSession
AS
BEGIN
    BEGIN TRANSACTION

    SELECT TOP 10000 * INTO #TempMonitoringSession FROM dbo.MonitoringSession WITH (ROWLOCK) WHERE [Status] = 0 ORDER BY UserId

    DECLARE @UserId INT
    DECLARE @CheckinTimestamp BIGINT

    DECLARE SessionCursor CURSOR FOR SELECT UserId, CheckinTimestamp FROM #TempMonitoringSession

    OPEN SessionCursor

    FETCH NEXT FROM SessionCursor INTO @UserId, @CheckinTimestamp

    WHILE @@FETCH_STATUS = 0 
    BEGIN
        UPDATE dbo.MonitoringSession SET [Status] = 1 WHERE UserId = @UserId AND CheckinTimestamp = @CheckinTimestamp
        FETCH NEXT FROM SessionCursor INTO @UserId, @CheckinTimestamp
    END

    CLOSE SessionCursor
    DEALLOCATE SessionCursor

    SELECT * FROM #TempMonitoringSession
    DROP TABLE #TempMonitoringSession

    COMMIT TRANSACTION  
END

但是这样做,dbo.MonitoringSession 将被永久锁定,直到存储过程结束。我不确定我在这里做错了什么。

此存储过程的唯一目的是选择和更新 10000 条最近没有任何主键的行,并确保整个表不会因为多个 Windows 服务正在访问此表而被锁定。

提前感谢您的帮助。

【问题讨论】:

  • top 没有order byupdate 中没有where - 这只是在带有问题的简化代码中发布?你能发布select-top的实际执行计划吗?
  • 如果你只是更新 10 行,为什么还需要一个循环呢?这可以在一个语句中完成。
  • 你的UPDATE 语句没有WHERE 子句,所以这个UPDATE 操作应用于对整个表,所以表中的每一行都是完全锁定以执行UPDATE .....
  • 按照马克所说的,你正在执行相同的操作 50 K 次
  • 伙计们,我已经编辑了问题并发布了原始代码。我还改写了这个问题,并提到了这个 SP 的目的。请现在看看。谢谢。

标签: sql-server stored-procedures


【解决方案1】:

(不是答案,但评论太长了)

目的描述应该是关于你为什么/为什么要更新整个表格。您的 SP 用于使用 Status=0 更新所有行以设置 Status=1。因此,当您的一项服务决定运行此 SP 时 - 所有行都变得无关紧要。我的意思是,从逻辑上讲,导致状态更改的事件已经发生,您只需要一些时间在数据库中进行物理更改。那么为什么要让其他服务读取不相关的行呢?好的,可能您需要读取可读取的行(未更改) - 但由于您正在更新 whole 表,因此再次不清楚。

您可以使用READPAST 提示跳过锁定的行,并且为此需要行锁。 好的,但是即使使用一条语句处理前 N 行更新这些 N 行也会比循环遍历此行数要快得多。你正在做同样的工作,但手动。

查看结合UPDLOCK + READPAST 以使用并行进程处理同一队列的示例:https://www.mssqltips.com/sqlservertip/1257/processing-data-queues-in-sql-server-with-readpast-and-updlock/

小提示 - CURSOR STATIC, READONLY, FORWARD_ONLY 会做与存储到临时表相同的事情。查看STATIC 选项: https://msdn.microsoft.com/en-us/library/ms180169.aspx

另一件事是建议考虑RCSI。这肯定会避免其他服务锁定,但这是一个数据库级别的选项,因此您必须测试所有功能。大部分都会和以前一样工作,但有些场景需要测试(并发事务在之前被锁定的情况下不会被锁定)。

我不清楚:

  • 10000 行占总行数的百分比是多少?
  • 是否有聚集索引或者这是一个堆?
  • 选择和更新的实际执行计划是什么?
  • 什么是并发事务:插入还是选择?

顺便发现了类似的问题: why the entire table is locked while "with (rowlock)" is used in an update statement

【讨论】:

  • 感谢@Ivan 的详细回复。当我的服务第一次运行时,该表中将总共有 100,000 行。此表中将发生连续插入(大约每秒 500 行)。如您所见,我在删除该临时表之前选择了所有数据。当数据返回到服务时,大约需要 4-5 分钟来对该数据进行一些处理。 UserId、CheckinTimestamp 上有聚集索引。我希望我的服务选择最大行数并立即更新这些行并将这些行再次返回给服务。
  • 我不想锁定我的整个表,因为有 10 个服务实例不断地访问该表。而且该表上也有一个持续的插入。我在实现锁定之前遇到了这个问题,当 2 个或更多服务一次查询该表并选择相同的行执行时。表中的估计总行数可能以百万计。
  • 我也想知道,有没有办法使用复合键在同一个查询中进行 SELECT 和 UPDATE。几乎在互联网上的每个解决方案中,我都发现开发人员习惯于在临时表中选择主键列。然后更新并从该表中选择临时表中选定的主键。我无法使用复合键找到解决方案。
  • 将代理 ID 键添加到您的表并使其成为聚集索引。现在插入运行可能需要锁定聚集索引中间的页面,有时需要拆分。并发布选择和更新的实际执行计划(已经问过两次)。对于并发读者 - 查看他们过滤的列,他们是否需要此表的所有列。为读者构建额外的非聚集索引可能是有意义的。 Status=0 的行的百分比是多少?
猜你喜欢
  • 1970-01-01
  • 2018-08-18
  • 1970-01-01
  • 2015-03-08
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多