【问题标题】:Lock Escalation - What's happening here?锁升级 - 这里发生了什么?
【发布时间】:2010-12-14 18:52:33
【问题描述】:

在 SQL Server 2008 中更改表(删除列)时,我单击了“生成更改脚本”按钮,我注意到它生成的更改脚本删除了列,说“开始”,然后运行附加的 ALTER TABLE 语句似乎将表的锁定升级设置为“TABLE”。示例:

ALTER TABLE dbo.Contract SET (LOCK_ESCALATION = TABLE)

我还应该注意,这是更改脚本所做的最后一件事。它在这里做什么,为什么将 LOCK_ESCALATION 设置为 TABLE?

【问题讨论】:

    标签: sql sql-server sql-server-2008


    【解决方案1】:

    Lock Escalation”是 SQL 处理大型更新锁定的方式。当 SQL 要更改大量行时,数据库引擎使用更少、更大的锁(例如整个表)而不是锁定许多较小的东西(例如行锁)会更有效。

    但是当您有一个巨大的表时,这可能会出现问题,因为锁定整个表可能会长时间锁定其他查询。这就是权衡:许多小粒度锁比更少(或一个)粗粒度锁慢,如果一个进程正在等待另一个进程,那么多个查询锁定表的不同部分可能会导致死锁。

    SQL 2008 中新增了一个表级选项LOCK_ESCALATION,它允许控制锁升级。默认情况下,“TABLE”允许锁一直升级到表级别。在大多数情况下,DISABLE 可防止锁定升级到整个表。 AUTO 允许表锁,除非表是分区的,在这种情况下,锁只构成分区级别。请参阅this blog post 了解更多信息。

    我怀疑 IDE 在重新创建表时添加了此设置,因为 TABLE 是 SQL 2008 中的默认设置。请注意,SQL 2005 不支持 LOCK_ESCALATION,因此如果尝试运行2005 实例上的脚本。此外,由于 TABLE 是默认值,因此您可以在重新运行脚本时安全地删除该行。

    另请注意,在此设置出现之前的 SQL 2005 中,所有锁都可以升级到表级别 - 换句话说,“TABLE”是 SQL 2005 上的唯一设置。

    【讨论】:

    • @dma_k - 此选项与 CREATE TABLE 无关,因为该表尚不存在,因此没有什么可锁定的。
    • 但是为什么在 SSMS 中设计表时,LOCK_ESCALATION 语句after 是更改脚本中的初始 ALTER TABLE 语句?到那时,工作肯定已经完成了。不应该是之前改变表的结构吗?
    • @DaveBoltman - SET 是 ALTER TABLE 语句的一部分。这不是一个单独的声明。见docs.microsoft.com/en-us/sql/t-sql/statements/…
    • JustinGrant,@DaveBoltman 的问题仍然存在。 SSMS 为添加新列生成的脚本有两个单独的ALTER TABLE 语句。首先是ALTER TABLE ADD column,然后是GO,然后是第二个ALTER TABLE SET LOCK_ESCALATION=TABLE,然后是第二个GO。所以,LOCK_ESCALATION 是在添加列之后设置的。事后设置它有什么意义?这两个ALTER TABLE 语句被包装在一个事务中,但仍然在设置LOCK_ESCALATION 之前添加了列。我想我会进一步挖掘并写另一个答案。
    【解决方案2】:

    您可以通过在运行脚本的主要部分之前和之后比较此值来检查是否需要在脚本中包含 LOCK_ESCALATION 语句:

    SELECT lock_escalation_desc FROM sys.tables WHERE name='yourtablename'
    

    就我而言,更改表以删除或添加约束似乎不会修改此值。

    【讨论】:

      【解决方案3】:

      Justin Grant 的回答解释了 LOCK_ESCALATION 设置的一般作用,但遗漏了一个重要细节,也没有解释为什么 SSMS 会生成设置它的代码。特别是,LOCK_ESCALATION 被设置为脚本中的最后一条语句,这看起来很奇怪。

      我做了很少的测试,这是我对这里发生的事情的理解。

      短版

      添加、删除或更改列的 ALTER TABLE 语句隐式地在表上采用模式修改 (SCH-M) 锁,这与表的 LOCK_ESCALATION 设置无关。 LOCK_ESCALATION 影响 DML 语句期间的锁定行为(INSERTUPDATEDELETE 等),而不是在 DDL 语句期间(ALTER)。 SCH-M 锁始终是整个数据库对象的锁,本例中为表。

      这很可能是混乱的来源。

      SSMS 在所有情况下都将ALTER TABLE <TableName> SET (LOCK_ESCALATION = ...) 语句添加到其脚本中,即使在不需要时也是如此。在需要此语句的情况下,添加它以保留表的当前设置,不锁定表在更改表时以某种特定方式schema这发生在那个脚本中。

      换句话说,表被第一个ALTER TABLE ALTER COLUMN 语句上的SCH-M 锁锁定,而更改表模式的所有工作都已完成。最后一个ALTER TABLE SET LOCK_ESCALATION 语句不影响它。它只会影响该表的未来 DML 语句(INSERTUPDATEDELETE 等)。

      乍一看,SET LOCK_ESCALATION = TABLE 确实与我们正在更改整个表的事实有关(我们在此处更改其架构),但它具有误导性。

      加长版

      在某些情况下更改表时,SSMS 会生成一个脚本来重新创建整个表,而在一些更简单的情况下(例如添加或删除列),该脚本不会重新创建表。

      我们以这个示例表为例:

      CREATE TABLE [dbo].[Test](
          [ID] [int] NOT NULL,
          [Col1] [nvarchar](50) NOT NULL,
          [Col2] [int] NOT NULL,
       CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED 
      (
          [ID] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO
      

      每个表都有一个LOCK_ESCALATION 设置,默认设置为TABLE。 让我们在这里改变它:

      ALTER TABLE dbo.Test SET (LOCK_ESCALATION = DISABLE)
      

      现在,如果我尝试在 SSMS 表设计器中更改 Col1 类型,SSMS 会生成一个重新创建整个表的脚本:

      BEGIN TRANSACTION
      SET QUOTED_IDENTIFIER ON
      SET ARITHABORT ON
      SET NUMERIC_ROUNDABORT OFF
      SET CONCAT_NULL_YIELDS_NULL ON
      SET ANSI_NULLS ON
      SET ANSI_PADDING ON
      SET ANSI_WARNINGS ON
      COMMIT
      BEGIN TRANSACTION
      GO
      CREATE TABLE dbo.Tmp_Test
          (
          ID int NOT NULL,
          Col1 nvarchar(10) NOT NULL,
          Col2 int NOT NULL
          )  ON [PRIMARY]
      GO
      ALTER TABLE dbo.Tmp_Test SET (LOCK_ESCALATION = DISABLE)
      GO
      IF EXISTS(SELECT * FROM dbo.Test)
           EXEC('INSERT INTO dbo.Tmp_Test (ID, Col1, Col2)
              SELECT ID, CONVERT(nvarchar(10), Col1), Col2 FROM dbo.Test WITH (HOLDLOCK TABLOCKX)')
      GO
      DROP TABLE dbo.Test
      GO
      EXECUTE sp_rename N'dbo.Tmp_Test', N'Test', 'OBJECT' 
      GO
      ALTER TABLE dbo.Test ADD CONSTRAINT
          PK_Test PRIMARY KEY CLUSTERED 
          (
          ID
          ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      
      GO
      COMMIT
      

      您可以在上面看到它为新创建的表设置了LOCK_ESCALATION。 SSMS 这样做是为了保留表的当前设置。 SSMS 会生成此行,即使设置的当前值是默认的TABLE 值。我想,如果将来这个默认值发生变化,只是为了安全和明确并防止未来可能出现的问题。这是有道理的。

      在此示例中,确实需要生成 SET LOCK_ESCALATION 语句,因为表是重新创建的,并且必须保留其设置。

      如果我尝试使用 SSMS 表设计器对表进行简单更改,例如添加新列,则 SSMS 会生成一个不会重新创建表的脚本:

      BEGIN TRANSACTION
      SET QUOTED_IDENTIFIER ON
      SET ARITHABORT ON
      SET NUMERIC_ROUNDABORT OFF
      SET CONCAT_NULL_YIELDS_NULL ON
      SET ANSI_NULLS ON
      SET ANSI_PADDING ON
      SET ANSI_WARNINGS ON
      COMMIT
      BEGIN TRANSACTION
      GO
      ALTER TABLE dbo.Test ADD
          NewCol nchar(10) NULL
      GO
      ALTER TABLE dbo.Test SET (LOCK_ESCALATION = DISABLE)
      GO
      COMMIT
      

      如您所见,它仍然添加了ALTER TABLE SET LOCK_ESCALATION 语句,尽管在这种情况下根本不需要它。第一个ALTER TABLE ... ADD 不会更改当前设置。我想,SSMS 开发人员认为,为了安全起见,尝试确定在什么情况下此 ALTER TABLE SET LOCK_ESCALATION 语句是多余的并始终生成它是不值得的。每次都加上这个语句没有坏处。

      再一次,表范围的LOCK_ESCALATION 设置无关紧要,而表架构通过ALTER TABLE 语句更改。 LOCK_ESCALATION 设置仅影响 DML 语句的锁定行为,例如 UPDATE

      最后,引用 ALTER TABLE 的一句话,强调我的:

      在 ALTER TABLE 中指定的更改会立即实施。如果 更改需要修改表中的行,ALTER TABLE 更新行。 ALTER TABLE 获取架构修改 (SCH-M) 锁定表以确保没有其他连接引用 甚至更改期间表的元数据,在线索引除外 最后需要非常短的 SCH-M 锁定的操作。在一个 ALTER TABLE…SWITCH 操作,在源上都获得了锁 和目标表。对表所做的修改被记录并 完全可以恢复。影响非常大的所有行的更改 表,例如删除一列,或者在某些版本的 SQL Server 上, 添加具有默认值的 NOT NULL 列,可能需要很长时间 完成并生成许多日志记录。这些 ALTER TABLE 语句 执行时应与任何 INSERT、UPDATE 或 DELETE 一样小心 影响许多行的语句。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-12-18
        • 1970-01-01
        • 2021-08-04
        • 1970-01-01
        • 2013-11-03
        • 1970-01-01
        • 2017-11-14
        相关资源
        最近更新 更多