SQL2005中的事务与锁定(三)
------------------------------------------------------------------------
-- Author : HappyFlyStone
-- Date : 2009-10-03 15:30:00
-- Version: Microsoft SQL Server 2005 - 9.00.2047.00 (Intel X86)
-- Apr 14 2006 01:12:25
-- Copyright (c) 1988-2005 Microsoft Corporation
-- Enterprise Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
-- 转载请注明出处,更多请关注:http://blog.csdn.net/happyflystone
-- 关键字:隔离等级 锁定 意外数据行为 Lock Hint
------------------------------------------------------------------------
在前面的两篇里我从纯理论上把事务相关的知识作了一个梳理,有人看了一定觉得无味了吧,好这一篇我们加入一点T-SQL语句把前面所说有东东关联起来,我们人为产生锁定来理解不同的意外数据行为在不同隔离等级下的表现,顺便再重温一下意外数据行及隔离等级,让大家对交易事务有一个直观的认识。
在进行实例前不得不先介绍一点锁的知识,注意这儿只是简单的说一下,不作深入讨论。我们根据用户访问资源的行为先归纳出几种锁,这几种锁在下面的实例里会出现,它们为:共享锁定、排它锁定、更新锁定及用意向这个限定词限定的三种锁(意向共享、意向排它、意向更新),当当然还有其它的模式,我们在下一篇再说。意向锁的存在是解决死锁的发生,保证进程在申请锁定前确定当前数据是否存在不兼容性的锁定。
先对上面提到的锁作一个简单的描述,更详细的下面再说。
共享锁定发生在查询记录时,直观就是我们select啦,但是并不是只有select才有共享锁定。一个查询记录的语句必须在没有与共享锁定互斥锁定存在或等待互斥锁定结束后,才能设置共享锁定并提取数据(互斥不互斥就是锁的兼容性,这在以后再说明)。
排它锁定发生在对数据增加、删除、修改时,事务开始以后语句申请并设置排它锁定(前提是没有其它互斥锁定存在),以明确告知其它进程对当前数据不可以查询或修改,等待事务结束后其它进程才可以查询或修改。
更新锁定是一个介于共享与排它之间的中继锁定,比如我们带where条件的update语句,在查询要更新的记录时是设置共享锁定,当要更新数据时这时锁定必须由共享锁定升级成更新锁定继而升级为排它锁定,当排它锁定设置成功才可以进行数据修改操作。显然也是要要求在锁升级的过程中没有互斥锁定的存在。简单的理解更新锁定是一个中继闸一样,把升级成排它锁定进程“序列化”,以解决死锁。最后重点说明一下,数据更新阶段是要对数据排它锁定不是更新锁定,不要被字面意思训导哦。
最后说一下在上述锁定模式下的互斥,共享锁定只与排它锁定互斥,更新锁定只与共享锁定不互斥。
在进行具体实例前我们一定要有一个工具来对我们实例过程进行监控,好,下面我写了一个过程,在需要时直接调用就行,过程如下:
Create Proc sp_us_lockinfo
---------------------------------------------------------------------
-- Author : HappyFlyStone
-- Date : 2009-10-03 15:30:00
-- BLOG : http://blog.csdn.net/happyflystone
-- 申明 :请保留作者信息,转载注明出处
---------------------------------------------------------------------
AS
BEGIN
SELECT
DB_NAME(t1.resource_database_id) AS [数据库名],
t1.resource_type AS [资源类型],
-- t1.request_type AS [请求类型],
t1.request_status AS [请求状态],
-- t1.resource_description AS [资源说明],
CASE t1.request_owner_type WHEN 'TRANSACTION' THEN '事务所有'
WHEN 'CURSOR' THEN '游标所有'
WHEN 'SESSION' THEN '用户会话所有'
WHEN 'SHARED_TRANSACTION_WORKSPACE' THEN '事务工作区的共享所有'
WHEN 'EXCLUSIVE_TRANSACTION_WORKSPACE' THEN '事务工作区的独占所有'
ELSE ''
END AS [拥有请求的实体类型],
CASE WHEN T1.resource_type = 'OBJECT'
THEN OBJECT_NAME(T1.resource_ASsociated_entity_id)
ELSE T1.resource_type+':'+ISNULL(LTRIM(T1.resource_ASsociated_entity_id),'')
END AS [锁定的对象],
t4.[name] AS [索引],
t1.request_mode AS [锁定类型],
t1.request_session_id AS [当前spid],
t2.blocking_session_id AS [锁定spid],
-- t3.snapshot_isolation_state AS [快照隔离状态],
t3.snapshot_isolation_state_desc AS [快照隔离状态描述],
t3.is_read_committed_snapshot_on AS [已提交读快照隔离]
FROM
sys.dm_tran_locks AS t1
left join
sys.dm_os_waiting_tasks AS t2
ON
t1.lock_owner_address = t2.resource_address
left join
sys.databases AS t3
ON t1.resource_database_id = t3.database_id
left join
(
SELECT rsc_text,rsc_indid,rsc_objid,b.[name]
FROM
sys.syslockinfo a
JOIN
sys.indexes b
ON a.rsc_indid = b.index_id and b.object_id = a.rsc_objid) t4
ON t1.resource_description = t4.rsc_text
END
GO
/*
调用示例:exec sp_us_lockinfo
*/
exec sp_us_lockinfo
/*
*/
drop proc sp_us_lockinfo
最后介绍一个隔离等级设置命令:
SET TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SNAPSHOT
| SERIALIZABLE}[;]
好,下面开始实例“快乐”之旅了。
五、隔离等级实例
测试数据准备:
CREATE DATABASE testcsdn;
GO
CREATE TABLE TA(TCID INT PRIMARY KEY,TCNAME VARCHAR(20))
INSERT TA SELECT 1,'AA'
INSERT TA SELECT 2,'AA'
INSERT TA SELECT 3,'AA'
INSERT TA SELECT 4,'BB'
INSERT TA SELECT 5,'CC'
INSERT TA SELECT 6,'DD'
INSERT TA SELECT 7,'DD'
GO
约定:以下提及的查询N,都是打开一个新连接执行查询
1、 未提交读(uncommitted Read)
概念回顾:未提交读是最低等级的隔离,允许其它进程读取本进程未提交的数据行,也就是读取数据时不设置共享锁定直接读取,忽略已经存在的互斥锁定。很显然未提交读这种隔离级别不会造成丢失更新,但是其它意外行为还是可以发生的。它和select 加锁定提示NOLOCK效果相当。
测试实例:
查询一:
SELECT * FROM TA WHERE TCID = 1
BEGIN TRAN
UPDATE TA
SET TCNAME = 'TA'
WHERE TCID = 1
--COMMIT TRAN --Don't commit
SELECT * FROM TA WHERE TCID = 1
SELECT @@SPID
/*
tcid Tcname
----------- --------------------
1 AA
(1 行受影响)
(1 行受影响)
tcid Tcname
----------- --------------------
1 TA
(1 行受影响)
SPID
------
54
(1 行受影响)
*/
查询二:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM TA WHERE TCID = 1
/*
tcid Tcname
----------- --------------------
1 TA
(1 行受影响)
*/
--显然未提交读模式我们读到SPID=54未提交的数据。
查询三:
SELECT * FROM TA WHERE TCID = 1
--查询一直进行中…… 无结果
--因为缺省下已提交读级别,所以修改数据设置了排它锁定必须等到SPID=54的事务结束
查询四:
这个时候如果我们回头到查询一里执行commit tran ,你会发现查询三会得到结果,并且是查询一修改后的结果,如果你改用rollback ,那么结果就是原来的值不变,这个你们自己再测试。
1、 已提交读(Read Committed)
概念回顾:已提交读是SQL SERVER的缺省隔离级别,悲观模型下是用锁定,乐观模型下使用行版本控制器。这个设置可以通过SET READ_CIMMITTED_SNAPSHOT来修改。在悲观模型下对于读取来说设置共享锁定仅阻止排它锁定,并在数据读取结束自动释放,其它进程方可进行修改操作。也就是说读不会阻止其它进程设置共享及更新锁定,仅阻止排它锁定。在悲观模型下对于修改数据来说设置排锁定阻止所有锁定请示,必须等到排它锁定释放。这个级别的隔离解决了脏读的意外行为。
A、 READ_COMMITTED_SNAPSHOT为OFF的情况(缺省)
I、读数据测试
查询一:
BEGIN TRAN
--用锁定提示模拟共享锁定,并强制共享锁定持续到事务结束
SELECT * FROM TA with(holdlock) WHERE TCID = 1
--COMMIT TRAN --Don't commit
SELECT @@SPID
/*
tcid Tcname
----------- --------------------
1 CA
(1 行受影响)
------
54
(1 行受影响)
*/
查询二:悲观模型下已提交读级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
UPDATE TA
SET TCNAME = 'TA'
WHERE TCID = 1
--查询一直没有结果,显然我们验证了共享锁定阻止了排它锁定。
查询三:
exec sp_us_lockinfo
--结果大家自己运行看结果。
II、修改数据测试
查询一:
SELECT * FROM TA WHERE TCID = 1
BEGIN TRAN
UPDATE TA
SET TCNAME = 'READ COMMITTED LOCK'
WHERE TCID = 1
--COMMIT TRAN --Don't commit
SELECT @@SPID
/*
tcid Tcname
----------- --------------------
1 TA
(1 行受影响)
------
54
(1 行受影响)
*/
查询二:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM TA WHERE TCID = 1
/*
--查询一直进行中……被锁定无结果
--修改数据设置了排它锁定必须等到SPID=54的事务结束
*/
查询三:
exec sp_us_lockinfo
/*
*/
A、 READ_COMMITTED_SNAPSHOT为ON的情况
先修改当前当前库的READ_COMMITTED_SNAPSHOT为ON
ALTER DATABASE TESTCSDN
SET READ_COMMITTED_SNAPSHOT ON
GO
查询一:
SELECT * FROM TA WHERE TCID = 1
BEGIN TRAN
UPDATE TA
SET TCNAME = 'READ COMMITTED SNAP'
WHERE TCID = 1
--COMMIT TRAN --Don't commit
SELECT @@SPID
/*
TCID TCNAME
----------- --------------------
1 AA
(1 行受影响)
(1 行受影响)
------
56
(1 行受影响)
*/
查询二:因为启用行版本控制器来锁定数据,保证其它进程读取到虽然被排它锁定但在事务开始前已经提交的保证一致性的数据。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM TA WHERE TCID = 1
/*
TCID TCNAME
----------- --------------------
1 AA
(1 行受影响)
*/
查询三:
exec sp_us_lockinfo
----------------------------------------------------------------------------------------------------------------
这一篇太长了,来个美丽的分隔符,明天继续。
请大家继续关注我的blog: http://blog.csdn.net/happyflystone