锁定升级 - 这里发生了什么?

Jam*_*der 128 sql sql-server sql-server-2008

在SQL Server 2008中更改表(删除列)时,我单击了Generate Change Script按钮,我发现它生成的更改脚本会删除列,说"go",然后运行另一个看似要设置的ALTER TABLE语句表的锁升级为"TABLE".例:

ALTER TABLE dbo.Contract SET (LOCK_ESCALATION = TABLE)
Run Code Online (Sandbox Code Playgroud)

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

Jus*_*ant 161

" 锁定升级 "是SQL处理大型更新锁定的方式.当SQL要更改大量行时,数据库引擎更有效地采用更少,更大的锁(例如整个表)而不是锁定许多较小的东西(例如行锁).

但是当你有一个庞大的表时,这可能会有问题,因为对整个表进行锁定可能会长时间锁定其他查询.这是权衡:许多小粒度锁比较少(或一个)粗粒度锁慢,并且如果一个进程在另一个进程上等待,则有多个查询锁定表的不同部分会导致死锁的可能性.

LOCK_ESCALATIONSQL 2008中有一个表级选项,它允许控制锁升级.默认情况下,"TABLE"允许锁一直升级到表级.在大多数情况下,DISABLE会阻止锁升级到整个表.AUTO允许表锁,除非表是分区的,在这种情况下,锁仅限于分区级别.有关详细信息,请参阅此博客文章.

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

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

  • @dma_k - 此选项与`CREATE TABLE`无关,因为该表尚不存在,因此无法锁定. (6认同)
  • @DaveBoltman-SET是ALTER TABLE语句的一部分。这不是单独的声明。请参阅https://docs.microsoft.com/zh-cn/sql/t-sql/statements/alter-table-transact-sql (2认同)
  • 贾斯汀·格兰特(JustinGrant)仍然站在@DaveBoltman的问题上。SSMS生成的用于添加新列的脚本具有两个单独的“ ALTER TABLE”语句。首先是“ ALTER TABLE ADD column”,然后是“ GO”,然后是第二个“ ALTER TABLE SET LOCK_ESCALATION = TABLE”,然后是第二个“ GO”。因此,在添加列后设置了“ LOCK_ESCALATION”。事后设置它有什么意义?这两个ALTER TABLE语句包装在一个事务中,但是仍然在设置LOCK_ESCALATION之前添加了该列。我想我会进一步探讨并写出另一个答案。 (2认同)

Bog*_*ets 10

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

SELECT lock_escalation_desc FROM sys.tables WHERE name='yourtablename'
Run Code Online (Sandbox Code Playgroud)

在我的情况下,改变表删除或添加约束似乎不会修改此值.


Vla*_*nov 8

贾斯汀·格兰特(Justin Grant)的答案解释了LOCK_ESCALATION设置的一般含义,但是错过了一个重要的细节,并且没有解释SSMS为何生成设置它的代码。特别是,将LOCK_ESCALATION设置为脚本中的最后一条语句看起来很奇怪。

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

简洁版本

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

这很可能是造成混乱的地方。

ALTER TABLE <TableName> SET (LOCK_ESCALATION = ...)在所有情况下,即使不需要,SSMS 也会将该语句添加到其脚本中。在需要此语句的情况下,添加该语句是为了保留表的当前设置,而不是在对该脚本中发生的表模式进行更改期间以某种特定方式锁定表

换句话说,该表锁定,就先SCH-M锁ALTER TABLE ALTER COLUMN,同时改变模式表的所有工作完成陈述。最后一条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
Run Code Online (Sandbox Code Playgroud)

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

ALTER TABLE dbo.Test SET (LOCK_ESCALATION = DISABLE)
Run Code Online (Sandbox Code Playgroud)

现在,如果我尝试Col1在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
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
Run Code Online (Sandbox Code Playgroud)

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

在此示例中,确实需要生成该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
Run Code Online (Sandbox Code Playgroud)

如您所见,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语句一样谨慎地执行。