您好,登录后才能下订单哦!
队列用来有序地锁定关系与非关系型的Oracle结构。关系型结构可能是Oracle的数据字典表或应用程序表。例如,当Oracle更新sys.col$表或一个应用程序更新它的employee表,队列将会被调用。如果一个服务器进程被锁定的表所阻止,不仅仅会post一个enqueue wait等待事件,还会在v$lock,dba_lock,v$enqueue_statistics与其它视图中显示锁信息。非关系型结构被锁定是为了阻止不合适的更改比如library cache cursor。
顾名思义,队列是非常有序的,并确保以非常确定的方式更改结构。进程的入队列请求会被推送到适当的队列上,当它需要处理时,它的入队列条目会从队列中弹出(也叫作dequeue)。这里并没有什么令人兴奋的地方,但是排队不是为了冒险,而是为了确保以一种非常有序的、类似会计的方式更改Oracle结构。
Oracle维护了数量惊人的队列。在Oracle 10gr2中有208种队列,在Oracle 11gr1中有247种队列。但不必惊慌,因为你可能只会遇到几个排队的人。另外,如果您是一位经验丰富的DBA,您已经处理过使用enqueue的行级和表级锁。
诊断Enqueue等待
当解决队列问题时,首先判断队列类型,然后确定所涉及的SQL,最后根据您对应用程序和相关Oracle内部的知识开发解决方案。在深入研究最常见的排队等待(事务(TX)排队)之前,务必了解如何确定正在等待哪个排队和oracle 10g之前和之后版本中的相关会话。
在Oracle 10g之前,所有队列的等待事件都是enqueue。这确实很不幸,因为这要从v$lock或v$session_wait中取样来确定队列名称。下面的SQL语句用来从v$session_wait中来确实enqueue名。会话4388已经锁表,没有等待锁,因此没有显示。队列中的第一个会话是4387,紧接着是会话4393。判断正在运行的SQL与所涉及的表最简单的方式就是从v$session中查询会话的sql_address或sql_hash_values。对于TM队列,表可以通过p2列(ID 1列)来识别。它包含object_id,可以使用它来从dba_objects中进行查询。这使得确定争用对象非常简单。
SQL> col sid format 9999 heading "Sid" SQL> col enq format a4 heading "Enq." SQL> col edes format a30 heading "Enqueue Name" SQL> col md format a10 heading "Lock Mode" trunc SQL> col p2 format 9999999 heading "ID 1" SQL> col p3 format 9999999 heading "ID 2" SQL> select sid, 2 chr(bitand(p1, -16777216) / 16777215) || 3 chr(bitand(p1, 16711680) / 65535) enq, 4 decode(chr(bitand(p1, -16777216) / 16777215) || 5 chr(bitand(p1, 16711680) / 65535), 6 'TX', 7 'Row related lock (row lock or ITL)', 8 'TM', 9 'Table related lock', 10 'TS', 11 'Tablespace and Temp Seg related lock', 12 'TT', 13 'Temporary Table', 14 'ST', 15 'Space Mgt (e.g., uet$, fet$)', 16 'UL', 17 'User Defined', 18 chr(bitand(p1, -16777216) / 16777215) || 19 chr(bitand(p1, 16711680) / 65535)) edes, 20 decode(bitand(p1, 65535), 21 1, 22 'Null', 23 2, 24 'Sub-Share', 25 3, 26 'Sub-Exlusive', 27 4, 28 'Share', 29 5, 30 'Share/Sub-Exclusive', 31 6, 32 'Exclusive', 33 'Other') md, 34 p2, 35 p3 36 from v$session_wait 37 where event = 'enqueue' 38 and state = 'WAITING' 39 / SQL> Sid Enq. Enqueue Name Lock Mode ID 1 ID 2 ----- ---- ------------------------------ ---------- -------- -------- 4387 TM Table related lock Exclusive 49911 0 4393 TM Table related lock Sub-Exlusi 49911 0 SQL> @swswp enq% Database: prod16 31-MAR-10 04:32pm Report: swswp.sql OSM by OraPub, Inc. Page 1 Session Wait Real Time w/Parameters Sess ID Wait Event P1 P2 P3 ----- ---------------------------- ------------ --------- ----- 4383 enq: TM – contention 1414332422 49911 0 4388 enq: TM – contention 1414332422 49911 0 2 rows selected. SQL> l 1 select sid, event, 2 p1, p2, p3 3 from v$session_wait 4 where event like '&input%' 5 and state = 'WAITING' 6* order by event,sid,p1,p2
与latch等待事件一样,从Oracle 10g开始,每一种队列都有它自己的等待事件。这节省了诊断步骤,因为我们可以通过一个简单的查询确定所涉及的会话和队列类型。会话4393已经持有表锁并且没有等待所以没有显示,会话4383和4388正等待锁表因此post一个TM队列等待。通过使用P2列(49911)来与dba_objects视图的object_id关联进行查询来获得被调用的表。
TX Enqueue等待
TX队列等待是最常见的队列等待事件。这也是最迷人的。想深入研究这个等待事件,因为它将使您更深入地了解Oracle如何管理事务并发性,这与块克隆、undo、读取一致性和相关事务列表有关。
TX队列也叫作行级锁队列,实际上出现TX队列有三个原因,并且只有一个实际上是行级锁。每一个Oracle数据块可以被抽象为三个区域:
.行数据包含真实的Oracle行记录并且是每个数据块最重要的一个部分。
.可变数据包含事务元数据
.可用空间数量可以通过行数据增长与可变数据增长而减小
相关事务列表(ITLs)
内置在每个Oracle数据块的可以数据区域的结构叫作相关事务列表(ITLs)。这些结构最主要是用来负责Oracle的行级锁与读一致性。从高度抽象的角度来看,可以认为ITLs就像检查框,每个检查框与一个特定的事务相关。如果想要更新行记录,但被锁定的行已经与其它事务的ITL关联,你将会收到一个TX队列等待,这确实是行级锁。
每个Oracle数据块都创建了特定数量的ITLs。ITLs的初始值是由表的initrans空间参数所控制的并且可以通过dba_tables视图的ini_trans列来查看。从Oracle 9i开始,缺省的ini_trans值为1,然而通过简单的块dump可以清楚的看到创建了两个ITL。使用两个ITLs,单个数据块可以同时并发地执行两个事务。
假设第三个事务想要修改块中没有被锁定的行而只在两个ITL存在时,第三个事务的服务器进程将尝试动态创建一个额外的ITL。然而服务器进程必须首先确保ITL的最大数(max_trans)不会被超过并且在数据块中要有可用空间。如果服务器进程不能创建额外的ITL,它将发出一个TX队列等待事件,并且这个进程将耐心等待。为了减小这种情况的出现,单个块的ITLs的缺省值与最大值都可以设置为255。当不超过这个值时可以执行alter table命令来修改。
一旦在数据块中创建了一个ITL后,唯一能获得空间的方式是重新创建整个表。修改空间参数将不会影响已经创建的ITL。这就是为什么缺省的ITLs为1(实际上创建了两个ITL)并且最大值设置为255的原因。如果数据块的并发请求更多的ITLs,Oracle宁愿消耗空间也不愿意发出TX队列等待事件而让事务等待。
初看,ITL的最大数是255可能看上去非常有限,但请考虑这种情况:想想在最高并发应用程序中,在最高并发的数据库中的最高并发表。也许有一个表可能有250个并发进程正在更新,删除与插入记录。现在真正有多少进程将会并发更新,删除或插入记录到一个数据块中,而不是整个表或区,是单个块。即使使用最高并发性的应用程序,在一个块中激活超过255个并发事务也是极不可能的。所以ITL的最大数255并没有太大的限制。然而如果确实出现了问题,可以通过增加表的pct_free参数来减小数据块的并发性或者为了减少存储在块中的行记录可以增加固定长度的列。
Unod段的事务表
每个undo段在它的头块中包含一个结构叫事务表。Oracle开发人员将事务表中的行称作slots(插槽)。每一个已经占用的slot都与正在或已经在undo段中存储undo信息的事务相关。如果一个事务已经提交或者回滚,它确实是一个非活动事务,否则它就是一个活动事务。除了包含slot号与事务状态,每个slot也包含一个序列号。为了区分不同的事务使用相同的slot并能让slot重用,序列号可以增长。UBA是undo块地址,提供到事务的undo的直接链接。SCN是当相关事务开始时事务的系统改变号。
事务表与性能分析人员相关因为它们提供了事务号。每个事务有一个相关的事务号,并且事务号是基于事务的事务表条目生成的。事务号由三组数字组成。第一部分是事务表号,第二部分是slot号,最后是相关序列号。例如,一个事务号为00100.000.00007。ITLs与事务表之间的联系是每个ITL条目关联到一个特定的事务并且在ITL条目中包含事务号,比如00100.000.00007。
深入了解相关事务列表(ITL)
已经了解了ITL与undo段事务表,现在是将它们作为单个工作单元组合在一起的时候了,并展示在事务活动期间ITLs是如何变化的。深入了解相关事务列表可以让你深入理解Oracle如何管理事务并发性,如何创建读一致性块以及为什么要小心“snapshot too old”错误。
下面通过执行命令alter system dump datafile 1 block 75847来dump数据块。在执行块dump时,这个块(1,75847)包含了许多行记录并且有三个活动事务更新四行不同的记录。第一个与第三个事务显示正在更新一行记录,第二个事务正在更新二行记录。
$ cat prod5_ora_21741.trc ... Block header dump: 0x00412847 Object id on Block? Y seg/obj: 0xff6b csc: 0x00.50fcb6 itc: 3 flg: O typ: 1 - DATA fsl: 0 fnx: 0x412848 ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0003.00d.00000318 0x00c3e3d0.0593.0c ---- 1 fsc 0x0000.00000000 0x02 0x0008.01b.00000340 0x00c41bce.0481.24 ---- 2 fsc 0x0000.00000000 0x03 0x0001.000.00000320 0x00c45fa0.0599.0b ---- 1 fsc 0x0000.00000000 ...
ITL条目包含以下内容:
itl:这是事务的ITL号
xid:这是事务ID,它由事务表ID(0003),事务表slot号(00d)与序列号(00000318)组成。事务ID是很重要的,因为它用于确保看起来相关的undo信息是真正相关的。
uba:这是undo块地址。这直接指向事务的最新更改undo,对于回滚事务和读取一致性(克隆缓冲区构造)都是必要的。
flag:事务的状态它可以有许多值,以下是常见值
----- 意味着事务是活动的,DML在执行事务没有提交或回滚
--U-- 意味着事务已经提交,因此任何行数据都可以引用在活动事务中没有被使用的ITL并且它们没有被锁定。事务的行数据可能没有被合并。例如,如果一个列被更新,在更改之前与之前的值可能保留在行数据中。
--C-- 意味着事务已经提交,行数据已经合并,并且行数据中的ITL条目已经被删除。任何块touch可能触发对这个flag的改变,包括select语句。我知道这很难相信。这种看似延迟的更改通常称为延迟块清除,或者简单地称为块清除。
Lck:这是事务在某个时刻锁定在这个块中的行数。大于0的值不能够说时行被锁定。如果这个值为2,就像第二个事务一样,这个事务关联两行记录。锁会保持到flag改变为C----为止。这意味着在一个事务提交后且不再被认为是活动(--U--)状态时,Lck值可能大于0
Scn/Fsc:SCN是系统改变号并用来判断事务是何时结束的(提交或回滚)。上面的例子中SCN没有被指泒,但在事务提交后,SCN被设置了如下所示。当创建一个buffer的读一致性版本判断是否需要检索undo时SCN是很重要的。FSC引用可用空间信用。它用于未提交的事务当一个更新或删除操作造成行记录长度收缩使用。Oracle将保护这个空闲空间,以防事务回滚和需要重新填充空间。如果空闲空间用于其他用途,然后事务回滚,则可能需要迁移行!。在下面的dump结果中,前两个事务(ITLs x01与x02)已经提交标记它们的事务为非活动状态。第三个事务,ITL x03,还没有提交。在前两个事务提交后,相同的块dump命令,alter system dump datafile 1 block 75847。注意flag已经改变了,一个SCN已经指泒给事务了。
$ cat prod5_ora_21741.trc ... Block header dump: 0x00412847 Object id on Block? Y seg/obj: 0xff6b csc: 0x00.50fcb6 itc: 3 flg: O typ: 1 - DATA fsl: 0 fnx: 0x412848 ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0003.00d.00000318 0x00c3e3d0.0593.0c --U- 1 fsc 0x0000.0050fd6f 0x02 0x0008.01b.00000340 0x00c41bce.0481.24 --U- 2 fsc 0x0000.0050fd6b 0x03 0x0001.000.00000320 0x00c45fa0.0599.0b ---- 1 fsc 0x0000.00000000
两个flags ----与--U-是必需的,因为活动事或者过去的活动事务中涉及的行可以在其行数据中具有有效的ITL条目。因为简单引用行数据与查看ITL条目不能说明行当前被活动事务调用与锁定。为了检查行是否被锁定,一个服务器进程必须从行数据中得到ITL引用然后检查数据块的可变ITL区域中的flag。如果flag为 ----,那么服务器进程知道行确实被一个活动事务所调用且被锁定。然而如果falg为--U,服务器进程知道行没有被锁定。
块清除进程的部分工作将删除非活动事务行数据ITL条目,将它们各自的ITL条目在数据块的可变部分的flag的状态修改为C---,并合并行数据。
这是一种聪明的策略,因为Oracle可以快速使用最小的改变来记录数据块中的改变,但仍然在行级别维护并发控制。最终需要对块进行最后的更改,但这可能发生在工作负载较低的时期,比如基准测试完成之后。
执行查询语句来touch块1,75847后,再执行dump命令的结果如下,数据块(1,75847)在执行查询语句touch数据块后事务flags从--U-变为了C---,指示块清除已经发生了。
$ cat prod5_ora_21741.trc ... Block header dump: 0x00412847 Object id on Block? Y seg/obj: 0xff6b csc: 0x00.510047 itc: 3 flg: O typ: 1 - DATA fsl: 0 fnx: 0x412848 ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0003.00d.00000318 0x00c3e3d0.0593.0c C--- 0 scn 0x0000.0050fd6f 0x02 0x0008.01b.00000340 0x00c41bce.0481.24 C--- 0 scn 0x0000.0050fd6b 0x03 0x0001.000.00000320 0x00c45fa0.0599.0b ---- 1 fsc 0x0000.00000000 ...
现在我意识到这很有趣,但是我也理解一些读者可能认为这个block dump和ITL的东西并没有那么强的关联。但我不敢苟同。您不仅对TX排队有了更全面的了解,而且还清楚地了解了如何排队Oracle实现了它的专利行级锁方案。
深入了解Buffer克隆
介绍块克隆是因为它与CBC latch竞争。现在将深入学习Oracle如何使用ITLs,undo块,SCNs与其它有趣的Oracle技术。当一个服务器进程要定位一个请求的buffer并且发现请求的行在查询开始后发生改变了,它必须为buffer创建一个时光倒流的镜像。这就叫作当前(CU)buffer的一致性读(CR)buffer。一旦buffer被拷贝,合适的undo被应用后,使被拷贝的buffer回退直到CR buffer被成功克隆好为止。
假设我们的查询执行时间是SCN 12330,查询最终得到要访问的buffer 7,678。然而,我们注意到存在一些ITL活动事务 7.3.8当前是活动状态并且buffer可能在我们查询开始后发生了改变。事务5.2.6是非活动状态(flag为C,并指派了SCN,并且Lck为0),但是改变的提交时间在我们查询开始之后并且影响这个当前(CU)buffer。这些块改变意味着在CU buffer在我们查询在SCN 12330时间点开始后已经发生改变了并且不能用于我们的查询。我们需要一个一致性读副本,它可以时光倒流回到SCN 12330时间点。因此CU buffer 7,678必须被克隆并应用undo,来创建一个SCN12330时间点的CR buffer。
在执行buffer克隆之前,必须找到一个不被频繁访问的free buffer然后使用7,678的CU buffer来替换它。服务器进程将获得LRU chain latch与相关的LRU chain,然后从LRU chain的LRU端开始扫描,查找不被频繁访问的free buffer。最终将找到一个不被频繁访问的free buffer并使用CU buffer 7,678的副本来替换它。当然CBC结构也将被更新来映射克隆buffer在buffer cache中的位置。
从与第一个ITL相关的活动事务7.3.8开始。服务器进程需要检索在我们查询开始时间scn 12330之后所有生成的undo记录。事务7.3.8的最近生成的undo可以通过它的ITL的undo块地址(UBA)所链接到的undo块2,45中找到。服务器进程然后必须访问undo块2,45。这需要请求CBC活动并且也可能请求LRU活动来执行IO调用。一旦访问到undo buffer 2,45,将会通过比较事务号来检查确保我们使用正确的事务在工作。数据块与undo块事务号需要匹配(7.3.8),因为事务是活动的,所以undo信息应该没有铺覆盖。
undo块2,45的SCN是12348,这意味着undo块代表的块改变出现在我们查询开始时间scn 12330之后,因此,我们需要对克隆的CR buffer应用undo数据,让它回退到过去一点点。
undo块2,45也链接到了另一个undo块2,90。这是一种undo链并且可能持续一段时间,消耗大量的计算资源。服务器进程现在必须访问undo块2,90(请求CBC活动并且也可能请求LRU活动来执行IO调用)并且再次比较事务号来确保它们是否匹配。它们匹配,现在检查SCN。undo块2,90的SCN是12320,它在我们的查询开始时间SCN 12330之前,因此我们不需要应用undo。如果不应用undo,我们的CR buffer将代表的是块7,678在SCN 12320时间点的版本,这比我们要查询的时间SCN 12330早了。
现在查看第二个ITL,它与事务5,2.6关联。这个事务在SCN 12350时间点已经提交了,在我们的查询开始时间之后,因此我们需要应用它的undo。从ITL条目来看,我们将得到ndo块地址2,70并且访问这个undo块。现在比较事务号,因为事务已经提交,undo信息将不再受保护。增加undo保留期可以让udno信息保留更长的境,但也不受保护。
假设另一个服务器进程覆盖了undo块2,70中的相关事务undo信息。如果出现这种情况,服务器进程的事务号将被记录并且这里将记录为5.2.6。通过事务号比较,我们注意到差异并且立即知道undo块2,70中的undo不能应用于我们的CR buffer。在这时,服务器进程将会发出快照太旧的错误信息并停止我们的查询。很明显,undo块快照太旧因为被其它进程覆盖了。
幸运地是,事务号是匹配的。undo块2,70中的undo是在SCN 12340时间点发生的改变,它在我们的查询开始之后,因此我们应用这个undo到我们的CR buffer。下一个undo链接是空的,因此没有其它undo需要应用了。
现在返回到ITL条目,这里没有更多的ITL需要考虑,因此我们完成的数据块的克隆。任何一个服务器进程现在都可以访问CR buffer 7,678它包含了SCN 12330时间所代表的内容。
现在应该很清楚为什么ITLs如此重要了,而且Oracle的读取一致性模型虽然非常强大、必要且高效,但仍然相对昂贵,因为它可能会消耗大量CPU和IO,从而减慢应用程序的响应时间。Oracle非常清楚这一点,并且从Oracle 10gr2开始使用内存优化结构来临时存储undo信息。这些对象不是段类型并且不受与段相关的CBC和LRU chain活动的影响。在内存中,undo被存储在shared pool中。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。