sql server锁(lock)知识及锁应用
提示:这里所摘抄的关于锁的知识有的是不一样sql server版本的,对应于特定版本时会有问题。
一 关于锁的基础知识
(一). 为何要引入锁
当多个用户同时对数据库的并发操做时会带来如下数据不一致的问题:html
◆丢失更新程序员
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另外一个修改的结果,好比订票系统sql
◆脏读数据库
A用户修改了数据,随后B用户又读出该数据,但A用户由于某些缘由取消了对数据的修改,数据恢复原值,此时B获得的数据就与数据库内的数据产生了不一致安全
◆不可重复读服务器
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现先后两次的值不一致网络
并发控制的主要方法是封锁,锁就是在一段时间内禁止用户作某些操做以免产生数据不一致session
(二) 锁的分类
◆锁的类别有两种分法:多线程
1. 从数据库系统的角度来看:分为独占锁(即排它锁),共享锁和更新锁架构
MS-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) 锁锁定的数据
◆意向锁
意向锁表示 SQL Server 须要在层次结构中的某些底层资源上获取共享 (S) 锁或排它 (X) 锁。例如,放置在表级的共享意向锁表示事务打算在表中的页或行上放置共享 (S) 锁。在表级设置意向锁可防止另外一个事务随后在包含那一页的表上获取排它 (X) 锁。意向锁能够提升性能,由于 SQL Server 仅在表级检查意向锁来肯定事务是否能够安全地获取该表上的锁。而无须检查表中的每行或每页上的锁以肯定事务是否能够锁定整个表。
意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
锁模式 描述
意向共享 (IS) 经过在各资源上放置 S 锁,代表事务的意向是读取层次结构中的部分(而不是所有)底层资源。
意向排它 (IX) 经过在各资源上放置 X 锁,代表事务的意向是修改层次结构中的部分(而不是所有)底层资源。IX 是 IS 的超集。
与意向排它共享 (SIX) 经过在各资源上放置 IX 锁,代表事务的意向是读取层次结构中的所有底层资源并修改部分(而不是所有)底层资源。容许顶层资源上的并发 IS 锁。例如,表的 SIX 锁在表上放置一个 SIX 锁(容许并发 IS 锁),在当前所修改页上放置 IX 锁(在已修改行上放置 X 锁)。虽然每一个资源在一段时间内只能有一个 SIX 锁,以防止其它事务对资源进行更新,可是其它事务能够经过获取表级的 IS 锁来读取层次结构中的底层资源。
◆独占锁:
只容许进行锁定操做的程序使用,其余任何对他的操做均不会被接受。执行数据更新命令时,SQL Server会自动使用独占锁。当对象上有其余锁存在时,没法对其加独占锁。
共享锁:共享锁锁定的资源能够被其余用户读取,但其余用户没法修改它,在执行Select时,SQL Server会对对象加共享锁。
◆更新锁:
当SQL Server准备更新数据时,它首先对数据对象做更新锁锁定,这样数据将不能被修改,但能够读取。等到SQL Server肯定要进行更新数据操做时,他会自动将更新锁换为独占锁,当对象上有其余锁存在时,没法对其加更新锁。
2. 从程序员的角度看:分为乐观锁和悲观锁。
◆乐观锁:彻底依靠数据库来管理锁的工做。
◆悲观锁:程序员本身管理数据或对象上的锁处理。
MS-SQLSERVER 使用锁在多个同时在数据库内执行修改的用户间实现悲观并发控制
三 锁的粒度
锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小
SQL Server支持的锁粒度能够分为为行、页、键、键范围、索引、表或数据库获取锁
资源 描述
RID 行标识符。用于单独锁定表中的一行。
键 索引中的行锁。用于保护可串行事务中的键范围。
页 8 千字节 (KB) 的数据页或索引页。
扩展盘区 相邻的八个数据页或索引页构成的一组。
表 包括全部数据和索引在内的整个表。
DB 数据库。
SQL Server 提供如下的锁级别:
DATABASE -- 不管什么时候当一个SQL Server 进程正在使用除master之外的数据库时,Lock Manager为该进程授予数据库级的锁。数据库级的锁老是共享锁,用于跟踪什么时候数据库在使用中,以防其余进程删除该数据库,将数据库置为脱机,或者恢复数据库。注意,因为master和tempdb数据库不能被删除或置为脱机,因此不须要在它们之上加锁。
FILE -- 文件级的锁用于锁定数据库文件。
EXTENT -- Extent锁用于锁定extents,一般仅在空间分配和从新分配的时候使用。一个extent由8个连续的数据页或索引页组成。Extent锁能够是共享锁也能够是独占锁。
ALLOCATION_UNIT -- 使用在数据库分配单元上。
TABLE -- 这种级别的锁将锁定整个表,包括数据和索引。什么时候将得到表级锁的例子包括在Serializable隔离级别下从包含大量数据的表中选取全部的行,以及在表上执行不带过滤条件的update或delete。
Heap or B-Tree (HOBT) -- 用于堆数据页,或者索引的二叉树结构。
PAGE -- 使用页级锁,由8KB数据或者索引信息组成的整个页被锁定。当须要读取一页的全部行或者须要执行页级别的维护如页拆分后更新页指针时,将会获取页级锁。
Row ID (RID) -- 使用RID锁,页内的单一行被锁定。不管什么时候当提供最大化的资源并发性访问是有效而且可能时,将得到RID锁。
KEY -- SQL Server使用两种类型的Key锁。其中一个的使用取决于当前会话的锁隔离级别。对于运行于Read Committed 或者 Repeatable Read 隔离模式下的事务,SQL Server 锁定与被访问的行相关联的的实际索引key。(若是是表的汇集索引,数据行位于索引的叶级。行上在这些你看到的是Key锁而不是行级锁。)若在Serializable隔离模式下,经过锁定必定范围的key值从而不容许新的行插入到该范围内,SQL Server防止了“幻读”。这些锁于是被称做“key-range lock”。
METADATA -- 用于锁定系统目录信息(元数据)。
APPLICATION -- 容许用户定义他们本身的锁,指定资源名称、锁模式、全部者、timeout间隔。
四 SQL Server 锁类型(与粒度相对应)
1. HOLDLOCK: 在该表上保持共享锁,直到整个事务结束,而不是在语句执行完当即释放所添加的锁。
2. NOLOCK:不添加共享锁和排它锁,当这个选项生效后,可能读到未提交读的数据或“脏数据”,这个选项仅仅应用于SELECT语句。
3. PAGLOCK:指定添加页锁(不然一般可能添加表锁)。
4. READCOMMITTED用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认状况下,SQL Server 2000 在此隔离级别上操做。
5. READPAST: 跳过已经加锁的数据行,这个选项将使事务读取数据时跳过那些已经被其余事务锁定的数据行,而不是阻塞直到其余事务释放锁,READPAST仅仅应用于READ COMMITTED隔离性级别下事务操做中的SELECT语句操做。
6. READUNCOMMITTED:等同于NOLOCK。
7. REPEATABLEREAD:设置事务为可重复读隔离性级别。
8. ROWLOCK:使用行级锁,而不使用粒度更粗的页级锁和表级锁。
9. SERIALIZABLE:用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。等同于 HOLDLOCK。
10. TABLOCK:指定使用表级锁,而不是使用行级或页面级的锁,SQL Server在该语句执行完后释放这个锁,而若是同时指定了HOLDLOCK,该锁一直保持到这个事务结束。
11. TABLOCKX:指定在表上使用排它锁,这个锁能够阻止其余事务读或更新这个表的数据,直到这个语句或整个事务结束。
12. UPDLOCK :指定在读表中数据时设置更新 锁(update lock)而不是设置共享锁,该锁一直保持到这个语句或整个事务结束,使用UPDLOCK的做用是容许用户先读取数据(并且不阻塞其余用户读数据),而且保证在后来再更新数据时,这一段时间内这些数据没有被其余用户修改。
五 锁定时间的长短
锁保持的时间长度为保护所请求级别上的资源所需的时间长度。
用于保护读取操做的共享锁的保持时间取决于事务隔离级别。采用 READ COMMITTED 的默认事务隔离级别时,只在读取页的期间内控制共享锁。在扫描中,直到在扫描内的下一页上获取锁时才释放锁。若是指定 HOLDLOCK 提示或者将事务隔离级别设置为 REPEATABLE READ 或 SERIALIZABLE,则直到事务结束才释放锁。
根据为游标设置的并发选项,游标能够获取共享模式的滚动锁以保护提取。当须要滚动锁时,直到下一次提取或关闭游标(以先发生者为准)时才释放滚动锁。可是,若是指定 HOLDLOCK,则直到事务结束才释放滚动锁。
用于保护更新的排它锁将直到事务结束才释放。
若是一个链接试图获取一个锁,而该锁与另外一个链接所控制的锁冲突,则试图获取锁的链接将一直阻塞到:
将冲突锁释放并且链接获取了所请求的锁。
链接的超时间隔已到期。默认状况下没有超时间隔,可是一些应用程序设置超时间隔以防止无限期等待
六 SQL Server 中锁的自定义
◆处理死锁和设置死锁优先级
死锁就是多个用户申请不一样封锁,因为申请者均拥有一部分封锁权而又等待其余用户拥有的部分封锁而引发的无休止的等待
可使用SET DEADLOCK_PRIORITY控制在发生死锁状况时会话的反应方式。若是两个进程都锁定数据,而且直到其它进程释放本身的锁时,每一个进程才能释放本身的锁,即发生死锁状况。
◆2 处理超时和设置锁超时持续时间。
@@LOCK_TIMEOUT 返回当前会话的当前锁超时设置,单位为毫秒
SET LOCK_TIMEOUT 设置容许应用程序设置语句等待阻塞资源的最长时间。当语句等待的时间大于 LOCK_TIMEOUT 设置时,系统将自动取消阻塞的语句,并给应用程序返回"已超过了锁请求超时时段"的 1222 号错误信息
示例
下例将锁超时期限设置为 1,800 毫秒。
SET LOCK_TIMEOUT 1800
◆设置事务隔离级别。
◆对 SELECT、INSERT、UPDATE 和 DELETE 语句使用表级锁定提示。
◆配置索引的锁定粒度
可使用 sp_indexoption 系统存储过程来设置用于索引的锁定粒度
七 查看锁的信息
1 执行 EXEC SP_LOCK 报告有关锁的信息
2 查询分析器中按Ctrl+2能够看到锁的信息
八 使用注意事项
如何避免死锁,最小化锁竞争
1 使用事务时,尽可能缩短事务的逻辑处理过程,及早提交或回滚事务,事务持有锁的时间越短,锁竞争发生的机会就越少;将不是事务所管理的工做单元锁必需的命令移出事务。;
2 设置死锁超时参数为合理范围,如:3分钟-10分种;超过期间,自动放弃本次操做,避免进程悬挂;
3 优化程序,检查并避免死锁现象出现;
4 .对全部的脚本和SP都要仔细测试,在正是版本以前。
5 全部的SP都要有错误处理(经过@error)
6 通常不要修改SQL SERVER事务的默认级别。不推荐强行加锁
7 将组成事务的语句做为一个的单独的批命令处理,以消除 BEGIN TRAN 和 COMMIT TRAN 语句之间的网络延迟形成的没必要要的延迟。
8 考虑彻底地使用存储过程编写事务代码。典型地,存储过程比批命令运行更快。
9 在游标中尽可早地Commit更新。由于游标处理比面向集合的处理慢得多,所以致使锁被持有的时间更久。
10 使用每一个进程所需的最低级别的锁隔离。好比说,若是脏读是可接受的而且不要求结果必须精确,那么能够考虑使用事务隔离级别0(Read Uncommitted),仅在绝对必要时才使用Repeatable Read or Serializable隔离级别。
11 在 BEGIN TRAN 和 COMMIT TRAN 语句之间,毫不容许用户交互,由于这样作可能锁被持有无限期的时间。
九 几个有关锁的问题
1 如何锁一个表的某一行
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM table ROWLOCK WHERE id = 1
2 锁定数据库的一个表
SELECT * FROM table WITH (HOLDLOCK)
加锁语句:
sybase:
update 表 set col1=col1 where 1=0 ;
MSSQL:
select col1 from 表 (tablockx) where 1=0 ;
oracle:
LOCK TABLE 表 IN EXCLUSIVE MODE ;
加锁后其它人不可操做,直到加锁用户解锁,用commit或rollback解锁
◆排它锁
新建两个链接,在第一个链接中执行如下语句
-
-
update table1 set A='aa' where B='b2'
-
-
-
-
-
select * from table1 where B='b2'
-
若同时执行上述两个语句,则select查询必须等待update执行完毕才能执行即要等待30秒
◆共享锁
在第一个链接中执行如下语句
-
-
select * from table1 holdlock --holdlock人为加锁
-
-
waitfor delay '00:00:30' --等待30秒
-
◆共享锁
在第一个链接中执行如下语句
-
-
select * from table1 holdlock
-
-
-
在第二个链接中执行如下语句
-
-
select A,C from table1 where B='b2'
-
update table1 set A='aa' where B='b2'
-
若同时执行上述两个语句,则第二个链接中的select查询能够执行
而update必须等待第一个事务释放共享锁转为排它锁后才能执行 即要等待30秒
◆死锁
-
-
-
update table1 set A='aa' where B='b2'
-
-
update table2 set D='d5' where E='e1'
-
-
-
-
-
-
update table2 set D='d5' where E='e1'
-
-
update table1 set A='aa' where B='b2'
-
同时执行,系统会检测出死锁,并停止进程
十 应用程序锁:
应用程序锁就是客户端代码生成的锁,而不是sql server自己生成的锁
处理应用程序锁的两个过程
sp_getapplock 锁定应用程序资源
sp_releaseapplock 为应用程序资源解锁
注意: 锁定数据库的一个表的区别
SELECT * FROM table WITH (HOLDLOCK) 其余事务能够读取表,但不能更新删除
SELECT * FROM table WITH (TABLOCKX) 其余事务不能读取表,更新和删除
交读事务使用行版本控制。
使用快照隔离。
使用绑定链接。
二 锁的分析及应用系列
1 用SqlServer Profile来查看分析锁的信息
这个工具我想你们都明白,它的监视能力真的是无所不能。。。锁的痉挛状态也全在它的掌握之中。
1. 首先我作一个Person表,Name字段设定4000字节,这样一个数据页能够容纳2条数据,以下图:
DROP TABLE dbo.Person
CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
--插入6条,生成3个数据页
INSERT INTO dbo.Person DEFAULT VALUES
go 6
2. 下面咱们看看数据在数据页的分布状况。

3. 而后咱们开启Profile,在“事件选择”的Events中选择”Lock:Acquired“和”Lock:Released“ ,而后运行,以下图:

使用测试数据
1. 首先我执行一个简单的 SELECT * FROM dbo.Person,看看表/数据页/记录的加锁状况。

从图中能够看到,select执行的大概步骤以下:
第一步:给表(Object)加上IS(意向共享锁)。
第二步:先给1:78号数据页加IS锁,扫描78号数据页,而后释放IS锁。
第三步:一样的道理扫描以后的数据页。
第四步:最后释放表的IS锁,结束整个锁流程。
看完上面的一系列的Lock:Acquired 和 Lock:Released的话,你有没有发现一个问题,不是说好给记录(RID)加上S锁么???这里没加,是由于引擎进入78号数据页的时候,未发现它存在IU锁或者IX锁。。。因此。。。这个属于锁的组合,后续会说。
2. 接下来用UPDATE dbo.Person SET NAME='bbbbb' WHERE ID=3来看看update的整个过程,乍一看,Profile捕获到的记录仍是比较多的,下面具体看图:

第一步: 给表(Object)加上IX锁,
第二步: 给数据页(1:78)数据页分配IU锁。而后开始逐一扫描78号数据页的RID记录,进入前就Acquired,退出后就Released,当扫描完78号数据页的全部RID后,再释放78 号数据页的IU锁,进入下一个数据页。。。
第三步: 咱们发现ID=3是在89号数据页上,当引擎扫到该RID以后,咱们观察到89号的数据页由IU锁变成了IX锁,而且把1:89:0(slot为0的记录)由U锁变成X锁,变成X锁 后,就排斥了其余全部的锁,这时候就能够进行Update操做了。
第四步: 后面就继续90号数据页,步骤相似,第二步和第三步。
不知道细心的你有没有发现,在Released Object以前咱们才释放1:89:0的X锁,而后释放89号数据页的IX锁,这说明什么???说明这个Update是贯穿于这个事务的,不像Select操做中,扫完一个数据页就释放一个数据页。
3. 最后再看一个DELETE FROM dbo.Person WHERE ID=3 的操做。

大概扫了一下上面的图,或许你感受和Update操做大差不差,会扫描数据页中的每一个记录并加上U锁。当在1:89:0槽位中找到了目标记录后,而后将U锁转化为X锁,具体能够参考Update。
2 深刻的探讨锁机制
上一篇我只是作了一个堆表让你们初步的认识到锁的痉挛状态,可是在现实世界上并无这么简单的事情,起码个人表不会没有索引对吧,,,还有就是个人表必定会有不少的链接过来,10:1的读写,不少码农可能都会遇到相似神乎其神的死锁,卡住,读不出来,插不进入等等神仙的事情致使性能低下,这篇咱们一块儿来探讨下。
一: 当select遇到性能低下的update会怎么样?
1. 仍是使用原始的person表,插入6条数据,因为是4000字节,因此两条数据就是一个数据页,以下图:
1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
3 --插入6条数据,恰好3个数据页
4 INSERT INTO dbo.Person DEFAULT VALUES
5 go 6
2. 为了模拟性能低下的Update操做,咱们开个显式事务来更新ID=4的记录,而且用profile看一下,以下图:
1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4

3. 而后咱们开下另外一个会话链接,读取ID=6的记录会是怎样?好奇吗?
1 SELECT * FROM Person WHERE ID=6

从上面流程你是否看到,当扫描到89号数据页的slot1槽位的时候卡住了。。。我想你应该知道update正好已经给这条记录加上了X锁。。。若是你够细心,你还会发现,给S锁附加记录的条件是在当引擎发现记录所在的数据页已经附加上了IX锁的状况下,才给该号数据页下的每条记录附加S锁,对吧。。。好了,既然在Profile上面看不到了,我仍是有其余办法来判断到底select语句如今处于什么状态。
4. 使用sys.dm_tran_locks来看当前各个链接持有锁的状态。
1 SELECT l.request_session_id,
2 DB_NAME(l.resource_database_id),OBJECT_NAME(p.object_id),
3 l.resource_description,l.request_type,
4 l.request_status,request_mode
5 FROM sys.dm_tran_locks AS l
6 LEFT JOIN sys.partitions AS p
7 ON l.resource_associated_entity_id=p.hobt_id

仔细观察上图能够看到,当前有51和52号会话,51号在1:89:1槽位上使用了X锁而且没有释放,52号此时也进入了1:89:1中,而且想给该RowID附加S锁,可是你也知道S和X锁是排斥的,因此很无奈的一直保持等待状态。
二:使用索引或许能够帮你逃过一劫
当你看完上面的讲叙,是否是有点惧怕???要是在生产环境下出现了这种状况,那咱们是否是死的很惨???那接下来使用索引是否是真的能够帮咱们躲过一劫呢?下面跟我一块儿看一看。
1. 新建索引index
1 -- 在ID列上建一个index
2 CREATE INDEX idx_person ON dbo.Person(ID)
2. 而后咱们看下数据页的分布状况,能够看到下图中78,89,90是表数据页,93号为索引数据页。
1 DBCC TRACEON(2588,3604)
2 DBCC IND(Ctrip,Person,-1)

3. 麻蛋的,继续执行上面的那个慢update
BEGIN TRAN
UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4
4. 激动人心的时刻来了,因为数据太少,因此我这里强制让引擎执行我建立的索引,看看结果怎样?

竟然没卡住???如今是否是有一股强烈的好奇心来了,狗狗狗。。。立刻开启profile,看看到底都发生了什么?

仔细看完这个图,是否是以为颇有意思呢???具体步骤以下:
第一步:给表(Object)加上IS锁。
第二步:由于要走索引,给93号索引数据页加上IS锁。
第三步:找到93号索引数据页的目标key,给这个key加上S锁,有人可能就会问了。。。这个key不就是6嘛,为何这个key=(61005a25560e),你要是太好奇我能够告 诉你,年轻人说话不要太屌,每行索引记录都有一个散列值,这个值就是根据索引的几个字段散列出来的,好处就是防止你的索引长度过大,致使锁这个记录的 时候太耗费锁空间了。。。。若是你仍是不太相信的话,我用DBCC给你看一看。

第四步:根据这个key直接跳到存放记录的90号数据页中,万幸的是update的记录恰好不在90号数据页中。。。。就这样躲过一劫了。。。而后select顺利的读取到了该 读的记录,最后释放相关的IS锁。
3 nolock引起的三级事件的一些思考
曾今有件事情让我记忆犹新,那年刚来携程不久,立刻就被安排写一个接口,供企鹅公司调用他们员工的差旅信息,而后我就三下五除二的给写好了,上线以后,大概过了一个月。。。DBA那边报告数据库出现大量锁超时,而且及时根据sql的来源将email发到了咱们部门,指出sql读取时间过长,而且缺乏nolock,影响了大量机票订单入库,而后我就拿着sql去生产环境跑了下,22s。。。花擦。。。项目上线时间过久,版本已经不存在了,没法回滚。。。本来准备撤下接口。。。看了下撤下接口跟加上nolock时间相差很少,最后决定先加上nolock,发布紧急单。。。而后再优化,DBA那边暂时作手工解锁,发上去后,最后就是损失XXXX订单。。。定级为三级事件。而后就是追责,固然这个责任只能有老大们去承担了,出了此次由我引起的事件,我得思考了,出了事情对我不见得全是坏事,起码此次会让我铭记如心,想一想也搓,来携程以前根本就不会关注要不要给select指定nolock,这其中也包括本身没遇到过大数据吧,也包括本身的能力有限,只知道有锁这个玩意,细说的话就啥也不知道了,后来才知道携程有个规则,就是不少业务产线所写的select都必须指定nolock,懂一点的人可能会说nolock能够提高性能,若是你这样说,确实是这样,由于数据库的锁是有96字节开销的,没了锁,也就没有你在profile中看到accquired和released痉挛了,当你看完个人事件以后,你可能会意识到,性能提高不是最关心的,最关心就是不要出现死锁,锁等待。。。好了,言归正传,下面咱们看看到底在数据库中能够指定多少个锁???
一:到底能够指定多少个锁
这个问题有意思,咱们不须要记,只要你装一个SQL Prompt,有了这个神器,你就知道到底有多少个?以下图:
1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'xxxxx')
3 INSERT INTO dbo.Person DEFAULT VALUES
4 go 6

一眼扫下去,仍是蛮多的,不过你要注意了,那些所谓的XXXLock才是咱们须要关注的,根据上面的图,咱们大概把锁分个类。。。
粒度锁:PAGLOCK, TABLOCK, TABLOCKX, ROWLOCK, NOLOCK
模式锁:HOLDLOCK, UPDLOCK, XLOCK
接下来我从粒度锁提及:
1. NOLOCK
都说nolock是无锁模式的,那究竟是怎样的无锁呢???到这篇为止,你应该知道,若是不加nolock,咱们的表,数据页是附加IS锁的,那接下来我用profile看下二者有什么区别。
从上图中,你会看到加上nolock以后,object上面附加了Sch-S锁,这个锁叫作“架构稳定锁”,很简单就是sql编译时附加的一把锁,目的就是防止在编译时,有其余链接修改表结构,而这个锁只与Sch-M锁冲突,与其余锁都兼容,这说明什么?说明其余链接锁住了记录也不要紧,个人nolock不跟他们打交道,这样的话,就可能会读到脏数据,不过不要紧,携程的不少业务是允许脏数据的,毕竟比锁等待,死锁要强得多,再说nolock读到了其余链接未修改或者未提交的数据,这个几率也比较低,就算遇到了也不要紧,通常不会招来客诉的,客人或许再刷下页面,数据或许就正确了,对不对。。。
2.TABLOCK
这个仍是比较见名识义的,就是附加在table上的锁,也就是表锁了,很恐怖的。。。下面我举个Update的例子,看看先后对比。


在上面你有没有看到,X锁已经附加到OBJECT上面去了。。。这样的话,其余链接就动不了这个Object了,只能等待。。。
3. PAGLOCK
看了名字你应该也知道,就是附加到页面这个级别的锁,我也举一个Update的例子。
1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='aaaaa' WHERE ID=6
3
4 BEGIN TRAN
5 UPDATE dbo.Person WITH(PAGLOCK) SET NAME='bbbbb' WHERE ID=4


从上面两个图中,你应该能够看到,原来附加到RID上面的U锁,因为PagLock的提高,如今要附加到Page上面了,这个就是所谓的数据页锁。
4.TABLOCKX, ROWLOCK
这两个我就不细说了,TABLOCKX就是直接附加在table上的X锁,你能够经过select看一下。

ROWLOCK的话,默认状况下就是ROWLOCK,好比默认的Update,你会发现RID上被附加的U锁,这个就是行锁。
5.UPDLOCK
这个锁仍是蛮有意思的,它就是update锁,若是你select下,它会呈现update的锁痉挛效果。

6. XLOCK
知道了UPDLOCK锁,我想XLOCK你也应该明白了。。。它就是delete锁,即排他锁,我可让select带上排他锁。

7.HOLDLOCK
最后一个我也没闹明白,听说是让语句在整个事务中持有锁,而后我就用select和update调试一下。
1 SELECT * FROM dbo.Person(HOLDLOCK)
2 UPDATE dbo.Person WITH(HOLDLOCK) SET NAME='bbbbb' WHERE ID=4

三 SQL Server 锁机制 悲观锁 乐观锁 实测解析
在使用SQL时,大都会遇到这样的问题,你Update一条记录时,须要经过Select来检索出其值或条件,而后在经过这个值来执行修改操做。
但当以上操做放到多线程中并发处理时会出现问题:某线程select了一条记录但还没来得及update时,另外一个线程仍然可能会进来select到同一条记录。
通常解决办法就是使用锁和事物的联合机制:
1. 把select放在事务中, 不然select完成, 锁就释放了
2. 要阻止另外一个select , 则要手工加锁, select 默认是共享锁, select之间的共享锁是不冲突的, 因此, 若是只是共享锁, 即便锁没有释放, 另外一个select同样能够下共享锁, 从而select出数据
-
-
SELECT * FROM Table WITH(UPDLOCK)
-
-
-
全部Select加 With (NoLock)解决阻塞死锁,在查询语句中使用 NOLOCK 和 READPAST
处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAST 。有关 NOLOCK 和 READPAST的一些技术知识点:
对于非银行等严格要求事务的行业,搜索记录中出现或者不出现某条记录,都是在可容忍范围内,因此碰到死锁,应该首先考虑,咱们业务逻辑是否能容忍出现或者不出现某些记录,而不是寻求对双方都加锁条件下如何解锁的问题。
NOLOCK 和 READPAST 都是处理查询、插入、删除等操做时候,如何应对锁住的数据记录。可是这时候必定要注意NOLOCK 和 READPAST的局限性,确认你的业务逻辑能够容忍这些记录的出现或者不出现:
简单来讲:
1.NOLOCK 可能把没有提交事务的数据也显示出来
2.READPAST 会把被锁住的行不显示出来
不使用 NOLOCK 和 READPAST ,在 Select 操做时候则有可能报错误:事务(进程 ID **)与另外一个进程被死锁在 锁 资源上,而且已被选做死锁牺牲品。
SELECT * FROM Table WITH(NOLOCK)
SELECT * FROM Table WITH(READPAST)
实际开始动手用代码说话吧!
SQLServer2012在查询分析器里面开两个链接
插入锁:
结论:“表锁”锁定对该表的Select、Update、Delete操做,但不影响对该表的Insert操做也不影响以主键Id为条件的Select,因此Select若是不想等待就要在Select后加With(Nolock),但这样会产生脏数据就是其余事务已更新但并无提交的数据,若是该事务进行了RollBack则取出的数据就是错误的,因此好本身权衡利弊,通常状况下90%以上的Select都容许脏读,只有帐户金额相关的不容许。
-
-
-
-
(
Name, Age, Mobile, AddTime, Type )
-
-
-
-
-
-
-
-
SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
-
WHERE request_session_id=@@SPID
-
GROUP BY resource_type,request_mode
-
-
-
-
-
(
Name, Age, Mobile, AddTime, Type )
-
-
-
-
-
-
-
-
SELECT * FROM dbo.UserInfo
-
SELECT * FROM dbo.UserInfo WHERE Age=1
-
SELECT * FROM dbo.UserInfo WHERE Id=3
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK)
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1
-
UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff'
-
DELETE FROM dbo.UserInfo WHERE Name='fff'
更新锁:
结论:“表锁”锁定对该表的Select、Update、Delete操做,但不影响对该表的Insert操做也不影响以主键Id为条件的Select
-
-
-
UPDATE dbo.UserInfo SET Name = 'eee' WHERE Age = 2
-
-
SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
-
WHERE request_session_id=@@SPID
-
GROUP BY resource_type,request_mode
-
-
-
-
-
-
(
Name, Age, Mobile, AddTime, Type )
-
-
-
-
-
-
-
SELECT * FROM dbo.UserInfo
-
SELECT * FROM dbo.UserInfo WHERE Name='ppp'
-
SELECT * FROM dbo.UserInfo WHERE Id=3
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK)
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp'
-
UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc'
-
DELETE dbo.UserInfo WHERE Age = 5
主键锁:
结论:“行锁+表锁” 锁定对该表的Select、Update、Delete操做,但不影响对该表的Insert操做也不影响以主键Id为条件的Select、Update、Delete
-
-
-
UPDATE dbo.UserInfo SET Name='hhh' WHERE Id=3
-
-
SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
-
WHERE request_session_id=@@SPID
-
GROUP BY resource_type,request_mode
-
-
-
-
-
-
(
Name, Age, Mobile, AddTime, Type )
-
-
-
-
-
-
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK)
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk'
-
-
-
SELECT * FROM dbo.UserInfo
-
SELECT * FROM dbo.UserInfo WHERE Id=3
-
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3
-
DELETE dbo.UserInfo WHERE Id=3
-
-
SELECT * FROM dbo.UserInfo WHERE Name='aaa'
-
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa'
-
DELETE dbo.UserInfo WHERE Name='aaa'
-
-
SELECT * FROM dbo.UserInfo WHERE id=1
-
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1
-
DELETE dbo.UserInfo WHERE Id=1
索引锁:
结论:“行锁+表锁” 锁定对该表的Select、Update、Delete操做,但不影响对该表的Insert操做也不影响以主键Id为条件的Select、Update、Delete,也不影响以索引列Name为条件的Update、Delete但不能够Select
-
-
DROP INDEX dbo.UserInfo.Index_UserInfo_Name
-
CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name)
-
-
-
UPDATE dbo.UserInfo SET age=66 WHERE Name='ddd'
-
-
SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
-
WHERE request_session_id=@@SPID
-
GROUP BY resource_type,request_mode
-
-
-
-
-
-
(
Name, Age, Mobile, AddTime, Type )
-
-
-
-
-
-
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK)
-
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk'
-
-
-
SELECT * FROM dbo.UserInfo
-
SELECT * FROM dbo.UserInfo WHERE Id=4
-
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4
-
DELETE dbo.UserInfo WHERE Id=4
-
-
SELECT * FROM dbo.UserInfo WHERE Age=5
-
UPDATE dbo.UserInfo SET Name='ooo' WHERE Age=5
-
DELETE dbo.UserInfo WHERE Age=5
-
-
SELECT * FROM dbo.UserInfo WHERE Id=1
-
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1
-
DELETE dbo.UserInfo WHERE Id=1
-
-
SELECT * FROM dbo.UserInfo WHERE Name='aaa'
-
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa'
-
DELETE dbo.UserInfo WHERE Name='aaa'
悲观锁(更新锁-人工手动设置上锁):
结论:能够理解为在使用版本控制软件的时候A迁出了一个文件,而且8i将这个87文件锁定,B就没法再迁出该文件了,直到A迁入解锁后才能被其余人迁出。
-
-
-
SELECT * FROM dbo.UserInfo WITH(UPDLOCK) WHERE Id=2
-
-
SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
-
WHERE request_session_id=@@SPID
-
GROUP BY resource_type,request_mode
-
-
-
-
-
-
SELECT * FROM dbo.UserInfo
-
SELECT * FROM dbo.UserInfo WHERE id=2
-
SELECT * FROM dbo.UserInfo WHERE Name='ooo'
-
-
UPDATE dbo.UserInfo SET Age=3 WHERE id=1
-
UPDATE dbo.UserInfo SET Age=3 WHERE Name='ccc'
-
-
DELETE dbo.UserInfo WHERE id=1
-
DELETE dbo.UserInfo WHERE name='ccc'
乐观锁(人工经过逻辑在数据库中模拟锁)
结论:能够理解为一样在使用版本控制软件的时候A迁出了一个文件,B也能够迁出该文件,两我的均可以对此文件进行修改,其中一我的先进行提交的时候,版本并无变化因此能够正常提交,另外一个后提交的时候,发现版本增长不对称了,就提示冲突由用户来选择如何进行合并再从新进行提交。
-
-
-
-
-
-
Id INT PRIMARY KEY IDENTITY(1,1),
-
Number VARCHAR(50) NOT NULL,
-
-
-
IsFlag
BIT DEFAULT(0) NOT NULL,
-
CreateTime DATETIME
DEFAULT(GETDATE()) NOT NULL
-
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000001')
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000002')
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000003')
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000004')
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000005')
-
INSERT INTO dbo.Coupon(Number) VALUES ( '10000006')
-
-
-
-
-
-
DECLARE @User VARCHAR(50)
-
-
-
-
SELECT @TempId=Id FROM dbo.Coupon WHERE IsFlag=0
-
-
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
-
-
-
-
-
DECLARE @User VARCHAR(50)
-
-
-
-
SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0
-
-
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
-
-
-
-
-
ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL
-
-
-
DECLARE @User VARCHAR(50)
-
-
DECLARE @RowVer BINARY(8)
-
-
-
-
-
SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0
-
-
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId AND RowVer=@RowVer
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
在乐观锁和悲观锁之间进行选择的标准是:冲突的频率与严重性。若是冲突不多,或者冲突的后果不会很严重,那么一般状况下应该选择乐观锁,由于它能获得更好的并发性,并且更容易实现。可是,若是冲突的结果对于用户来讲痛苦的,那么就须要使用悲观策略。
我认为若是同一张表的并发很高,但并发处理同一条数据的冲突概率很低,那就应该使用乐观锁,反之,若是同一张表的并发不高,但同时处理同一条数据的概率很高,就应该使用悲观锁。
四 SQL Server 中WITH (NOLOCK)浅析
概念介绍
开发人员喜欢在SQL脚本中使用WITH(NOLOCK), WITH(NOLOCK)实际上是表提示(table_hint)中的一种。它等同于 READUNCOMMITTED 。 具体的功能做用以下所示(摘自MSDN):
1: 指定容许脏读。不发布共享锁来阻止其余事务修改当前事务读取的数据,其余事务设置的排他锁不会阻碍当前事务读取锁定数据。容许脏读可能产生较多的并发操做,但其代价是读取之后会被其余事务回滚的数据修改。这可能会使您的事务出错,向用户显示从未提交过的数据,或者致使用户两次看到记录(或根本看不到记录)。有关脏读、不可重复读和幻读的详细信息,请参阅并发影响。
2: READUNCOMMITTED 和 NOLOCK 提示仅适用于数据锁。全部查询(包括那些带有 READUNCOMMITTED 和 NOLOCK 提示的查询)都会在编译和执行过程当中获取 Sch-S(架构稳定性)锁。所以,当并发事务持有表的 Sch-M(架构修改)锁时,将阻塞查询。例如,数据定义语言 (DDL) 操做在修改表的架构信息以前获取 Sch-M 锁。全部并发查询(包括那些使用 READUNCOMMITTED 或 NOLOCK 提示运行的查询)都会在尝试获取 Sch-S 锁时被阻塞。相反,持有 Sch-S 锁的查询将阻塞尝试获取 Sch-M 锁的并发事务。有关锁行为的详细信息,请参阅锁兼容性(数据库引擎)。
3: 不能为经过插入、更新或删除操做修改过的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查询优化器忽略 FROM 子句中应用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示。
功能与缺陷
使用WIHT(NOLOCK)有利也有弊,因此在决定使用以前,你必定须要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否适合你的业务需求,不要以为它能提高性能,稀里糊涂的就使用它。
1:使用WITH(NOLOCK)时查询不受其它排他锁阻塞
打开会话窗口1,执行下面脚本,不提交也不回滚事务,模拟事务真在执行过程中
BEGIN TRAN
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
--ROLLBACK
打开会话窗口2,执行下面脚本,你会发现执行结果一直查询不出来(其实才两条记录)。当前会话被阻塞了
打开会话窗口3,执行下面脚本,查看阻塞状况,你会发如今会话2被会话1给阻塞了,会话2的等待类型为LCK_M_S:“当某任务正在等待获取共享锁时出现”
SELECT wt.blocking_session_id AS BlockingSessesionId
,sp.program_name AS ProgramName
,COALESCE(sp.LOGINAME, sp.nt_username) AS HostName
,ec1.client_net_address AS ClientIpAddress
,db.name AS DatabaseName
,wt.wait_type AS WaitType
,ec1.connect_time AS BlockingStartTime
,wt.WAIT_DURATION_MS/1000 AS WaitDuration
,ec1.session_id AS BlockedSessionId
,h1.TEXT AS BlockedSQLText
,h2.TEXT AS BlockingSQLText
FROM sys.dm_tran_locks AS tl
INNER JOIN sys.databases db
ON db.database_id = tl.resource_database_id
INNER JOIN sys.dm_os_waiting_tasks AS wt
ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.dm_exec_connections ec1
ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2
ON ec2.session_id = wt.blocking_session_id
LEFT OUTER JOIN master.dbo.sysprocesses sp
ON SP.spid = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

此时查看会话1(会话1的会话ID为53,执行脚本1前,能够用SELECT @@spid查看会话ID)的锁信息状况,你会发现表TEST(ObjId=1893581784)持有的锁信息以下所示

打开会话窗口4,执行下面脚本.你会发现查询结果很快就出来,会话4并不会被会话1阻塞。
SELECT * FROM TEST WITH(NOLOCK)
从上面模拟的这个小例子能够看出,正是因为加上WITH(NOLOCK)提示后,会话1中事务设置的排他锁不会阻碍当前事务读取锁定数据,因此会话4不会被阻塞,从而提高并发时查询性能。
2:WITH(NOLOCK) 不发布共享锁来阻止其余事务修改当前事务读取的数据,这个就不举例子了。
本质上WITH(NOLOCK)是经过减小锁和不受排它锁影响来减小阻塞,从而提升并发时的性能。所谓凡事有利也有弊,WITH(NOLOCK)在提高性能的同时,也会产生脏读现象。
以下所示,表TEST有两条记录,我准备更新OBJECT_ID=1的记录,此时事务既没有提交也没有回滚

BEGIN TRAN
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
--ROLLBACK
此时另一个会话使用WITH(NOLOCK)查到的记录为未提交的记录值

假如因为某种缘由,该事务回滚了,那么咱们读取到的OBJECT_ID=1的记录就是一条脏数据。
脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,而后事务T2读取该值,此后T1由于某种缘由撤销对该值的修改,这就致使了T2所读取到的数据是无效的。
WITH(NOLOCK)使用场景
何时可使用WITH(NOLOCK)? 何时不能使用WITH(NOLOCK),这个要视你系统业务状况,综合考虑性能状况与业务要求来决定是否使用WITH(NOLOCK), 例如涉及到金融或会计成本之类的系统,出现脏读那是要产生严重问题的。关键业务系统也要慎重考虑。大致来讲通常有下面一些场景可使用WITH(NOLOCK)
1: 基础数据表,这些表的数据不多变动。
2:历史数据表,这些表的数据不多变动。
3:业务容许脏读状况出现涉及的表。
4:数据量超大的表,出于性能考虑,而容许脏读。
另一点就是不要滥用WITH(NOLOCK),我发现有个奇怪现象,不少开发知道WITH(NOLOCK),可是有不了解脏读,习惯性的使用WITH(NOLOCK)。
WITH(NOLOCK)与 NOLOCK区别
为了搞清楚WITH(NOLOCK)与NOLOCK的区别,我查了大量的资料,咱们先看看下面三个SQL语句有啥区别
SELECT * FROM TEST NOLOCK
SELECT * FROM TEST (NOLOCK);
SELECT * FROM TEST WITH(NOLOCK);
上面的问题归纳起来也就是说NOLOCK、(NOLOCK)、 WITH(NOLOCK)的区别:
1: NOLOCK这样的写法,其实NOLOCK其实只是别名的做用,而没有任何实质做用。因此不要粗心将(NOLOCK)写成NOLOCK
2:(NOLOCK)与WITH(NOLOCK)其实功能上是同样的。(NOLOCK)只是WITH(NOLOCK)的别名,可是在SQL Server 2008及之后版本中,(NOLOCK)不推荐使用了,"不借助 WITH 关键字指定表提示”的写法已通过时了。 具体参见MSDN http://msdn.microsoft.com/zh-cn/library/ms143729%28SQL.100%29.aspx
2.1 至于网上说WITH(NOLOCK)在SQL SERVER 2000不生效,我验证后发现彻底是个谬论。
2.2 在使用连接服务器的SQL当中,(NOLOCK)不会生效,WITH(NOLOCK)才会生效。以下所示

消息 4122,级别 16,状态 1,第 1 行
Remote table-valued function calls are not allowed.
3.语法上有些许出入,以下所示
这种语法会报错
SELECT * FROM sys.indexes WITH(NOLOCK) AS i
-Msg 156, Level 15, State 1, Line 1
-Incorrect syntax near the keyword 'AS'.
这种语法正常
SELECT * FROM sys.indexes (NOLOCK) AS i
能够所有改写为下面语法
SELECT * FROM sys.indexes i WITH(NOLOCK)
SELECT * FROM sys.indexes i (NOLOCK)
WITH(NOLOCK)会不会产生锁
不少人误觉得使用了WITH(NOLOCK)后,数据库库不会产生任何锁。实质上,使用了WITH(NOLOCK)后,数据库依然对该表对象生成Sch-S(架构稳定性)锁以及DB类型的共享锁, 以下所示,能够在一个会话中查询一个大表,而后在另一个会话中查看锁信息(也可使用SQL Profile查看会话锁信息)
不使用WTIH(NOLOCK)

使用WITH(NOLOCK)

从上能够看出使用WITH(NOLOCK)后,数据库并非不生成相关锁。 对比能够发现使用WITH(NOLOCK)后,数据库只会生成DB类型的共享锁、以及TAB类型的架构稳定性锁.
另外,使用WITH(NOLOCK)并非说就不会被其它会话阻塞,依然可能会产生Schema Change Blocking
会话1:执行下面SQL语句,暂时不提交,模拟事务正在执行
BEGIN TRAN
ALTER TABLE TEST ADD Grade VARCHAR(10) ;
会话2:执行下面语句,你会发现会话被阻塞,截图以下所示。
SELECT * FROM TEST WITH(NOLOCK)

-----
本文引用了如下网址内容:
http://www.cnblogs.com/huangxincheng/p/4292320.html
http://blog.itpub.net/13651903/viewspace-1091664/
http://www.cnblogs.com/kerrycode/p/3946268.html
http://www.cnblogs.com/taiyonghai/p/5674462.html
--- end ---