------------------------------------------------------------------------
-- Author : HappyFlyStone
-- Date : 2009-10-12 ――2009-10-17
-- 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
-- 关键字:锁定资源 锁定 应用程序锁定 绑定 锁生命周期 实体类型
------------------------------------------------------------------------
在前面一篇我开始说到锁,然后在说明可锁定资源并没有深入的解释资源这一块,所以没深入是想在大家对锁的模式有一定了解后再来学习资源。在前面的好多用例里我们使用sp_us_lockinf这个用户过程取回的锁的相关信息都有一栏资源类型,呵呵,还有印象吧。下面我们来稍深入理解一下,然后我们对个别类型的资源再来点实例,最后说点锁的本质及生存周期和所有者等等。
4、深入可锁定资源及特殊锁定
可资源资源有哪些呢,我在前面已经用
SELECT * FROM MASTER..SPT_VALUES WHERE TYPE = ‘LR‘
进行了列表,一共有12种之多,其实我们从本篇的开始到现在一直在接触的行锁(RID),键锁(KEY),分页(PAG)、表(TAB)及对象(OBJECT)都是我们可锁定的资源,这几类我们已经接触到很多了,下面我就不常关注的几个进行一下说明。
EXT这是数据或索引页面扩展。这一块如果以后有时间整理表的数据存储或索引分页的结构时可以细细说说扩展,现在我们可以简单的理解为:扩展是一个64K的分配单元,是由连续的8个8K分页组成。SQLSERVER在表或索引分配扩展时会分配8个连续的8K空间,每一个扩展的首页号是8的倍数,但是扩展间本身不一定连续哦,这个不连续就是碎片了。在扩展上也可以加锁定,其实这也好理解,在不同的表或索引需要新的扩展时,系统为了让同一扩展不被错误使用(比如两个表同时得到一个扩展,那比较恐怖哦)而进行共享或排它锁定。不过是系统自发进行的,我们一般看不到。这种物理上的一致性我们在前面提到过一种闩锁,嘿嘿有印象不?我们也可以把这个当作一种事实上的闩锁。
DB数据库(DATABAES)。其实只要我打开一个连接,如果你使用sp_us_lockinfo一定得到一条相应当前连接的DB类型的锁定。结果如下图
select @@spid go exec sp_us_lockinfo
那么在DATABASE这种类型下有几种锁呢,为别对应哪些操作呢?能不能模拟出来呢?好,下面我们来模拟一些吧,比如删除库操作:
1、 先打开一个managerment studio,我们先创建一个数据库,库名为dblock,建好后运行一下我们前面那个工具sp_us_lockinfo
2、 打开另一个managerment studio,右击dblock进行删除操作,在弹出的窗口点确认
3、 在第二步操作后迅速切换到第一个managerment staido并运行如下代码,并得到图63
select @@spid exec sp_us_lockinfo
由上图很明显我们看到我们第一个打开的连接对Dblock有一个DB资源类型的共享锁定排斥了第二个删除操作的排它锁。再比如我们来把Dblock设置成只读,这个更好玩,多了几个锁定哦!先打开两个查询(一定要同时打开两个哦!!)
查询一:
alter database dblock SET READ_ONLY /* 查询一直进行中…… */
查询二:(结果如下图)
select @@spid exec sp_us_lockinfo
其它动作大家可以自己模拟 。
APP应用程序资源。有一个应用程序锁定模式与之对应。应用程序类型的资源锁定与我们前面讨论的所有锁定模式都不一样,前面所有的锁定全是SQLSERVER自己管理的,应用程序级锁定利用了SQLSERVER的检测阻塞及死锁的机制来锁定自己想要锁定的任何对象。比如我们现在想要达到这一种效果:一个表或过程同时只有一个进程能够运行。为了来模拟这种类型的锁定,先看如下语法:
sp_getapplock [ @Resource = ] ‘resource_name‘, [ @LockMode = ] ‘lock_mode‘ [ , [ @LockOwner = ] ‘lock_owner‘ ] [ , [ @LockTimeout = ] ‘value‘ ] [ , [ @DbPrincipal = ] ‘database_principal‘ ] [ ; ]
下面我们来模拟一下:
查询一:
exec sp_getapplock ‘testapplock‘,‘exclusive‘,‘session‘; go select * from ta --sp_releaseapplock ‘testapplock‘,‘session‘;
查询二:
exec sp_getapplock ‘testapplock‘,‘exclusive‘,‘session‘; go select * from ta /* 查询一直进行中。。。。。。 */
查询三:结果见下图
--create database dblock select @@spid exec sp_us_lockinfo
METADATA 元数据。这种类型的可锁定资源我们其实前面已经有看过,我们在修改当前库为只读时,看下图
好,到目前为止对可锁定的资源就说完了。嘿嘿,另外几个比如堆或B树类型锁定(HBT)不如以后整理索引时再旧事重提了。
5、锁的本质、生命周期及请求锁定的实体类型
首先我告诉大家SQLSERVER并不知道(或不关心)被它锁定的对象,有时即使我们能关联到对象却无法正确解析被锁定对象的结构,你相信吗?猛一听有点吓人吧,这么重要的任务交给它管理,它不知道被管理的东西是何方神圣?呵呵,下面稍稍说一点内部的结构。
锁是SQLSERVER的一种内存结构,并不是一种物理的数据结构,所以这种元数据追踪是无法被记录的。关闭或中止一个连接相应的部分锁定信息就会消失。这个结构书上都称锁块(Lock block),用这个锁块来跟踪数据、锁定及对锁相关的信息描述。显然这些锁块是要被不同进程所拥有的,SQL有另一个叫所有者块来管理这些,所有者块与锁块之间通过所有者的指针相连。
在锁块里有一个专门对锁定资源的描述结构叫资源块,资源块负责维护资源名称、锁块的指针、已经授权的所有者指针列表、转换中的所有者指针列表、等待的所有者列表。对资源块有一个类型描述,占用一个字节,这个类型描述我在前面已经通过SQL语句进行列表过:
查询一:
SELECT * FROM MASTER..SPT_VALUES WHERE TYPE = ‘LR‘
对具体的资源描述有12个字节的内存结构来记录。我们在2000下使用过一个表syslockinfo,这些信息在rsc_bin中有体现,这个字段对资源进行描述,根据类型的不同,这12个字节有着不同的分工组合。在这里我还要提醒2000下的用户,rsc_bin在2005里意义不再等同于2000,但是仅是微小的差别。为了说明我上面的论点,所以对syslockinfo进行一下深入,证明SQLSERVER对被的对象是不关心的。我们先来实际看看这个表的数据,结果集如下图
通过这个图我们应该看到rsc_bin的最两个字节和2000下不一样了,这两个字节交换后就是资源块的类型,这一点大家要注意哦。
查询一
CREATE TABLE TB (ID INT primary key, COL VARCHAR(16)) GO INSERT INTO TB SELECT 1,‘A‘ GO SET TRANSACTION ISOLATION LEVEL repeatable read BEGIN TRAN SELECT * FROM TB WHERE id BETWEEN 1 AND 5
对上图我们很容易理解类型为objec及page的rsc_bin意义。重点说一下KEY类型的共享锁定。这一条记录在syslockinfo中rsc_bin列,前6个字节是分区ID,我们可以通过sys.partitions查找到相应的分区。紧接着的6个字节(上图用红粗线标识的部分)来头不小,是根据当前索引的所有索引列生成的中介现象哈希值,在这里对这个哈希会值是无法反推的。如果上面的表及页面我们可以查找到相应的结构的话,那个对这个键值锁定真的就是不是不想知道结构,而是真的不知道什么结构了哈。哈哈,这儿证明我上面的结论吧。其实我们不用担心,因为虽然不能反推,但是阻塞或死锁机制是还是可以查找匹配的呀,也就是如果相同时生成的哈希一样,那样SQLSERVER还知道这是相同听资源,阻塞或死锁已经发生。嘿嘿,其实只要关心被它锁定资源的标识串就足以管理阻塞或死锁,
请求锁定的实体类型,这个在前面的几乎每一个图表里都可以看到的一列。图68
在这个图里我们只看到两类,还有另外两类,一共四类:事务类型,游标类型、事务工作空间类型、会话类型。
事务类型:这个就不用多说了吧,上面我们显式的开始一个事务时,此连接都会有一个事务类型的实体。
事务工作空间类型:这是SQLSERVER2005引入的。SQLSERVER2000把所有除事务与游标类型的实体全归为会话型。通常我们看到的数据库级锁定就是空间类型的,也就是基本一个会话就对应一个工作空间,这样会话中所有的数据库级锁定会保留在相同的工作空间里,这对于绑定会话是有十分重要的意义。
就绑定来说MS明确会在以后的版本不支持了。因为我们在以前版本用过,所以这儿稍微说说。
先说说绑定出现的背景吧,首先明确一点锁定的阻塞是发生在不同的SQL进程间,也就是进程自己是不会把自己锁起来的。接着再明确一点何为相同进程,每一个独立的SQL连接,但是我们不要错误的认为同一个用户同一个程序就一定一个连接、一个进程。事实上同一个用户同一个应用程序拥有多个连接即进程的可能程是非常大的。那么我们显然就可以想像到同一个用户同一个程序自己可能锁定自己,因为它拥有多个连接是吧,而SQLSERVER是无法知道同一应用程序拥有哪几个连接,也不知道这些连接间怎么关联的。那么这个问题怎么解决呢。在SQL6.5以后SQL2005以前就是用绑字,当然2005也支持,但是MS明确在以后的版本可能不支持了。
绑定分为本地绑字及分布式绑定,它完成不同连接在同一事务空间内的交互或不同服务器上工作单元的交互。一般绑它以第一个连接进行连接并申请“绑定令牌”,这个令牌就是一个事务空间的变量字符串,这个变量字符串可供后续的连接进行检索,并且这个变量串对应的事务空间就会被共享。
下面简单的做一个测试吧!
查询一
begin tran declare @token varchar(100) exec sp_getbindtoken @token output select @token select * from tb update tb set col = ‘b‘ where id = 1 /* ------------------------- .Y4bBahH9aCGCj[a[0b-:-5---03E=-- (1 行受影响) ID COL ----------- ---------------- 1 A (1 行受影响) (1 行受影响) */ commit tran –-这一步留到查询三执行完后执行,一定要注意 /* 此会话中的活动事务已由另外一个会话提交或终止。 消息3902,级别16,状态1,第1 行 COMMIT TRANSACTION 请求没有对应的BEGIN TRANSACTION。 */
查询二
select * from tb where id = 1 /* 查询一直进行中…… */
查询三
exec sp_bindsession ‘.Y4bBahH9aCGCj[a[0b-:-5---03E=--‘ update tb set col = ‘c‘ where id = 1 commit tran select * from tb /* (1 行受影响) ID COL ----------- ---------------- 1 c (1 行受影响) */
在查询一里我们先是申请一个“令牌”,然后开始修改ID=1的记录,注意 我们是更新为B,然后我们打另一连接并执行对ID=1的查询,发现查询一直进行中,如果你打开另一连接执行sp_us_lockinfo你会发现在共离锁定。在查询三里我们先共享刚才的连接,然后进行数据的修改,注意我们修改ID=1的COL值为c并提交事务,这时我们查询数据里的数据发现记录是第三个查询里执行修改的结果,显然这两个进程间没阻塞。如果此时我们现回到第一个查询里执行commit tran你发现这是提交操作失败,因为当前事务已经在第三个查询里提交了,所以出相应的错误。
最后说明一下绑定理论没有个数的限制,事实上如果程序不严谨也麻烦,在2005有一个多个活动结果集配置选项,可以实现与绑定相似基本功能,默认下是关闭,大家可以自行测试。
游标型:这个我们经常会用到,只是没有注意。这种实体必须在使用游标时显式申请,每提取一行时得到一个游标锁,直到提取下一条或关闭游标,否则即使你提交或rollback事务这个游标锁定也不会释放,这怎么来理解呢,下面我来实例:
BEGIN TRAN DECLARE @f1 varchar(10) DECLARE cur CURSOR SCROLL_LOCKS FOR SELECT f1 FROM ta FOR UPDATE OPEN cur FETCH NEXT FROM cur INTO @f1 EXEC sp_us_lockinfo – 第一次图69 WHILE @@FETCH_STATUS = 0 BEGIN UPDATE TA SET s1 = 0 WHERE CURRENT OF cur EXEC sp_us_lockinfo –第二次图6a BREAK;--只取回一条了,其它记录类同,所以不再模拟 FETCH NEXT FROM cur INTO @f1 END EXEC sp_us_lockinfo—第三次图6b CLOSE cur DEALLOCATE cur EXEC sp_us_lockinfo—第四次图6c COMMIT EXEC sp_us_lockinfo—第五次图6d
(图69)
(图6a)大家注意这一张图,因为这是一个可更新游标,我在取出第一行记录时企图去更新当行的信息,因为此时事务型和游标型实体同时出现了。
(图6b)
图6b和图6相同,我们只是模拟一下取完所有行后数据库上的锁定行为。
(图6c)这是关闭并删除游标引用后在数据库里留下的锁定,也就是在关闭游标后游标型实体锁定不存了,只有了事务型的实体,因为我们这是可更新游标,并且我在实例中有更新动作,所以最后还有事务型锁定存在。
(图6d)只有当前会话的事务工作空间型实体了
给大家留一个课后作业:
1、 对表结构作由A修改成B,会得到什么样的结果,注意哦,这时会出现一个NULL的锁定哦,这个锁的作用是为插入准备的(以上实例测试在A结构下测试的):
A:
create table ta(f1 int,s1 char(10)) insert into ta select 1,‘a‘ ===>增加一个主键,这意味着什么?
B:
create table ta(f1 int primary key ,s1 char(10)) insert into ta select 1,‘a‘
2、 在上一问的B结构下,执行如下SQL看看又是什么样的结果,仔细比较
BEGIN TRAN DECLARE @f1 varchar(10) DECLARE cur CURSOR SCROLL_LOCKS FOR SELECT f1 FROM ta --FOR UPDATE OPEN cur FETCH NEXT FROM cur INTO @f1 EXEC sp_us_lockinfo WHILE @@FETCH_STATUS = 0 BEGIN UPDATE TA SET s1 = 0 WHERE f1 = 0 EXEC sp_us_lockinfo BREAK;-- FETCH NEXT FROM cur INTO @f1 END EXEC sp_us_lockinfo CLOSE cur DEALLOCATE cur EXEC sp_us_lockinfo COMMIT EXEC sp_us_lockinfo
会话型:会话型锁定必须显式的申请,在上面我介绍APP类型资源时就有明确的会话型锁定实体类型,大家可以看看篇首的APP类型资源。下面给一个图看看:
锁的生命周期:最后说说锁的持续时间,就像我们程序变量的生命周期一样,锁定也有一个持续时间。持续时间事实上取决于锁的模式及隔离等级,另外我们还可以使用锁定提示来改变锁的持续时间。因为这个持续时间和我们程序变量的生命周期一样,所以习惯称为生命周期。
关于锁定提示改变锁的生命周期这在以后再说,我把几个等级下常规模式的锁定做一个列表:
(对快照模式下锁定模式有点错误,特此更新,深表歉意)
这一篇够长,收笔有点匆忙,不过大体上应该说我的意思全部表达完了,如果有不当之处欢迎大家指正。