概念介绍sql
开发人员喜欢在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 锁的并发事务。有关锁行为的详细信息,请参阅锁兼容性(数据库引擎)。session
3: 不能为经过插入、更新或删除操做修改过的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查询优化器忽略 FROM 子句中应用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示。架构
功能与缺陷并发
使用WIHT(NOLOCK)有利也有弊,因此在决定使用以前,你必定须要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否适合你的业务需求,不要以为它能提高性能,稀里糊涂的就使用它。app
1:使用WITH(NOLOCK)时查询不受其它排他锁阻塞高并发
打开会话窗口1,执行下面脚本,不提交也不回滚事务,模拟事务真在执行过程中post
BEGIN TRAN
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
--ROLLBACK
打开会话窗口2,执行下面脚本,你会发现执行结果一直查询不出来(其实才两条记录)。当前会话被阻塞了性能
SELECT * FROM TEST;
打开会话窗口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.
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)