1. 处理死锁
MicrosoftSQL Server 数据库引擎 实例选择某事务作为死锁牺牲品后,将终止当前批处理,回滚事务并将 1205 号错误消息返回应用程序, 由于可以选择任何提交 Transact-SQL 查询的应用程序作为死锁牺牲品,应用程序应该有能够捕获 1205 号错误消息的错误处理程序。如果应用程序没有捕获到错误,则会继续处理而未意识到已经回滚其事务且已发生错误。通过实现捕获 1205 号错误消息的错误处理程序,使应用程序得以处理该死锁情况并采取补救措施(例如,可以自 动重新提交陷入死锁中的查询)。通过自动重新提交查询,用户不必知道发生了死锁。应用程序在重新提交其查询前应短暂暂停。这样会给死锁涉及的另一个事务一个机会来完成并释放构成死锁循环一部分的该事务的锁。这将把重新提交的查询请求其锁时,死锁重新发生的可能性降到最低。
2. 动态锁定
使用低级锁(如行锁)可以降低两个事务同时在相同数据块上请求锁的可能性,从而提高并发性。使用低级锁还会增加锁的数量以及管理锁所需的资源。使用高级表锁或页锁可以减少开销,但代价是降低了并发性。
Microsoft SQL Server 数据库引擎 使用动态锁定策略确定最经济的锁。执行查询时,数据库引擎 会根据架构和查询的特点自动决定最合适的锁。例如,为了缩减锁定的开销,优化器可能在执行索引扫描时在索引中选择页级锁。
动态锁定具有下列优点:
· 简化数据库管理。数据库管理员不必调整锁升级阈值。
· 提高性能。数据库引擎 通过使用适合任务的锁使系统开销减至最小。
· 应用程序开发人员可以集中精力进行开发。数据库引擎 将自动调整锁定。
3. 锁粒度
Microsoft SQL Server 数据库引擎 具有多粒度锁定,允许一个事务锁定不同类型的资源。为了尽量减少锁定的开销,数据库引擎 自动将资源锁定在适合任务的级别。锁定在较小的粒度(例如行)可以提高并发度,但开销较高,因为如果锁定了许多行,则需要持有更多的锁。锁定在较大的粒度(例如表)会降低了并发度,因为锁定整个表限制了其他事务对表中任意部分的访问。但其开销较低,因为需要维护的锁较少。数据库引擎 通常必须获取多粒度级别上的锁才能完整地保护资源。这组多粒度级别上的锁称为锁层次结构。例如,为了完整地保护对索引的读取,数据库引擎 实例可能必须获取行上的共享锁以及页和表上的意向共享锁。下表列出了数据库引擎 可以锁定的资源。
资源 | 说明 |
RID | 用于锁定堆中的单个行的行标识符。 |
KEY | 索引中用于保护可序列化事务中的键范围的行锁。 |
PAGE | 数据库中的 8 KB 页,例如数据页或索引页。 |
EXTENT | 一组连续的八页,例如数据页或索引页。 |
HOBT | 堆或 B 树。保护索引或没有聚集索引的表中数据页堆的锁。 |
TABLE | 包括所有数据和索引的整个表。 |
FILE | 数据库文件。 |
APPLICATION | 应用程序专用的资源。 |
METADATA | 元数据锁。 |
ALLOCATION_UNIT | 分配单元。 |
DATABASE | 整个数据库。 |
4. 编写有效的事务
尽可能使事务保持简短很重要。当事务启动后,数据库管理系统 (DBMS) 必须在事务结束之前保留很多资源,以保护事务的原子性、一致性、隔离性和持久性 (ACID) 属性。如果修改数据,则必须用排他锁保护修改过的行,以防止任何其他事务读取这些行,并且必须将排他锁控制到提交或回滚事务时为止。根据事务隔离级别设置,SELECT 语句可以获取必须控制到提交或回滚事务时为止的锁。特别是在有很多用户的系统中,必须尽可能使事务保持简短以减少并发连接间的资源锁定争夺。在有少量用户的系统中,运行时间长、效率低的事务可能不会成为问题,但是在有上千个用户的系统中,将不能忍受这样的事务。
以下是编写有效事务的指导原则:
· 不要在事务处理期间要求用户输入。
在事务启动之前,获得所有需要的用户输入。如果在事务处理期间还需要其他用户输入,则回滚当前事务,并在提供了用户输入之后重新启动该事务。即使用户立即响应,作为人,其反应时间也要比计算机慢得多。事务占用的所有资源都要保留相当长的时间,这有可能会造成阻塞问题。如果用户没有响应,事务仍然会保持活动状态,从而锁定关键资源直到用户响应为止,但是用户可能会几分钟甚至几个小时都不响应。
· 在浏览数据时,尽量不要打开事务。
在所有预备的数据分析完成之前,不应启动事务。
· 尽可能使事务保持简短。
在知道要进行的修改之后,启动事务,执行修改语句,然后立即提交或回滚。只有在需要时才打开事务。
· 若要减少阻塞,请考虑针对只读查询使用基于行版本控制的隔离级别。有关详细信息,请参阅使用基于行版本控制的隔离级别。
· 灵活地使用更低的事务隔离级别。
可以很容易地编写出许多使用只读事务隔离级别的应用程序。并不是所有事务都要求可序列化的事务隔离级别。
· 灵活地使用更低的游标并发选项,例如开放式并发选项。
在并发更新的可能性很小的系统中,处理“别人在您读取数据后更改了数据”的偶然错误的开销要比在读取数据时始终锁定行的开销小得多。
· 在事务中尽量使访问的数据量最小。
这样可以减少锁定的行数,从而减少事务之间的争夺。
为了防止并发问题和资源问题,应小心管理隐式事务。使用隐式事务时,COMMIT 或 ROLLBACK 后的下一个 Transact-SQL 语句会自动启动一个新事务。这可能会在应用程序浏览数据时(甚至在需要用户输入时)打开一个新事务。在完成保护数据修改所需的最后一个事务之后,应关闭隐性事务,直到再次需要使用事务来保护数据修改。此过程使 SQL Server 数据库引擎 能够在应用程序浏览数据以及获取用户输入时使用自动提交模式。
另外,启用快照隔离级别后,尽管新事务不会控制锁,但是长时间运行的事务将阻止从 tempdb 中删除旧版本。
5. 锁兼容性(数据库引擎)
锁兼容性控制多个事务能否同时获取同一资源上的锁。如果资源已被另一事务锁定,则仅当请求锁的模式与现有锁的模式相兼容时,才会授予新的锁请求。如果请求锁的模式与现有锁的模式不兼容,则请求新锁的事务将等待释放现有锁或等待锁超时间隔过期。例如,没有与排他锁兼容的锁模式。如果具有排他锁(X 锁),则在释放排他锁(X 锁)之前,其他事务均无法获取该资源的任何类型(共享、更新或排他)的锁。另一种情况是,如果共享锁(S 锁)已应用到资源,则即使第一个事务尚未完成,其他事务也可以获取该项的共享锁或更新锁(U 锁)。但是,在释放共享锁之前,其他事务无法获取排他锁。
下表显示了最常见的锁模式的兼容性。
| 现有授予模式 |
|
|
|
|
|
请求模式 | IS | S | U | IX | SIX | X |
意向共享 (IS) | 是 | 是 | 是 | 是 | 是 | 否 |
共享 (S) | 是 | 是 | 是 | 否 | 否 | 否 |
更新 (U) | 是 | 是 | 否 | 否 | 否 | 否 |
意向排他 (IX) | 是 | 否 | 否 | 是 | 否 | 否 |
意向排他共享 (SIX) | 是 | 否 | 否 | 否 | 否 | 否 |
排他 (X) | 否 | 否 | 否 | 否 | 否 | 否 |
注意: |
意向排他锁(IX 锁)与 IX 锁模式兼容,因为 IX 表示打算只更新部分行而不是所有行。还允许其他事务尝试读取或更新部分行,只要这些行不是其他事务当前更新的行即可。 |
完整的锁兼容性矩阵
使用下表可以确定 Microsoft SQL Server 2005 中所有可用的锁模式的兼容性。
7.锁分区
对于大型计算机系统,在经常引用的对象上放置的锁可能会变成性能瓶颈,因为获取和释放锁对内部锁资源造成了争用。锁分区通过将单个锁资源拆分为多个锁资源而提高了锁性能。此功能只适用于拥有 16 个或更多 CPU 的系统,它是自动启用的,而且无法禁用。只有对象锁可以分区。
注意: |
拥有子类型的对象锁不能分区。有关详细信息,请参阅 sys.dm_tran_locks。 |
了解锁分区
锁任务访问几个共享资源,其中两个通过锁分区进行优化:
· 调节锁。它控制对锁资源(例如行或表)的访问。
不进行锁分区,一个调节锁就得管理单个锁资源的所有锁请求。在具有大量活动的系统上,在锁请求等待释放调节锁时会出现资源争用的现象。在这种情况下,获取锁可能变成了一个瓶颈,并且可能会对性能造成负面影响。
为了减少对单个锁资源的争用,锁分区将单个锁资源拆分成多个锁资源,以便将负荷分布到多个调节锁上。
· 内存。它用于存储锁资源结构。
获取调节锁后,锁结构将存储在内存中,然后即可对其进行访问和可能的修改。将锁访问分布到多个资源中有助于消除在 CPU 之间传输内存块的需要,这有助于提高性能。
实现和监视锁分区
默认情况下,对于具有 16 个或更多 CPU 的系统,锁分区是打开的。启用锁分区后,将在 SQL Server 错误日志中记录一条信息性消息。
获取已分区资源的锁时:
· 只能获取单个分区的 NL、SCH-S、IS、IU 和 IX 锁模式。
· 对于以分区 ID 0 开始并且按照分区 ID 顺序排列的所有分区,必须获取非 NL、SCH-S、IS、IU 和 IX模式的共享锁 (S)、排他锁 (X) 和其他锁。已分区资源的这些锁将比相同模式中未分区资源的锁占用更多的内存,因为每个分区都是一个有效的单独锁。内存的增加由分区数决定。Windows 性能监视器中 SQL Server 锁计数器将显示已分区和未分区锁所使用内存信息。
启动一个事务时,它将被分配给一个分区。对于此事务,可以分区的所有锁请求都使用分配给该事务的分区。按照此方法,不同事务对相同对象的锁资源的访问被分布到不同的分区中。
sys.dm_tran_locks 动态管理视图中的 resource_lock_partition 列为锁分区资源提供锁分区 ID。有关详细信息,请参阅 sys.dm_tran_locks。
在 SQL Server Profiler 中的 Locks 事件下,BigintData1 列为每个锁分区资源提供锁分区 ID。
使用锁分区
以下代码示例说明了锁分区。在这些示例中,为了显示一个具有 16 个 CPU 的计算机系统上的锁分区行为,在两个不同的会话中执行了两个事务。
这些 Transact-SQL 语句创建了后续示例中使用的测试对象。
USE AdventureWorks;
GO
-- Create a test table.
CREATE TABLE TestTable
(col1 int);
GO
-- Create a clustered index on the table.
CREATE CLUSTERED INDEX ci_TestTable
ONTestTable (col1);
GO
-- Populate the table.
INSERT INTO TestTable VALUES (1);
GO
示例 A
会话 1:
在一个事务中执行 SELECT 语句。由于 HOLDLOCK
锁提示的原因,此语句将获取并保留一个对此表的意向共享锁(IS 锁)(此例中忽略行锁和页锁)。IS 锁只能在分配给事务的分区中获取。对于此示例,假定 IS 锁是在 ID 为 7 的分区中获取的。
-- Start a transaction.
BEGIN TRANSACTION
-- ThisSELECT statement will acquire an IS lock on the table.
SELECTcol1
FROM TestTable
WITH(HOLDLOCK);
会话 2:
启动事务,在此事务下运行 SELECT 语句将获取共享锁(S 锁)并将其保留在表中。将获取所有分区的 S 锁,这将产生多个表锁,每个分区一个。例如,在具有 16 个 CPU 的系统上,将在锁分区 ID 0-15 中发出 16个 S 锁。因为 S 锁与分区 ID7 上由会话 1 中的事务持有的 IS 锁兼容,所以事务之间没有阻塞。
BEGIN TRANSACTION
SELECTcol1
FROM TestTable
WITH(TABLOCK, HOLDLOCK);
会话 1:
将在会话 1 下仍然活动的事务下执行以下 SELECT 语句。由于排他 (X) 表锁提示,事务将尝试获取表的 X 锁。但是,由会话 2 中的事务持有的 S 锁将阻塞分区 ID 0 的 X 锁。
SELECT col1
FROMTestTable
WITH(TABLOCKX);
示例 B
会话 1:
在一个事务中执行 SELECT 语句。由于 HOLDLOCK
锁提示的原因,此语句将获取并保留一个对此表的意向共享锁(IS 锁)(此例中忽略行锁和页锁)。IS 锁只能在分配给事务的分区中获取。对于此示例,假定 IS 锁是在 ID 为 6 的分区中获取的。
-- Start a transaction.
BEGIN TRANSACTION
-- ThisSELECT statement will acquire an IS lock on the table.
SELECTcol1
FROMTestTable
WITH (HOLDLOCK);
会话 2:
在一个事务中执行 SELECT 语句。由于 TABLOCKX
锁提示,事务将尝试获取表的排他锁(X 锁)。请记住,必须获取从分区 ID 0 开始的所有分区的 X 锁。将获取分区 ID 0-5 的 X 锁,但它会被为分区 ID 6 获取的 IS锁阻塞。
对于尚未获取 X 锁的分区 ID 7-15,其他事务可以继续获取锁。
BEGIN TRANSACTION
SELECTcol1
FROMTestTable
WITH(TABLOCKX, HOLDLOCK);
8. 数据库引擎中的隔离级别
事务指定一个隔离级别,该隔离级别定义一个事务必须与其他事务所进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读或幻读)的角度进行描述。
事务隔离级别控制:
· 读取数据时是否占用锁以及所请求的锁类型。
· 占用读取锁的时间。
· 引用其他事务修改的行的读取操作是否:
· 在该行上的排他锁被释放之前阻塞其他事务。
· 检索在启动语句或事务时存在的行的已提交版本。
· 读取未提交的数据修改。
选择事务隔离级别不影响为保护数据修改而获取的锁。事务总是在其修改的任何数据上获取排他锁并在事务完成之前持有该锁,不管为该事务设置了什么样的隔离级别。对于读取操作,事务隔离级别主要定义保护级别,以防受到其他事务所做更改的影响。
较低的隔离级别可以增强许多用户同时访问数据的能力,但也增加了用户可能遇到的并发副作用(例如脏读或丢失更新)的数量。相反,较高的隔离级别减少了用户可能遇到的并发副作用的类型,但需要更多的系统资源,并增加了一个事务阻塞其他事务的可能性。应平衡应用程序的数据完整性要求与每个隔离级别的开销,在此基础上选择相应的隔离级别。最高隔离级别(可序列化)保证事务在每次重复读取操作时都能准确检索到相同的数据,但需要通过执行某种级别的锁定来完成此操作,而锁定可能会影响多用户系统中的其他用户。最低隔离级别(未提交读)可以检索其他事务已经修改、但未提交的数据。在未提交读中,所有并发副作用都可能发生,但因为没有读取锁定或版本控制,所以开销最少。
数据库引擎隔离级别
SQL-99标准定义了下列隔离级别,Microsoft SQL Server 数据库引擎支持所有这些隔离级别:
· 未提交读(隔离事务的最低级别,只能保证不读取物理上损坏的数据)
· 已提交读(数据库引擎的默认级别)
· 可重复读
· 可序列化(隔离事务的最高级别,事务之间完全隔离)
SQL Server 2005 还支持使用行版本控制的两个事务隔离级别。一个是已提交读隔离的新实现,另一个是新事务隔离级别(快照)。
· 当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,已提交读隔离使用行版本控制提供语句级读取一致性。读取操作只需要 SCH-S 表级别的锁,不需要页锁或行锁。当READ_COMMITTED_SNAPSHOT 数据库选项设置为 OFF(默认设置)时,已提交读隔离的行为方式与其在早期版本 SQLServer 中行为方式相同。两个实现都满足已提交读隔离的 ANSI 定义。
· 快照隔离级别使用行版本控制来提供事务级别的读取一致性。读取操作不获取页锁或行锁,只获取 SCH-S 表锁。读取其他事务修改的行时,读取操作将检索启动事务时存在的行的版本。将ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON 时,将启用快照隔离。默认情况下,用户数据库的此选项设置为 OFF。
下表显示了不同隔离级别允许的并发副作用。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读 | 是 | 是 | 是 |
已提交读 | 否 | 是 | 是 |
可重复读 | 否 | 否 | 是 |
快照 | 否 | 否 | 否 |
可序列化 | 否 | 否 | 否 |
有关每个事务隔离级别控制的特定类型的锁或行版本控制的详细信息,请参阅SETTRANSACTION ISOLATION LEVEL (Transact-SQL)。
9. 并发影响
SQLServer 2005
修改数据的用户会影响同时读取或修改相同数据的其他用户。即这些用户可以并发访问数据。如果数据存储系统没有并发控制,则用户可能会看到以下负面影响:
· 丢失更新。
· 未提交的依赖关系(脏读)。
· 不一致的分析(不可重复读)。
· 幻读。
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其他事务的存在。最后的更新将覆盖由其他事务所做的更新,这将导致数据丢失。
例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
当第二个事务选择其他事务正在更新的行时,会发生未提交的依赖关系问题。第二个事务正在读取的数据还没有提交并且可能由更新此行的事务所更改。
例如,一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该副本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应视为从未存在过。如果在第一个编辑人员保存最终更改并提交事务之前,任何人都不能读取更改的文档,则可以避免此问题。
当第二个事务多次访问同一行而且每次读取不同的数据时,会发生不一致的分析问题。不一致的分析与未提交的依赖关系类似,因为其他事务也是正在更改第二个事务正在读取的数据。但是,在不一致的分析中,第二个事务读取的数据是由已进行了更改的事务提交的。此外,不一致的分析涉及多次(两次或更多)读取同一行,而且每次信息都被其他事务更改,因此我们称之为“不可重复读”。
例如,编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果在编辑人员完成最后一次读取文档之前,作者不能更改文档,则可以避免此问题。
当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻读问题。由于其他事务的删除操作,事务第一次读取的行的范围显示有一行不再存在于第二次或后续读取内容中。同样,由于其他事务的插入操作,事务第二次或后续读取的内容显示有一行并不存在于原始读取内容中。
例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主副本时,发现作者已将未编辑的新材料添加到该文档中。与不可重复读的情况相似,如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免此问题
10. 键范围锁定
在使用可序列化事务隔离级别时,对于 Transact-SQL 语句读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。可序列化隔离级别要求每当在事务期间执行任一查询时,该查询都必须获取相同的行集。键范围锁可防止其他事务插入其键值位于可序列化事务读取的键值范围内的新行,从而确保满足此要求。
键范围锁可防止幻读。通过保护行之间的键范围,它还可以防止对事务访问的记录集进行幻插入。
键范围锁放置在索引上,指定开始键值和结束键值。此锁将阻止任何要插入、更新或删除任何带有该范围内的键值的行的尝试,因为这些操作会首先获取索引上的锁。例如,可序列化事务可能发出了一个 SELECT 语句,以读取其键值介于 'AAA' 与 'CZZ'之间的所有行。从 'AAA'到 'CZZ' 范围内的键值上的键范围锁可阻止其他事务插入带有该范围内的键值(例如 'ADG'、'BBD' 或 'CAL')的行。
键范围锁模式
键范围锁包括按范围-行格式指定的范围组件和行组件:
· 范围表示保护两个连续索引项之间的范围的锁模式。
· 行表示保护索引项的锁模式。
· 模式表示使用的组合锁模式。键范围锁模式由两部分组成。第一部分表示用于锁定索引范围 (RangeT)的锁类型,第二部分表示用于锁定特定键 (K)的锁类型。这两部分用连字符 (-) 连接,例如 RangeT-K。
范围 | 行 | 模式 | 说明 |
RangeS | S | RangeS-S | 共享范围,共享资源锁;可序列化范围扫描。 |
RangeS | U | RangeS-U | 共享范围,更新资源锁;可序列化更新扫描。 |
RangeI | Null | RangeI-N | 插入范围,空资源锁;用于在索引中插入新键之前测试范围。 |
RangeX | X | RangeX-X | 排他范围,排他资源锁;用于更新范围中的键。 |
注意: |
内部空锁模式与所有其他锁模式兼容。 |
键范围锁模式有一个兼容性矩阵,表示哪些锁与在重叠键和范围上获取的其他锁兼容。有关完整的锁兼容性矩阵,请参阅锁兼容性。
| 现有的授权模式 |
|
|
|
|
|
|
请求的模式 | S | U | X | RangeS-S | RangeS-U | RangeI-N | RangeX-X |
共享 (S) | 是 | 是 | 否 | 是 | 是 | 是 | 否 |
更新 (U) | 是 | 否 | 否 | 是 | 否 | 是 | 否 |
排他 (X) | 否 | 否 | 否 | 否 | 否 | 是 | 否 |
RangeS-S | 是 | 是 | 否 | 是 | 是 | 否 | 否 |
RangeS-U | 是 | 否 | 否 | 是 | 否 | 否 | 否 |
RangeI-N | 是 | 是 | 是 | 否 | 否 | 是 | 否 |
RangeX-X | 否 | 否 | 否 | 否 | 否 | 否 | 否 |
转换锁
当键范围锁与其他锁重叠时,将创建转换锁。
锁 1 | 锁 2 | 转换锁 |
S | RangeI-N | RangeI-S |
U | RangeI-N | RangeI-U |
X | RangeI-N | RangeI-X |
RangeI-N | RangeS-S | RangeX-S |
RangeI-N | RangeS-U | RangeX-U |
在不同的复杂环境下(有时是在运行并发进程时),可以在一小段时间内观察到转换锁。
可序列化范围扫描、单独提取、删除和插入
键范围锁定确保以下操作是可序列化的:
· 范围扫描查询
· 对不存在的行的单独提取
· 删除操作
· 插入操作
必须满足下列条件才能发生键范围锁定:
· 事务隔离级别必须设置为 SERIALIZABLE。
· 查询处理器必须使用索引来实现范围筛选谓词。例如,SELECT 语句中的 WHERE 子句可以用以下谓词建立范围条件:ColumnXBETWEEN N'AAA' AND N'CZZ'。仅当 ColumnX 被一个索引键覆盖时,才能获取键范围锁。
示例
以下表和索引用作随后的键范围锁定示例的基础。
范围扫描查询
为了确保范围扫描查询是可序列化的,每次在同一事务中执行的相同查询应返回同样的结果。其他事务不能在范围扫描查询中插入新行;否则这些插入将成为幻插入。例如,以下查询将使用上图中的表和索引:
SELECT name
FROM mytable
WHERE name BETWEEN 'A' AND 'C';
键范围锁放置在与数据行范围(名称在值 Adam 与 Dale 之间的行)对应的索引项上,以防止添加或删除满足上述查询条件的新行。尽管此范围中的第一个名称是 Adam,但是此索引项上的 RangeS-S 模式键范围锁确保了以字母 A 开头的新名称(例如 Abigail)不能添加在Adam 之前。同样,Dale 索引项上的 RangeS-S 键范围锁确保了以字母 C 开头的新名称(例如 Clive)不能添加在 Carlos 之后。
注意: |
包含的 RangeS-S 锁数量为 n+1,此处 n 是满足查询条件的行数。 |
对不存在的数据的单独提取
如果事务中的查询试图选择不存在的行,则以后在相同的事务中发出这一查询时,必须返回相同的结果。不允许其他事务插入不存在的行。例如,对于下面的查询:
SELECT name
FROMmytable
WHERE name= 'Bill';
键范围锁放置在与从 Ben
到 Bing
的名称范围对应的索引项上,因为名称 Bill
将插入到这两个相邻的索引项之间。RangeS-S 模式键范围锁放置在索引项 Bing
上。这样可阻止其他任何事务在索引项 Ben
与 Bing
之间插入值(例如 Bill
)。
删除操作
在事务中删除值时,在事务执行删除操作期间不必锁定该值所属的范围。锁定删除的键值直至事务结束足以保持可序列化性。例如,对于下面的 DELETE 语句:
DELETE mytable
WHERE name = 'Bob';
排他锁(X 锁)放置在与名称 Bob
对应的索引项上。其他事务可以在删除的值 Bob
的前后插入或删除值。但是任何试图读取、插入或删除值 Bob
的事务都将被阻塞,直到删除的事务提交或回滚为止。
可以使用三个基本锁模式执行范围删除:行锁、页锁或表锁。行、页或表锁定策略由查询优化器确定,或者可以由用户通过优化程序提示(例如 ROWLOCK、PAGLOCK 或 TABLOCK)来指定。当使用PAGLOCK 或 TABLOCK 时,如果从某个索引页中删除所有的行,则数据库引擎 将立即释放该索引页。相反,当使用 ROWLOCK 时,所有删除的行只是标记为已删除;以后通过后台任务从索引页中删除它们。
插入操作
在事务中插入值时,在事务执行插入操作期间不必锁定该值所属的范围。锁定插入的键值直至事务结束足以维护可序列化性。例如,对于下面的 INSERT 语句:
INSERT mytable VALUES('Dan');
RangeI-N 模式键范围锁放置在与名称 David 对应的索引项上,以测试范围。如果已授权锁,则插入 Dan
,并且排他锁(X 锁)将放置在值 Dan
上。RangeI-N 模式键范围锁仅对测试范围是必需的,而不在执行插入操作的事务期间保留。其他事务可以在插入的值 Dan
的前后插入或删除值。但是,任何试图读取、插入或删除值 Dan
的事务都将被阻塞,直到插入的事务提交或回滚为止。
11. 锁模式
Microsoft SQL Server 数据库引擎使用不同的锁模式锁定资源,这些锁模式确定了并发事务访问资源的方式。
下表显示了数据库引擎使用的资源锁模式。
锁模式 | 说明 |
共享 (S) | 用于不更改或不更新数据的读取操作,如 SELECT 语句。 |
更新 (U) | 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 |
排他 (X) | 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时对同一资源进行多重更新。 |
意向 | 用于建立锁的层次结构。意向锁包含三种类型:意向共享 (IS)、意向排他 (IX) 和意向排他共享 (SIX)。 |
架构 | 在执行依赖于表架构的操作时使用。架构锁包含两种类型:架构修改 (Sch-M) 和架构稳定性 (Sch-S)。 |
大容量更新 (BU) | 在向表进行大容量数据复制且指定了 TABLOCK 提示时使用。 |
键范围 | 当使用可序列化事务隔离级别时保护查询读取的行的范围。确保再次运行查询时其他事务无法插入符合可序列化事务的查询的行。 |
共享锁(S 锁)允许并发事务在封闭式并发控制下读取 (SELECT) 资源。有关详细信息,请参阅并发控制的类型。资源上存在共享锁(S 锁)时,任何其他事务都不能修改数据。读取操作一完成,就立即释放资源上的共享锁(S 锁),除非将事务隔离级别设置为可重复读或更高级别,或者在事务持续时间内用锁定提示保留共享锁(S 锁)。
更新锁(U 锁)可以防止常见的死锁。在可重复读或可序列化事务中,此事务读取数据 [获取资源(页或行)的共享锁(S 锁)],然后修改数据 [此操作要求锁转换为排他锁(X 锁)]。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排他锁(X 锁)。共享模式到排他锁的转换必须等待一段时间,因为一个事务的排他锁与其他事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排他锁(X 锁)以进行更新。由于两个事务都要转换为排他锁(X 锁),并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
若要避免这种潜在的死锁问题,请使用更新锁(U 锁)。一次只有一个事务可以获得资源的更新锁(U 锁)。如果事务修改资源,则更新锁(U 锁)转换为排他锁(X 锁)。
排他锁(X 锁)可以防止并发事务对资源进行访问。使用排他锁(X 锁)时,任何其他事务都无法修改数据;仅在使用 NOLOCK 提示或未提交读隔离级别时才会进行读取操作。
数据修改语句(如 INSERT、UPDATE 和 DELETE)合并了修改和读取操作。语句在执行所需的修改操作之前首先执行读取操作以获取数据。因此,数据修改语句通常请求共享锁和排他锁。例如,UPDATE 语句可能根据与一个表的联接修改另一个表中的行。在此情况下,除了请求更新行上的排他锁之外,UPDATE 语句还将请求在联接表中读取的行上的共享锁。
数据库引擎使用意向锁来保护共享锁(S 锁)或排他锁(X 锁)放置在锁层次结构的底层资源上。意向锁之所以命名为意向锁,是因为在较低级别锁前可获取它们,因此会通知意向将锁放置在较低级别上。
意向锁有两种用途:
· 防止其他事务以会使较低级别的锁无效的方式修改较高级别资源。
· 提高数据库引擎在较高的粒度级别检测锁冲突的效率。
例如,在该表的页或行上请求共享锁(S 锁)之前,在表级请求共享意向锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排他锁(X 锁)。意向锁可以提高性能,因为数据库引擎仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。而不需要检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
意向锁包括意向共享 (IS)、意向排他 (IX) 以及意向排他共享 (SIX)。
锁模式 | 说明 |
意向共享 (IS) | 保护针对层次结构中某些(而并非所有)低层资源请求或获取的共享锁。 |
意向排他 (IX) | 保护针对层次结构中某些(而并非所有)低层资源请求或获取的排他锁。IX 是 IS 的超集,它也保护针对低层级别资源请求的共享锁。 |
意向排他共享 (SIX) | 保护针对层次结构中某些(而并非所有)低层资源请求或获取的共享锁以及针对某些(而并非所有)低层资源请求或获取的意向排他锁。顶级资源允许使用并发 IS 锁。例如,获取表上的 SIX 锁也将获取正在修改的页上的意向排他锁以及修改的行上的排他锁。虽然每个资源在一段时间内只能有一个 SIX 锁,以防止其他事务对资源进行更新,但是其他事务可以通过获取表级的 IS 锁来读取层次结构中的低层资源。 |
意向更新 (IU) | 保护针对层次结构中所有低层资源请求或获取的更新锁。仅在页资源上使用 IU 锁。如果进行了更新操作,IU 锁将转换为 IX 锁。 |
共享意向更新 (SIU) | S 锁和 IU 锁的组合,作为分别获取这些锁并且同时持有两种锁的结果。例如,事务执行带有 PAGLOCK 提示的查询,然后执行更新操作。带有 PAGLOCK 提示的查询将获取 S 锁,更新操作将获取 IU 锁。 |
更新意向排他 (UIX) | U 锁和 IX 锁的组合,作为分别获取这些锁并且同时持有两种锁的结果。 |
数据库引擎在表数据定义语言 (DDL) 操作(例如添加列或删除表)的过程中使用架构修改 (Sch-M) 锁。保持该锁期间,Sch-M 锁将阻止对表进行并发访问。这意味着 Sch-M 锁在释放前将阻止所有外围操作。
某些数据操作语言 (DML) 操作(例如表截断)使用 Sch-M 锁阻止并发操作访问受影响的表。
数据库引擎在编译和执行查询时使用架构稳定性 (Sch-S) 锁。Sch-S 锁不会阻止某些事务锁,其中包括排他 (X) 锁。因此,在编译查询的过程中,其他事务(包括那些针对表使用 X 锁的事务)将继续运行。但是,无法针对表执行获取 Sch-M 锁的并发 DDL 操作和并发 DML 操作。
数据库引擎在将数据大容量复制到表中时使用了大容量更新 (BU) 锁,并指定了 TABLOCK 提示或使用 sp_tableoption 设置了 table lock on bulk load 表选项。大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据的进程访问该表。
在使用可序列化事务隔离级别时,对于 Transact-SQL 语句读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。键范围锁可防止幻读。通过保护行之间键的范围,它还防止对事务访问的记录集进行幻像插入或删除。
12.死锁优先级
指定当前会话与其他会话发生死锁时继续处理的相对重要性。
语法
SET DEADLOCK_PRIORITY { LOW | NORMAL | HIGH |<numeric-priority> | @deadlock_var | @deadlock_intvar }
<numeric-priority> ::= { -10 | -9 | -8 | ?| 0| ?| 8 | 9 | 10 }
参数
LOW
指定如果当前会话发生死锁,并且死锁链中涉及的其他会话的死锁优先级设置为 NORMAL 或 HIGH 或大于 -5 的整数值,则当前会话将成为死锁牺牲品。如果其他会话的死锁优先级设置为小于 -5的整数值,则当前会话将不会成为死锁牺牲品。此参数还指定如果其他会话的死锁优先级设置为 LOW 或 -5,则当前会话将可能成为死锁牺牲品。
NORMAL
指定如果死锁链中涉及的其他会话的死锁优先级设置为 HIGH 或大于 0 的整数值,则当前会话将成为死锁牺牲品,但如果其他会话的死锁优先级设置为 LOW 或小于 0 的整数值,则当前会话将不会成为死锁牺牲品。它还指定如果其他会话的死锁优先级设置为 NORMAL 或 0,则当前会话将可能成为死锁牺牲品。NORMAL 为默认优先级。
HIGH
指定如果死锁链中涉及的其他会话的死锁优先级设置为大于 5 的整数值,则当前会话将成为死锁牺牲品,或者如果其他会话的死锁优先级设置为HIGH 或 5,则当前会话可能成为死锁牺牲品。
<数值优先级>
用以提供 21 个死锁优先级别的整数值范围(-10 到 10)。它指定如果死锁链中涉及的其他会话以更高的死锁优先级值运行,则当前会话将成为死锁牺牲品,但如果其他会话以低于当前会话的死锁优先级值运行,则当前会话不会成为死锁牺牲品。它还指定如果其他会话以相同于当前会话的死锁优先级值运行,则当前会话可能成为死锁牺牲品。LOW 对应于 -5、NORMAL对应于 0 以及 HIGH 对应于 5。
@deadlock_var
指定死锁优先级的字符变量。此变量必须设置为“LOW”、“NORMAL”或“HIGH”中的一个值。而且必须足够大以保存整个字符串。
@deadlock_intvar
指定死锁优先级的整数变量。此变量必须设置为 -10 到 10 范围中的一个整数值。
备注
当两个会话同时等待访问由其他会话锁定的资源时,便会发生死锁。当 SQL Server 实例检测到两个会话发生死锁时,将选择其中一个会话作为死锁牺牲品来解决死锁。此牺牲品的当前事务将回滚,且死锁错误消息 1205 返回客户端。这样可释放由该会话所控制的所有锁,从而允许其他会话继续进行。
将哪个会话选为死锁牺牲品取决于每个会话的死锁优先级:
· 如果两个会话的死锁优先级相同,则 SQL Server 实例将回滚开销较低的会话选为死锁牺牲品。例如,如果两个会话都将其死锁优先级设置为 HIGH,则此实例便将它估计回滚开销较低的会话选为牺牲品。
· 如果会话的死锁优先级不同,则将死锁优先级最低的会话选为死锁牺牲品。
SETDEADLOCK_PRIORITY 是在执行或运行时设置,而不是在分析时设置。
权限
要求具有 public角色的成员身份。
示例
以下示例使用变量将死锁优先级设置为 LOW
。
DECLARE @deadlock_var NCHAR(3);
SET @deadlock_var = N'LOW';
SET DEADLOCK_PRIORITY @deadlock_var;
GO
以下示例将死锁优先级设置为 NORMAL
。
SET DEADLOCK_PRIORITY NORMAL;
GO