Answers:
“ 锁定升级 ”是SQL处理大型更新锁定的方式。当SQL要更改很多行时,数据库引擎采用较少的较大的锁(例如整个表)而不是锁定许多较小的内容(例如行锁)会更有效率。
但是,当您有一个巨大的表时,这可能会带来问题,因为对整个表进行锁定可能会长时间锁定其他查询。这是一个权衡:许多小粒度锁比少数(或一个)粗粒度锁要慢,并且如果一个进程在等待另一个进程,则有多个查询锁定表的不同部分会产生死锁的可能性。
LOCK_ESCALATION
SQL 2008中有一个表级选项,它可以控制锁升级。默认值“ TABLE”允许锁一直升级到表级别。在大多数情况下,DISABLE可以防止将锁升级到整个表。AUTO允许表锁,除非表已分区(在这种情况下,锁仅由分区级别组成)。有关更多信息,请参见此博客文章。
我怀疑IDE在重新创建表时会添加此设置,因为TABLE是SQL 2008中的默认设置。请注意,SQL 2005不支持LOCK_ESCALATION,因此如果尝试在Windows 2000上运行脚本,则需要删除它。 2005年实例。另外,由于TABLE是默认设置,因此可以在重新运行脚本时安全地删除该行。
还要注意,在存在此设置的SQL 2005中,所有锁都可以升级到表级别-换句话说,“ TABLE”是SQL 2005上的唯一设置。
CREATE TABLE
因为该表尚不存在,因此没有什么可锁定的。
ALTER TABLE
语句。首先ALTER TABLE ADD column
,然后GO
,第二ALTER TABLE SET LOCK_ESCALATION=TABLE
,然后第二GO
。因此,LOCK_ESCALATION
在添加列后设置。事后设置它有什么意义?这两个ALTER TABLE
语句包装在一个事务中,但是仍然在LOCK_ESCALATION
设置之前添加了该列。我想我会进一步探讨并写出另一个答案。
贾斯汀·格兰特(Justin Grant)的答案解释了LOCK_ESCALATION
设置的一般含义,但是错过了一个重要的细节,并且没有解释SSMS为何生成设置它的代码。特别是,将LOCK_ESCALATION
设置为脚本中的最后一条语句看起来很奇怪。
我做了很少的测试,这是我对这里发生的事情的理解。
ALTER TABLE
隐式添加,删除或更改列的语句将对表进行模式修改(SCH-M)锁定,这与LOCK_ESCALATION
表的设置无关。LOCK_ESCALATION
影响在DML语句(锁定行为INSERT
,UPDATE
,DELETE
等),而不是在DDL语句(ALTER
)。SCH-M锁始终是整个数据库对象(在此示例中为表)的锁。
这很可能是造成混乱的地方。
ALTER TABLE <TableName> SET (LOCK_ESCALATION = ...)
在所有情况下,即使不需要,SSMS 也会将该语句添加到其脚本中。在需要此语句的情况下,添加该语句是为了保留表的当前设置,而不是在对该脚本中发生的表模式进行更改期间以某种特定方式锁定表。
换句话说,该表被锁定,就先SCH-M锁ALTER TABLE ALTER COLUMN
,同时改变模式表的所有工作完成陈述。最后一条ALTER TABLE SET LOCK_ESCALATION
语句不影响它。它只会影响未来的DML语句(INSERT
,UPDATE
,DELETE
等),该表。
乍一看,它确实似乎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)
现在,如果我尝试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
您可以在上面看到它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
如您所见,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语句一样谨慎地执行。