【问题标题】:Table deadlock issues with lock escalations锁定升级的表死锁问题
【发布时间】:2021-01-11 03:46:13
【问题描述】:

我有一个表,其中有 1 到 500 万条记录作为批处理进入,然后在它们上运行一堆存储过程,以更新和删除批处理中的记录。 所有这些存储过程都使用两个字段进行选择性,因此它们只在该批次中的记录上运行。 这两个字段都在非聚集索引中。 有时会同时运行多个批次,并且我在批次之间不断发生死锁,我认为是由于锁定升级。 试图弄清楚是否有一种方法可以解决这个问题,而无需完全重新设计以对每个批次使用专用表。禁用页面锁定是否会带来更多麻烦?

附加信息:

表结构和索引示例(真实的列比这个多很多)

CREATE TABLE [dbo].[TempImport](
    [UID] [int] IDENTITY(1,1) NOT NULL,
    [EID] [int] NULL,
    [EXTID] [int] NULL,
    [COL1] [varchar](50) NULL,
    [COL2] [varchar](50) NULL,
 CONSTRAINT [PK_TempImport] PRIMARY KEY CLUSTERED 
(
    [UID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_TempImport_Main] ON [dbo].[TempImport]
(
    [EID] ASC,
    [EXTID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

存储过程中的查询类型如下所示:

update TempImport set COL1 = 'foo' where EID = @EID and EXTID = @EXT and COL2='bar'

批处理完成时发生的最后一件事是这样的:

Delete from TempImport where EID = @EID and EXTID = @EXT

死锁通常是存储过程中的删除和更新。

如果有其他有用的信息请告诉我

【问题讨论】:

  • 使用 MS SQL 2019
  • Brent Ozar 最近做了一个nice video on deadlocks。它可能对此有一些想法。这是 45 分钟,但我发现它有助于理解它们以及如何修复它们。正如您所说,表很可能被锁定 - 我相信如果 SQL Server 更新表中超过 5000 行,它会将锁升级为全表锁。从视频中我不清楚的一件事是,在一个事务(或几个较小的事务)中进行批处理是否会克服死锁。
  • 谢谢,我会看看那个视频。我当时的想法是以某种方式将更新和删除分解成更小的块,但在处理数百万条记录时似乎效率会低得多。

标签: sql sql-server database-deadlocks


【解决方案1】:

只是一些潜在的建议

  • 是的,您可以将它们分成(比如说)2000 个批次,以阻止行锁升级为表锁,但您将有大量的运行。可能不是一个好的解决方案。
  • 您可以修改更新过程(根据Brent Ozar's video re deadlocks I recommended in the comments)以仅更新每个表一次。作为建议,您也可以尝试将它们封装在事务中。这些可以消除死锁,但会增加阻塞(第二个操作必须等待第一个操作完成)。
  • 一种结构化方法是制作一个“加载”或“暂存”表,其中包含要操作的 ID 和相关数据(您可以将其视为队列)。当调用更新时,他们只需将他们的请求插入到该队列中。然后你有一个异步进程(一次只能调用一个),它从队列中获取所有未完成的数据(并将其标记为这样),进行相关处理,然后从临时表中清除处理过的数据。

请注意,如果您有其他事物访问/使用此表,那么它们也可能在此表上被阻塞或死锁,那么您的方法需要非常小心。

【讨论】:

  • 感谢您的想法,我将使用您建议的第二个选项进行一些测试,因为它似乎破坏性最小。在合理的范围内,我可以接受阻塞并且查询需要更长的时间以避免死锁。
  • FWIW 我想我会更新我们最终所做的事情。不是一个完美的解决方案,但它对我们有用。我们修改了所有存储的过程以将数据复制到临时表中,然后运行临时表中的所有规则。然后我们将所有数据更新回主表,并在该更新上添加死锁重试代码。
  • 这似乎是合理的——事实上,这是一个很好的策略,可以最大限度地减少对活动表的影响。在临时表上进行所有多个数据更新以使其满足您的需要,然后只需将更改应用到一个或两个命令中,这是 imo 的一个好方法。大多数人没有意识到的一件事是,您可以将主键放在临时表上。如果临时表上的主键与最终表相同,那么它可以使插入/更新更快,因为索引匹配(数据已经正确预排序)。
猜你喜欢
  • 2015-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 2012-04-22
  • 1970-01-01
相关资源
最近更新 更多