sqlserver调优-锁

继续接着上一篇文章,这篇文章我们主要看看模拟多客户的环境,同样是sqlserver,为什么有些表几千万-甚至上亿数据量依然响应很快?为什么有些表几十万-几百万就延时很大甚至拖垮应用程序。没错确实有这样的事情发生,我们今天就来看看到底是怎么发生的?今天的讨论抛开硬件资源的问题,因为大家知道数据库最大的瓶颈在内存。说到数据库就不得不说一下事务,可以这么说事务是关系型数据库的一个最基础的功能,我们常说的数据库ACID。试想一下ACID的基础靠谁保证?没错就是数据库引擎提供的锁机制。事务我们简单的发散一下,因为在.Net技术平台上对事务的支持还是不错的,比如我们的ADO.NET它就集成了数据库事务,包括后期的EF EFCORE等等因为他们的数据访问底层还是ADO.NET。还有甚至分布式事务DTC,比如WCF平台事务流,它就是基于windows平台提供的DTC分布式事务的基础结构实现的,我们知道在分布式事务DTC中,有一个资源管理器的角色,而我们一般接触的这个资源管理器就是数据库。我们继续正题。
事务隔离级别
我们了解事务的隔离是依靠锁来实现,在sqlserver数据库里面支持4中事务隔离级别(隔离级别有高有低),这个很好理解,sqlserver数据库为了应付不同的行业领域设计的。比如银行可能就需要最高级别的事务隔离。我们今天讨论的内容就是基于可重复读这个事务隔离级别(数据库默认是已提交读)。可重复读简单来说就是1.事务修改尚未提交的行,其他事务不能读取。2.其他事务不能修改当前事务读取的行,直到当前事务完成,提交或回滚。事务隔离级别就简单介绍到这里,下面简单介绍下锁的一些概念性的东西。
Sqlserver数据库引擎可以锁定的资源大概有一下几种。
RID:锁定堆表的某一行;
KEY:锁定索引上的某一行;
PAGE:锁定8k的数据页或者索引页;
TABLE:锁定整个表;
DATABASE:锁定整个数据库;
以上就是数据库引擎可以锁定的资源,我只列出了我熟悉的并且比较常用的,其他还有架构啥的等等,这里不讨论。
Sqlserver数据库引擎资源锁类型大概有一下4种。
共享(S)锁:读取数据select操作,如果资源上存在S锁,其他事务不能修改改资源;
更新(U)锁:更新数据update操作防止死锁的一把锁,我们可以这么理解,更新操作需要先读取资源获取S锁之后再升级为X锁,那么问题来了,S锁兼容,在可重复读或以上级别,S锁需要事务完成才会释放,如果这个时候同一个资源有多把S锁,并且需要修改数据升级为X锁,因为X锁不兼容,最后发生死锁。U锁不同,在同一个资源上只能有一把U锁,这样如果发生update操作就能顺利升级为X锁,完成更新操作。
排他(X)锁:最高级别的隔离锁,资源获取X锁,任何锁资源都无法兼容,也就是独占,除开最低的隔离级别未提交读,我相信没人会用这个隔离级别;
意向(I)锁:I锁主要作用于提高访问性能,主要表级资源。I锁又分三种,意向共享(IS),意向排他(IX),意向共享排他(SIX)。
Sqlserver锁资源兼容性
不解释,都在图里了。概念就到这,下面看实例效果,演示锁部分,我随便创建了两张表如下。
 
两张是关联关系表,数据量均在10w左右,test1表id为聚集索引,test2表只有一个非聚集索引tid。某个具体数据库上面锁资源的查看,我们可以通过sql查看代码如下。
1 select  request_session_id,resource_type,resource_associated_entity_id,request_status,request_mode,
2 resource_description,p.object_id,object_name(p.object_id) as object_name, p.*
3 from sys.dm_tran_locks d left join sys.partitions p
4 on d.resource_associated_entity_id =p.hobt_id where resource_database_id=db_id('数据库名')
5 order by request_session_id,resource_type,resource_associated_entity_id
后续查看锁资源的操作我就不贴代码了,好了准备工作就到这,我们先修改数据库事务隔离级别为重复读提交,因为它比较有代表性,修改事务隔离级别代码。
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
2 GO
SELECT
1 begin tran
2 select id, name, age, createTime, createBy from t_test1
3 where id in(12, 10000)
开启一个事务并且未提交,where in 查询id=12和10000的两个数据,选择12和10000只是想错开数据页,下面我们看看计划和锁资源情况
说明一下,我当前的sessionid为54,执行计划内容说明是通过聚集索引seek查找,这个没什么悬念,id本来就算聚集索引,并且返回两条数据。继续看锁资源表,1.首先在db级别申请了一把s锁,其实目的就算防止db修改删除操作。2.在table级别申请了一个is锁,也是防止修改操作,比如添删改字段索引等等操作。3.在两个数据的聚集索引键上面分别申请了两把s锁,同样防止修改操作。4.在数据页上面同时也申请了两把is锁。此时如果另外一个用户尝试update这两条数据会被阻塞,直到该事务完成,如果是读提交级别是可以完成修改。操作看效果,我现在模拟另外一个用户,尝试修改id为12的这条数据,看代码。
1 delete from t_test1 where id = 12
执行结果
模拟的用户sessionid为64,执行del语句一直被阻塞,这个时候当我提交sessionid为54的事务,sessionid64的删除操作即可完成,这一步我就不测试了。接下来我们看看非聚集条件查询会怎么样,我为表test1字段name添加一条非聚集索引,看代码。
1 begin tran
2 select id, name, age, createTime, createBy from t_test1
3 where name ='10000bbbb'
计划和锁资源情况
这里返回1条数据,却有两个key被申请了s锁。为什么?看上面的计划,先由非聚集索引seek到1条数据(在这个key上加了s锁),由于非聚集索引叶上只有非聚集字段值和聚集字段值所以还需要聚集索引seek数据,这样聚集索引的key也就被加了s锁,其他锁资源情况一样,其他操作情况跟上面也是一样。以上两条查询是索引seek,下面我们来看一条索引scan的查询,看看所资源怎么样。
1 begin tran
2 select id, name, age, createTime, createBy from t_test1
3 where [createTime] = '2015-07-06 22:28:55.000'
计划和锁资源情况
where条件筛选了未加索引的createTime字段,执行计划没办法选择了聚集索引扫描,最后返回了那么一条数据。由于采用聚集索引scan,导致所到page都申请is锁,并且还对聚集索引key申请了一个s锁,我大概看了下page上申请的is锁大概有1000个。是不是比较恐怖?SELECT最后看下join操作锁资源会是什么样子的。
1 begin tran
2 select a.id, a.name, a.age, a.createTime, createBy, b.tid from t_test1  a
3 inner join [dbo].[t_test2] b
4 on a.id = b.tid
5 where a.id = 10000
计划和所资源
最终返回一条数据,所资源情况也差不多,两张表因为都有聚集索引,分别在两张表的聚集索引key上申请了s锁,同时两张表也申请了is锁,page上的is锁主要都是子表的,因为子表用的是scan扫描。好了select操作就到这,接下来看看update操作。
UPDATE
1 begin tran
2 update t_test1 set createBy = 't' where name = 'ghf'
简单说明该表有聚集索引,createBy没有索引,name字段有非聚集索引。
计划和所资源情况
通过非聚集索引seek到一条数据,所以在非聚集索引key上申请了一把u锁,然后通过聚集索引做update操作,所以在聚集索引key键上申请了x锁。同时其他资源上的锁均有升级,聚集索引所在page上的锁升级为ix锁,说明更新操作所有涉及到的资源情况都会升级更高的级别,表示兼容粒度越来越小。现在我们在sessionid64上面查询id为10000的数据看什么效果。
1 select id, name, age, createTime, createBy from t_test1
2 where id =10000
结果
一直被阻塞,为什么?因为聚集索引key键为10000的这个数据申请了x锁,所以session64这条查询语句想要申请s锁,不兼容所以阻塞等待。我们继续修改上面update语句,set字段为非聚集索引字段,where条件为聚集索引,看看什么效果。
1 begin tran
2 update t_test1 set name = 'y' where id = 10000
结果
奇怪的是有三条数据输出在key上面申请了x锁?我们结合执行计划来分下一下,首先通过聚集索引seek到一条数据,并且需要修改的也是这条聚集索引对应的存放数据,所以在这个聚集key上申请了一把x锁,同时set的字段为非聚集字段name,因为非聚集索引数据列里有name字段,所以这个地方也会申请两把x锁,删除老的和添加新的,这个很容易理解,非聚集字段不能直接修改,因为需要排序。我们在来看下最后一种没有索引的字段scan,update操作锁资源会是什么样子。
1 begin tran
2 update t_test1 set name = 'a' where createTime = '2015-07-09 19:04:03.000'
结果
符合预期,除了多了很多page上面申请了iu锁,其他几乎一样。最后我们看最后一个操作insert。
INSERT
1 insert into t_test1 values('t1', 20, getdate(), 'c1')
结果
insert感觉简单多了,表上有几个索引就针对索引key申请几把x锁。其实也不难理解,增删改数据需要维护索引资源,所以增加一条数据时会把表上所有的索引key键添加一把x锁。t_test1我刚删除了一条非聚集索引,剩下两条,所以有两把x锁。
简单总结
通过上面的实例演示,是不是觉得,索引和锁有密不可分的关系,同时对性能影响也是非常大的。回到我们开始的问题,试想如果一张表频繁的增删改查,并且因为索引的利用率不高,是不是会发生几十万或者百万数据就会停止服务?好了就到这吧,数据库文件系统太复杂,我们要学的还有很多很多。不说了我看电视了。
 
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章