SQL查询语句使用nolock
写查询语句时,为了性能,往往会在表后面加一个 nolock
,或者是 with(nolock)
,其目的是查询时不锁定表,从而达到提高查询速度的目的。
并发访问
什么是并发访问?同一时间有多个用户访问同一资源,并发用户中如果有用户对资源做了修改,此时就会对其它用户产生某些不利的影响,例如:
1、脏读。一个用户对一个资源做了修改,此时另外一个用户正好读取了这条被修改的记录,然后,第一个用户放弃修改,数据回到修改之前,这两个不同的结果就是脏读。
2、不可重复读:一个用户的一个操作是一个事务,这个事务分两次读取同一条记录,如果第一次读取后,有另外用户修改了这个数据,然后第二次读取的数据正好是其它用户修改的数据,这样造成两次读取的记录不同,如果事务中锁定这条记录就可以避免。
3、幻读:指用户读取一批记录的情况,用户两次查询同一条件的一批记录,第一次查询后,有其它用户对这批数据做了修改,方法可能是修改,删除,新增,第二次查询时,会发现第一次查询的记录条目有的不在第二次查询结果中,或者是第二次查询的条目不在第一次查询的内容中。
并发访问的控制机制
为什么会在查询的表后面加 nolock
标识?为了避免并发访问产生的不利影响,
SQL Server 有两种并发访问的控制机制:锁、行版本控制。表后面加 nolock
是解决并发访问的方案之一。
锁
锁:每个事务对所依赖的资源会请求不同类型的锁,它可以阻止其他事务以某种可能会导致事务请求锁出错的方式修改资源。当事务不再依赖锁定的资源时,锁将被释放。
锁的类型:
1、表类型:锁定整个表;
2、行类型:锁定某个行;
3、文件类型:锁定某个数据库文件;
4、数据库类型:锁定整个数据库;
5、页类型:锁定 8K 为单位的数据库页。
锁的分类还有一种分法,就是按用户和数据库对象来分:
1、从数据库系统的角度来看:分为独占锁(即排它锁),共享锁和更新锁
(1)共享 (S) :用于不更改或不更新数据的操作(只读操作),一般常见的例如 select 语句。
(2)更新 (U) :用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
(3)排它 (X) :用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
2、从程序员的角度看:分为乐观锁和悲观锁。
(1)乐观锁:完全依靠数据库来管理锁的工作。
(2)悲观锁:程序员自己管理数据或对象上的锁处理。
一般程序员一看到什么锁之类,觉的特别复杂,对专业的 DBA 当然是入门级知识了。可喜的是程序员不用去设置、控制这些锁,SQLServer 通过设置事务的隔离级别自动管理锁的设置和控制。
锁管理器通过查询分析器分析待执行的 sql 语句,来判断语句将会访问哪些资源,进行什么操作,然后结合设定的隔离级别自动分配管理需要用到的锁。
行版本控制
行版本控制:当启用了基于行版本控制的隔离级别时,数据库引擎 将维护修改的每一行的版本。应用程序可以指定事务使用行版本查看事务或查询开始时存在的数据,而不是使用锁保护所有读取。
通过使用行版本控制,读取操作阻止其他事务的可能性将大大降低。也就是相当于针对所有的表在查询时都会加上 nolock
,同样会产生脏读的现象,但差别在于在一个统一管理的地方。
隔离级别
说到了基于行版本控制的隔离级别,这里有必要说下隔离级别的概念。
隔离级别的用处:控制锁的应用,即什么场景应用什么样的锁机制。
最终目的:解决并发处理带来的种种问题。
隔离级别的分类:
1、未提交读,隔离事务的最低级别,只能保证不读取物理上损坏的数据;
2、已提交读,数据库引擎的默认级;
3、可重复读;
4、可序列化;隔离事务的最高级别,事务之间完全隔离。
小结:NOLOCK
语句执行时不发出共享锁,允许脏读 ,等于 READ UNCOMMITTED
事务隔离级别。nolock
确实在查询时能提高速度,但它并不是没有缺点的,起码它会引起脏读。
nolock的使用场景
nolock的使用场景(个人观点):
1、数据量特别大的表,牺牲数据安全性来提升性能是可以考虑的。
2、允许出现脏读现象的业务逻辑,反之一些数据完整性要求比较严格的场景就不合适了,像金融方面等。
3、数据不经常修改的表,这样会省于锁定表的时间来大大加快查询速度。
综上所述,如果在项目中的每个查询的表后面都加 nolock
,这种做法并不科学,起码特别费时间,不如行版本控制来的直接有效。而且会存在不可预期的技术问题。应该有选择性的挑选最适合的表来放弃共享锁的使用。
nolock和with(nolock)的区别
最后说下 nolock和with(nolock)的几个小区别:
1、SQL05 中的同义词,只支持 with(nolock)
2、with(nolock)
的写法非常容易再指定索引
3、跨服务器查询语句时 不能用 with (nolock)
只能用 nolock
4、同一个服务器查询时 则 with (nolock)
和 nolock
都可以用
比如:
1
2
3
4
5
# 这样会提示用错误
select * from [IP].a.dbo.table1 with (nolock)
# 这样就可以
select * from a.dbo.table1 with (nolock)
Sql Server中的NOLOCK作用
先说下其区别,之后再做测试。大家都知道,每新建一个查询,都相当于创建一个会话,在不同的查询分析器里面进行的操作,可以影响到其他会话的查询,极端的情况可能会一直处于阻塞中,哪怕只是一个很简单的查询都特别慢。
BEGIN TRAN
是开始一个事务的意思,开始之后可执行一些SQL语句,接着需要执行 COMMIT
进行提交或者 ROLLBACK
进行回滚,否则就会出现上面的情况。
但如果使用 NOLOCK
进行查询的时候,就不会因为别的回话没有提交或回滚,而受阻塞。
所以概括起来,可以用以下语句来总结:
NOLOCK
能使当前会话的查询,不受其它会话的事务所阻塞。但是这样做,就读取了其它事务的修改后未提交的数据。
现在我们进行测试,一定要注意,必须在多个会话下才可以,也就是说,需要建三个查询分析器窗口。表用最简单的表,自己动手建一个。
NOLOCK测试
查询分析器一
执行:
1
SELECT * FROM dbo.test_main
得到:
1
2
3
4
5
id value
1 one
2 two
3 three
4 four
接着执行如下:
1
2
BEGIN TRAN
INSERT INTO test_main VALUES(5, 'five')
一行受影响
查询分析器二
执行:
1
SELECT * FROM dbo.test_main
则卡死,受上一会话所阻塞。查不出结果。
补充:那么卡死怎么办呢?我们已经说过,要执行提交或者回滚操作才可以,那么在会话一中执行 COMMIT
即可。之后此查询立刻显示结果。
查询分析器三
执行:
1
SELECT * FROM test_main(NOLOCK)
则显示如下:
1
2
3
4
5
6
id value
1 one
2 two
3 three
4 four
5 five
但最后一行并没有真正存储在数据库中,因为会话一还没有进行提交,我们用 NOLOCK
就查询出来了。也许你会想,那什么情况下用 NOLOCK
呢?经过我们的分析,用 NOLOCK
是为了避免出现卡死状态,那我们就可以分析其环境了。
一个经常操作的表,并且每次操作都很重要,这样一般要用到事务进行处理,因为可以避免出错的几率,我们查询时,要用 NOLOCK
,否则遇上卡死的几率很大。别人执行一个事务,还没处理完呢,你就查询了,那就卡死了。有了 NOLOCK
就可以解决这个问题了。
脏数据
涉及到一个脏数据的概念:
1、脏数据临时更新(脏读)产生
2、事务 A 更新了某数据项X,由于某种原因事务 A 出现了问题,于要把 A 回滚,回滚之前另事务 B 读取了数据项 X 值(A 更新),A 回滚了事务数据项恢复了原值事务,B 读取数据项 X 临时值脏数据。
相关链接:
SQL查询用NoLock