SQL SERVER的锁机制系列:
SQL SERVER的锁机制(一)——概述(锁的种类与范围)
SQL SERVER的锁机制(二)——概述(锁的兼容性与可以锁定的资源)
SQL SERVER的锁机制(三)——概述(锁与事务隔离级别)
SQL SERVER的锁机制(四)——概述(各种事务隔离级别发生的影响)
锁定:通俗的讲就是加锁。锁定是 Microsoft SQL Server 数据库引擎用来同步多个用户同时对同一个数据块的访问的一种机制。
定义:当有事务操作时,数据库引擎会要求不同类型的锁定,如相关数据行、数据页或是整个数据表,当锁定运行时,会阻止其他事务对已经锁定的数据行、数据页或数据表进行操作。只有在当前事务对于自己锁定的资源不在需要时,才会释放其锁定的资源,供其他事务使用。
一、锁的种类与范围(如下表)
锁类型 |
说明 |
共享 (S) |
用于不更改或不更新数据的读取操作,如 SELECT 语句。 |
更新 (U) |
用于可更新的资源中。 防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 |
独占(也可称排他)(X) |
用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。 确保不会同时对同一资源进行多重更新。 |
意向 |
用于建立锁的层次结构。 意向锁包含三种类型:意向共享 (IS)、意向排他 (IX) 和意向排他共享 (SIX)。 |
架构 |
在执行依赖于表架构的操作时使用。 架构锁包含两种类型:架构修改 (Sch-M) 和架构稳定性 (Sch-S)。 |
大容量更新 (BU) |
在向表进行大容量数据复制且指定了 TABLOCK 提示时使用。 |
键范围 |
当使用可序列化事务隔离级别时保护查询读取的行的范围。 确保再次运行查询时其他事务无法插入符合可序列化事务的查询的行。 |
(一)共享锁
共享锁(S 锁)允许并发事务在封闭式并发控制下读取 (SELECT) 资源。
当查询(SELECT)某条记录时,SQL SERVER 会尝试取得该条记录的上存在共享锁(S 锁),或无法获取,就必须等待别人释放对该记录中某几种与共享锁互斥的锁,才能在设置共享锁之后,获取该条记录。
举例来说:当某人查询某张表的一条记录时,就会在该记录上放置共享锁,在而其他人也要查询这张表的此记录时,因为共享锁彼此不互斥,所以也可以再次放置共享锁,也就是说SQL SERVER允许不同连接同时读取相同的数据。如果此时有人要更新此记录,因为独占锁与共享锁互斥,所以无法放置独占锁,要等到所有读取此记录的人都读取完毕,释放了共享锁,更新数据的人才能对该记录设置独占锁,并进一步更新数据。一般情况下,在默认的事务隔离级别下,当数据读取完毕,SQL SERVER就会释放共享锁,除非有特别的设置。
(二)更新锁
更新锁是一种中继锁。当同一项资源从原来的查询操作转换为更新操作时,锁定机制会从共享锁变为更新锁,再进一步变成独占锁。
在可重复读或可序列化事务中,此事务读取数据 [获取资源(页或行)的共享锁(S 锁)],然后修改数据 [此操作要求锁转换为独占锁(X 锁)]。 如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为独占锁(X 锁)。 共享模式到独占锁的转换必须等待一段时间,因为一个事务的独占锁与其他事务的共享模式锁不兼容;发生锁等待。 第二个事务试图获取独占锁(X 锁)以进行更新。 由于两个事务都要转换为独占锁(X 锁),并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
若要避免这种潜在的死锁问题,请使用更新锁(U 锁)。 一次只有一个事务可以获得资源的更新锁(U 锁)。 如果事务修改资源,则更新锁(U 锁)转换为独占锁(X 锁)。
例如,当查询一条记录后,要将此内容更新(Update语句加上Where条件),一定会先查找记录,在查找的过程中就会对相关的记录放置共享锁,等找到相应的记录之后,SQL SERVER 会先对记录放置更新锁,以避免发生死锁。因为共享锁与更新锁并不互斥,如果两个人同时对同一条记录放置共享锁,先进行更新的人,可以在别人也对同一条记录放置了共享锁时,继续放置更新锁,但因为更新锁互斥,所以当另一个人想再放置更新锁时,将无法设置,而进入停止等待状态。
(三)独占锁(也可称为排他锁)
独占锁(排他锁)(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 锁的组合,作为分别获取这些锁并且同时持有两种锁的结果。 |
下面来实际举例来说明
--示例代码一: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN SELECT * FROM [Book] WHERE [bookid]=1 WAITFOR DELAY '00:00:10' COMMIT TRAN
可以通过另一条连接执行SP_LOCK 来查看上面代码的执行结果,如下图。在数据表上与数据页上都放置了意向共享锁,而在锁定的记录上放置了共享锁。
--示例代码二: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN SELECT * FROM [WBK_PDE_LIST] WHERE [WBOOK_NO]='BE404942450020' and cop_g_no='60217445' WAITFOR DELAY '00:00:10' COMMIT TRAN
可以通过另一条连接执行SP_LOCK 来查看上面代码的执行结果,如下图。由于上述代码中的[WBK_PDE_LIST]是一张堆表,所以直接就对数据表加了共享锁。
对上述示例中表WBK_PDE_LIST添加索引,
CREATE NONCLUSTERED INDEX [IX_WBK_PDE_LIST_WBOOKNO] ON [dbo].[WBK_PDE_LIST] ( [WBOOK_NO] ASC, [COP_G_NO] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF
, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO
然后再次执行代码示例二,再打开一个新的查询分析器,在查询分析器中执行SP_LOCK 来查看上面代码的执行结果,如下图。在数据表与数据页上分别放置了意向共享锁,在索引与数据行上放置了共享锁。
(五)架构锁
数据库引擎在表数据定义语言 (DDL) 操作(例如添加列或删除表)的过程中使用架构修改 (Sch-M) 锁。 保持该锁期间,Sch-M 锁将阻止对表进行并发访问。 这意味着 Sch-M 锁在释放前将阻止所有外围操作。
某些数据操作语言 (DML) 操作(例如表截断)使用 Sch-M 锁阻止并发操作访问受影响的表。
数据库引擎在编译和执行查询时使用架构稳定性 (Sch-S) 锁。 Sch-S 锁不会阻止某些事务锁,其中包括排他 (X) 锁。 因此,在编译查询的过程中,其他事务(包括那些针对表使用 X 锁的事务)将继续运行。 但是,无法针对表执行获取 Sch-M 锁的并发 DDL 操作和并发 DML 操作。
(六)大容量更新锁
数据库引擎在将数据大容量复制到表中时,指定 TABLOCK 提示或使用 sp_tableoption 选项(将数据表设置为 table lock on bulk load),则是使用大容量更新锁(BU)。 大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表,以降低数据表的锁定竞争,同时防止其他不进行大容量加载数据的进程访问该表。
(七)键范围锁
在使用可序列化事务隔离级别时,保护用户对于查询时所读取的数据行范围,以确保其他事务无法插入受“键范围锁”保护的数据行。键范围锁放置在索引上,指定开始与结束的索引键值。这些操作会先在索引上获取锁定,此种锁定可以封锁任何尝试进行插入、修改、删除索引键值在“键范围锁”中的数据行。例如:在索引键值“AAA”至“CZZ”范围中放置键范围锁,避免其他事务将含有索引键值的数据行插入到该范围内的任何地方,例如:“ABC”、“BCD”、“CEF”。另外当UPDATE语句搭配WHERE子句时,当SQL SERVER还在查找数据时,也有可能会设置键范围锁。