您的位置:首页 > 其它

事务隔离级别与阻塞

2016-03-10 21:44 239 查看
本篇文章参考《Microsoft SQL Server企业级平台管理实践》中第9章和第10章 阻塞与死锁
未提交读(read uncommitted)
指定语句可以读取已由其他事务修改但尚未提交的行。也就是说,允许脏读。
未提交读的意思也就是,读的时候不申请共享锁。所以它不会被其他人的排他锁阻塞,它也不会阻塞别人申请排他锁。
SELECT * FROM TABLE WITH(NOLOCK)
已提交读(read committed)--防脏读
指定语句不能读取已由其他事务修改但尚未提交的数据。这样可以避免脏读。其他事务可以在当前事务的各个语句之间更改数据,从而产生不可重复读取数据和幻像数据。
可重复读(repeatable read)--防重复读
指定语句不能读取已由其他事务修改但尚未提交的数据,并且指定,其他任何事务都不能在当前事务完成之前修改由当前事务读取的数据。
对事务中的每个语句所读取的全部数据都设置了共享锁,并且该共享锁一直保持到事务完成为止。
可序列化(serializable)--防幻影
语句不能读取已由其他事务修改但尚未提交的数据
任何其他事务都不能在当前事务完成之前修改由当前事务读取的数据
在当前事务完成之前,其他事务不能使用当前事务中任何语句读取的键值插入新行
SELECT * FROM TABLE WITH(HOLDLOCK)

SELECT p.session_id
,p.request_id
,p.start_time
,p.status
,p.command
,p.blocking_session_id
,p.wait_type
,p.Wait_time
,p.wait_resource
,p.total_elapsed_time
,p.open_transaction_count
,p.transaction_isolation_level
,SUBSTRING(
qt.text
,p.statement_start_offset/2
,(
CASE WHEN p.statement_end_offset=-1 THEN LEN(CONVERT(NVARCHAR(MAX) ,qt.text))*2
ELSE p.statement_end_offset
END-p.statement_start_offset
)/2
) AS "SQL statement"
,p.statement_start_offset
,p.statement_end_offset
,batch = qt.text
FROM   MASTER.sys.dm_exec_requests p
CROSS APPLY sys.dm_exec_sql_text(p.sql_handle) AS qt
WHERE  p.session_id>50


View Code
运行DBCC INPUTBUFFER(spid)可以获得从客户端发送到SQLServer实例的最后一个批处理语句。这句话的优点是不管连接是否正在运行,都会返回结果。缺点是它返回的是整个批处理语句,而不是当前正在执行的子句。
找到以后,一般就能确定阻塞是否是当前运行的语句造成的。如果阻塞发生在表A上,而当前这句话不可能在这个表上加相应的锁,那基本上可以断定阻塞是由于一个先前开启的事务导致的。
c、阻塞的源头当前的状态是什么?是一直在执行,还是已经进入空闲状态?

SELECT spid,kpid,blocked,waittype,status FROM sys.sysprocesses s WHERE spid>50


如果kpid和waittype两个字段都是0,就是一个处于空闲状态的连接。
d、如果它一直在执行,为什么要执行这么久?
如果一个连接的kpid<>0(连接拿到来了一个线程资源),waittype=0(它不需要等待任何资源),它的状态就会是runnable或running。如果一个连接的kpid<>0 and waittype<>0,则说明它要等待某个资源才能继续执行。这时候连接状态一般会是suspended。
e、如果已经进入空闲状态,那为什么没有释放锁资源?

SELECT spid,kpid,blocked,waittype,status,open_tran FROM sys.sysprocesses s WHERE spid>50


如果一个连接的kpid=0(连接没有占用线程资源) and waittype=0(它不需要等待任何资源),那么这个连接已经完成了客户端发过来的所有请求,现在进入了空闲状态,正在等待客户端发送新的请求。
按道理在这种情况下连接应该释放先前申请的锁资源才对。如果这时它还是阻塞的源头,一般是因为它有先前开启的事务没有及时提交。这可以通过检查sysprocesses里的open_tran>0确认。
f、其他被阻塞的连接它们想要做什么?为什么也要申请这些锁资源?
使用步骤b里的脚本,也能够知道被阻塞住的连接正在运行的语句。然后再去比较sp_lock的结果,就能大致判断它申请的锁数量是否合理。如果不是很合理,可以通过优化语句、加合适的索引解决。
举个例子(已提交读)
会话1(spid=54)开启事务更新数据尚未提交

USE AdventureWorks2008;
GO
BEGIN TRANSACTION;
-- 修改1
-- 休假时间减8
UPDATE HumanResources.Employee
SET VacationHours = VacationHours - 8
WHERE BusinessEntityID = 4;


会话2(spid=55)读取会话1中修改的行

USE AdventureWorks2008;
GO
BEGIN TRANSACTION;
-- 查询1
-- 这个查询会被会话1阻塞
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;


在已提交读的隔离级别下,指定语句不能读取已由其他事务修改但尚未提交的数据。
此时SQLServer里阻塞情况



SPID 55的blocked字段不为0,而是54。SPID 54的相应字段为0。可以得出结论,此时有阻塞发生,55被54阻塞住了。
和阻塞有关的连接是从哪些应用来的



发生阻塞和阻塞别人的连接分别是SPID 55和SPID 54,它们都是来自WORK上的Management Studio。
现在阻塞发生在哪个或哪些资源上



SPID 55等待表Employee的主键PK_Employee_BusinessEntityID的S锁,而SPID 54在表Employee的主键PK_Employee_BusinessEntityID持有X锁。
阻塞的源头是在做什么事情的时候申请了这些锁?为什么会申请这些锁?



由于SPID 54已经是空闲连接,因此只能用DBCC INPUTBUFFER(spid)返回最后一个批处理语句。返回的语句是更新Employee表,因此它在对应键上申请X锁。也就是说阻塞是由这个UPDATE语句造成的。注意,SPID 54中也可以由UPDATE...GO...SELECT...此时,DBCC INPUTBUFFER(spid)仅会返回SELECT部分,显然SELECT只会加S锁,它和SPID 55相互兼容,那基本上可以断定阻塞是由于一个先前开启的事务导致的。
阻塞的源头当前的状态是什么?是一直在执行,还是已经进入空闲状态?如果已经进入空闲状态,那为什么没有释放锁资源?



SPID 54的kpid和waittype两个字段都是0,就是一个处于空闲状态的连接。open_tran=1,说明有一个未提交的事务。
其他被阻塞的连接它们想要做什么?为什么也要申请这些锁资源?



被阻塞的SPID 55从Employee表读取被SPID 54修改的行,需申请S锁,无法通过优化语句、添加索引解决。只能在阻塞源头上,让其更新完成后及时提交事务。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: