一:背景1.讲故事相信大部分使用SQLSERVER作为底层存储的程序员都知道nolock关键字。之后果断加了nolock,但是这个东西有什么注意事项呢?这需要了解其基本原理。二:nolock的原理1.SQL阻塞和恢复为了方便起见,先创建一个post表,插入6条记录。参考代码如下:CREATETABLEpost(idINTIDENTITY,contentchar(4000))GOINSERTINTOdbo.postVALUES('aaa')INSERTINTOdbo.postVALUES('bbb')INSERTINTOdbo.postVALUES('ccc');INSERTINTOdbo.postVALUES('ddd');INSERTINTOdbo.postVALUES('eee');INSERTINTOdbo.postVALUES('fff');这里为了简单我没有创建索引,所以会有TableScan。毕竟生产环境的SQL无法避免TableScan和ClusteredIndexScan的存在,然后还原阻塞场景,开启两个session会话,session1是运行的update事务,session2是简单的select操作,这个场景会导致session2阻塞,参考代码如下:session1BEGINTRANUPDATEpostSETcontent='xxxxx'WHEREid=3session2SELECT*FROMpostWHEREid=4从图中可以看出,这个select已经阻塞了9分钟,那么为什么它被阻止了?可以观察SQLSERVER内部的统计信息,比如锁相关的动态视图sys.dm_tran_locks,参考代码如下:SELECTt.request_session_id,CASEWHENt.resource_type='OBJECT'THENOBJECT_NAME(t.resource_associated_entity_id)WHENt.resource_associated_entity_id=0THEN'/'ELSEOBJECT_NAME(p.object_id)ENDASresource_name,index_id,t.resource_type,t.resource_descriptionASdescription,t.request_modeASmode,t.request_statusASstatusFROMsys.dm_tran_locksAStLEFTJOINsys.partitionsASpONp.hobt_id=t.resource_associated_entity_idWHEREt.resource_database_id=DB_ID()从图中可以看出,session55在准备给槽1:489:0指向的记录附加S锁时被阻塞了,因为1:489:0已经附加了带有X锁,显然这个X锁是由更新给出的。以上是静态视图。为了方便展示动态视图,这里我们打开sqlprofile观察两个session给锁的过程。事件选择如下:打开sqlprofile最后重新运行刚才的两个session,观察profile的变化趋势。截图如下:图中的注释已经很清楚了,和sys.dm_tran_locks的显示是一致的。当你去with(nolock)时会发生什么?SELECT*FROMpost(NOLOCK)WHEREid=4你会发现结果能出来,那为什么能出来呢?继续观察剖面。从session55的lock输出来看,with(nolock)会对post表附加一个Sch-S架构的稳定锁,对分区中的heap或者BTree附加一个S锁,不再对PAGE附加任何锁,所以just没有阻塞的情况,但是肯定会造成脏读。这基本上就是nolock的底层玩法了,但是还有一点需要注意,nolock真的会造成阻塞吗?我们接下来再说。3、nolock真的无视阻塞吗?从sqlprofile观察锁趋势图,nolock只是在页面级别忽略上限,而不能在页面之上做,比如你看到的Sch-S,可能有朋友会问,为什么要加Sch-S锁?其实很简单。在查询过程中,结构必须保持稳定。查询过程中不能突然删除post表,这样大家就尴尬了。接下来也可以做一个简单的测试。-----session1BEGINTRANTUNCATETABLEpost;-----session2SELECT*FROMpost(NOLOCK)WHEREid=4可以发现nolock查询也被阻塞了,因为post表的sch-s不能获得了锁,因为TRUNCATE给post附加了一个Sch-Mschema修改锁,那么有数据支持吗?继续用动态视图sys.dm_tran_locks观察即可。三:总结总结一下,nolock只是在页面级别不阻塞,在某些情况下会出现阻塞。因为没有锁,自然会读取其他会话修改过但未提交的记录,作为数据库应用,sqlserver包含了大量的运行时统计信息。这些统计数据可以通过系统视图和动态视图获取,并可以基于它们做一个完整的APM监控。
