Sql Server之旅——终点站 nolock引发的三级事件的一些思考

本文分享了一个因忽视SQL锁设置导致数据库性能问题的真实案例,并深入解析了不同类型的SQL锁及其应用场景,帮助开发者理解如何合理使用SQL锁。

  曾今有件事情让我记忆犹新,那年刚来携程不久,马上就被安排写一个接口,供企鹅公司调用他们员工的差旅信息,然后我就三下五除二的给写好

了,上线之后,大概过了一个月。。。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 中,`NOLOCK` 是一种查询提示(Table Hint),用于指示查询优化器在读取表时不需要获取共享锁或互斥锁,从而避免阻塞其他操作。这种行为允许查询在读取数据时不会因为其他事务未提交而被阻塞,同时也可以减少锁资源的占用,提升查询性能。然而,这种操作也可能导致读取到未提交的“脏”数据[^2]。 ### 使用方法 `NOLOCK` 查询提示通常以 `WITH(NOLOCK)` 的形式附加在表名之后,适用于 `SELECT` 语句。其基本语法如下: ```sql SELECT column1, column2 FROM table_name WITH(NOLOCK) WHERE condition; ``` 例如,假设有一个名为 `Employees` 的表,可以使用以下语句查询数据而不会受到锁的影响: ```sql SELECT Name, Department FROM Employees WITH(NOLOCK) WHERE Salary > 50000; ``` ### 注意事项 1. **脏读风险** 使用 `NOLOCK` 时,可能会读取到其他事务尚未提交的数据。如果这些数据随后被回滚,那么查询结果中将包含不存在的数据,这可能导致业务逻辑错误。 2. **数据一致性问题** 由于 `NOLOCK` 绕过了锁机制,因此可能会导致查询结果中出现重复记录或遗漏记录的情况,尤其是在数据频繁更新的环境中。 3. **适用场景** `NOLOCK` 适用于对数据一致性要求不高的场景,例如报表生成、数据分析等,而不建议在需要高精度和一致性的事务处理中使用。 4. **性能影响** 虽然 `NOLOCK` 可以减少锁争用并提高查询性能,但在某些情况下,它可能导致查询计划的变化,从而影响性能。 5. **索引使用** 如果表具有非聚集索引,并且希望在使用 `NOLOCK` 时指定索引,可以使用 `WITH(NOLOCK, INDEX(index_name))` 的形式[^1]。 6. **与事务隔离级别的关系** `NOLOCK` 实际上等同于 `READ UNCOMMITTED` 隔离级别。因此,在使用 `NOLOCK` 时,应确保理解当前数据库的事务隔离级别及其对查询行为的影响。 ### 示例:结合索引提示使用 NOLOCK ```sql SELECT Name, Department FROM Employees WITH(NOLOCK, INDEX(IX_Employees_Department)) WHERE Department = 'HR'; ``` 上述语句将强制查询使用名为 `IX_Employees_Department` 的索引,并且不会阻塞其他事务。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值