时间流逝比较快,博主也在马不停蹄学习SQL Server,下班回来再晚也不忘记更新下博客,时间挤挤总会有的,如今的努力求的是将来所谓的安稳,每学一门为的是深度而不是广度,求的是知识自成体系而不是零散,废话很少说本节咱们来说讲SQL Server基础系列最后几节内容,这话博主说了n次,呵呵。数据库
随便翻翻博客园对于各类锁的介绍真的是一个字【多】,仅仅介绍其概念,再要么就是转载其概念,不知道那些转载概念的园友是否已经弄懂了,稍微发下感慨。NOLOCK在概念上相似于READ UNCOMMITTED隔离级别,而且只针对于SELECT查询语句,它不会获取表的共享锁,换句话说不会阻止排它锁来更新数据行。当咱们对表进行NOLOCK有什么好处呢?它可以提升并发性能,由于此时SQL Server数据库引擎没必要去维护共享锁,因为不会对正在读取的表获取共享锁,因此可能致使未提交的事务也会被读取,因此此时缺点显而易见将致使脏读,至于脏读是何含义则无需我再多讲。咱们重点的明白什么状况下应该用NOLOCK。咱们看下实际例子来理解NOLOCK,创建测试表并插入300条测试数据:并发
IF OBJECT_ID('Example')>0 DROP TABLE Example; GO CREATE TABLE [dbo].[Example] ( [SaleID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, [Product] [char](150) NULL, [SaleDate] [datetime] NULL, [SalePrice] [money] NULL ) GO DECLARE @i SMALLINT SET @i = 1 WHILE (@i <=100) BEGIN INSERT INTO Example (Product, SaleDate, SalePrice) VALUES ('Computer', DATEADD(mm, @i, '3/11/1919'), DATEPART(ms, GETDATE()) + (@i + 57)) INSERT INTO Example (Product, SaleDate, SalePrice) VALUES ('BigScreen', DATEADD(mm, @i, '3/11/1927'), DATEPART(ms, GETDATE()) + (@i + 13)) INSERT INTO Example (Product, SaleDate, SalePrice) VALUES ('PoolTable', DATEADD(mm, @i, '3/11/1908'), DATEPART(ms, GETDATE()) + (@i + 29)) SET @i = @i + 1 END GO
此时咱们再来插入一条测试数据:函数
BEGIN TRANSACTION INSERT INTO Example (Product, SaleDate, SalePrice) VALUES ('PoolTable', GETDATE(), 500)
此时咱们保持该事务窗口打开,因此此时在表中仍然会记录着对其所发出的锁,接下来咱们在另一个窗口查询表中数据总行数并使用NOLOCK提示。高并发
SELECT COUNT(*) FROM Example WITH(NOLOCK)
此时显示数据总函数为301,由于上述插入语句的事务进入到了表中只是并未提交而已,此时咱们不想插入那条数据进行撤销即回滚性能
BEGIN TRANSACTION INSERT INTO Example (Product, SaleDate, SalePrice) VALUES ('PoolTable', GETDATE(), 500) ROLLBACK TRANSACTION
此时咱们回滚了以前插入的数据,咱们再来利用NOLOCK提示来查询数据总函数。学习
此时返回的为实际总数据行,而我么第一次查询的数据并未提交这就是典型的-脏读。测试
READPAST表提示相信不少童鞋用的比较少,可是实际上其做用很是大,当在表中用READPAST指定提示时此时SQL Server数据库引擎在返回结果集时将不会返回锁定的行或者数据页。它除了和NOLOCK同样不会致使查询阻塞外,由于不会返回锁定的行记录因此其优势好包括不存在脏读。可是其缺点则是由于不包含锁定的行记录可是很难保证结果集或者修改语句是否包含咱们所必须须要返回的行。有可能在咱们的业务逻辑中,须要返回咱们必须须要的行。它的使用方式和NOLOCK同样,下面咱们来看下实际例子,更新测试表中的SalePrice列,以下:spa
BEGIN TRANSACTION UPDATE TOP(1) Example SET SalePrice = SalePrice + 1
因为咱们并未提交或者回滚事务因此此时更新的数据行已经被影响,下面咱们利用READPAST提示来查询表中总数据行。翻译
SELECT COUNT(*)
FROM Example WITH(READPAST)
在咱们的测试表中数据行为300条,同时咱们进行了上述更新,当咱们利用READPAST提示进行查询总数据行时,由于更新而未提交或者回滚致使此时有一行记录被排它锁锁住,而READPAST的做用则是跳过锁住的行,因此此时很明显只返回299条数据,以下:code
经过上述图显示因为更新数据行被锁定,因此此时利用READPAST来查询总数据行时致使更新数据行将被忽略。
怎么会出现一个更新锁的呢,原来咱们对于查询和更新死锁说到了排它锁,这个排它锁和更新锁不是同样的么,此言差矣,容我娓娓道来,这个UPDLOCK只是针对于表中的某一行记录来锁定从而阻止其余操做对该行的数据更新,说到这里想必咱们已经明了,UPDLOCK是行级别,而排它锁则是表级别,两者不可同日而语。也就说当咱们对某一行添加UPDLOCK提示时并不会阻塞其余查询操做,下面咱们来看看,咱们打开一个窗口来更新测试表中筛选条件为SaleID等于1的记录并用UPDLOCK锁住。
BEGIN TRAN select * from Example WITH (UPDLOCK) where SaleID = 1
此时咱们再来开一个窗口进行查询,以下:
select * from Example
此时咱们将看到可以查询出全部数据,以下:
这个又是什么玩意了,根据词达意翻译为厚住锁【哈哈】,这个翻译虽然有点勉强,可是很是明确的表达了其意思,有点强制性的意味,当咱们使用HOLDLOCK提示时,此时查询将锁定表且被强制序列化,直到事务完成,才会被释放,其相似于SERIALIZABLE最高隔离级别。咱们结合上述例子来看下,当咱们对表进行HOLDLOCK后再进行查询
BEGIN TRAN select * from Example WITH (UPDLOCK,HOLDLOCK) where SaleID = 1
此时咱们再来运行查询
select * from Example
什么状况仍是能查询出数据,不知道看到本文的你是否心生疑窦,咱们并未提交事务并用UPDLOCK和HOLDLOCK提示此时再查询时应该会出现阻塞,由于此时已有排它锁的存在。咱们先搁置疑问,在咱们建立测试表时毫无疑问会对主键建立汇集索引,此时咱们删除汇集索引试试。
此时咱们从新运行上述语句,此时将致使查询阻塞,以下:
咱们简短的解释一下,若是咱们对表创建了汇集索引或非汇集索引此时排它锁将消失代替的则是RangeS-U锁,因此当咱们未添加汇集索引排它锁则存在致使查询阻塞,有关RangeS-S,RangeS-U,RangeX-X,RangeI-N咱们将深刻研究。因此上述因为致使了查询阻塞,咱们结合本节所学内容,咱们利用NOLOCK来查询数据。
select * from Example WITH(NOLOCK)
此时毫无疑问将可以查询出数据,以下:
固然除非咱们意识到NOLOCK致使脏读的问题,不然谨慎用。
关于NOLOCK和UPDLOCK以及HOLDLOCK则没有什么可讲的,咱们来说讲UPDLOCK和READPAST,经过UPDLOCK和READPAST的结合咱们可以解决许多问题,好比我当前项目中对于更新预定人数,则用到了UPDLOCK和READPAST,由于考虑到并发若是固定预定人数为100,那么当出现并发时将有可能致使预定超出的状况,利用UPDLOCK则能够解决其余进程过来时对其进行修改的状况,同时结合READPAST解决脏读,同时不会阻塞,当有请求过来时咱们直接利用表变量对预定人数进行更新,若更新失败咱们再进行回滚,算是一个解决方案。同时利用UPDLOCK和READPAST还能够解决其余问题,好比,当有多个并发时咱们要根据筛选条件获取第一值,也就是说第二个请求过来时获取到的值是下一个,那么这样的问题该如何处理呢,若咱们只是简单进行处理,那么第二个请求同时过来时可能也会读取到以前读取的那个值,基于此场景,咱们能够利用UPDLOCK和READPAST来解决。咱们看以下代码就能够理解。
DECLARE @Next INTEGER BEGIN TRANSACTION -- 找到下一个知足条件的值 SELECT TOP 1 @Next = Id FROM Test WITH (UPDLOCK, READPAST) WHERE Flag = 0 ORDER BY Id ASC --若找到利用标识更新,防止下一次被读取到 IF (@Next IS NOT NULL) BEGIN UPDATE Test SET Flag = 1 WHERE Id = @Next END COMMIT TRANSACTION -- 返回咱们查询到的值 IF (@Next IS NOT NULL) SELECT * FROM Test WHERE Id = @Next
固然上述能够避免阻塞,咱们也能够在阻塞的状况下来处理利用ROWLOCK和HOLDLOCK来解决
BEGIN TRAN SELECT FROM Test WITH (HOLDLOCK, ROWLOCK) WHERE Id = 1 --TODO COMMIT TRAN
本节咱们讲述了博主比较疑惑的几种锁例如READPAST,以前未接触过,项目中在老大的指导下才知道,原本打算今天结束SQL Server基础系列,谁知中途学习时遇到了其余问题,好比还有其余四种锁类型,我还得再研究研究,真的是SQL Server基础系列最后一篇,真的不骗你,同时.NET Core也会不定时更新,欢迎你们继续关注博客和公众号。