oracle学习undo之事务槽和oracle的多种提交方式


1、事务槽数量参数

每一个oracle数据块里面在数据块头部都有事务槽,事务槽的数量可以去查一下,如果数据库中的表T2:

select INI_TRANS,MAX_TRANS from dba_tables where table_name='T2'; INI_TRANS MAX_TRANS 1 255 1 255 这个是事务槽的数量,默认INI_TRANS的值是1 ,表刚建立的时候默认值是1 ,最大是MAX_TRANS255,这两个参数属于单个表,在系统参数和隐含参数都没有这两个参数,oracle从10G开始max的值不能修改,默认的ini_trans可以修改,alter table t2 initrans 4;对于事务槽的数量,一般我们不去改,取默认值就行。   2、事务槽的争用 一个事务A可以在数据块中修改多行,但是只需要一个事务槽就可以,这时候有可能另外一个事务B也要对这个数据块进行修改,这时候它需要第二个事务槽,事务槽以是事务为单位的,A事务在数据块获得事务槽后修改了几行,一直没有提交,这个事务槽就不能被覆盖,只有这个事务被提交后,这个事务槽才能被覆盖,这个点很关键。B事务上来后也要分一个事务槽,底下是数据行,中间保留10%空间,留10%是为了将来:a、更新底下数据行可能往上占用空间 b、当多个事务同时修改数据行时,需要增加事务槽的数量,块头的数据要往下压。 有可能出现这个情况,多个事务同时修改这个数据块,产生了非常多的事务槽,这个时候我们发现这里面只能放四个事务槽,再往下就没空间了,第5个事务再要修改数据块的时候,它就会要等待前面四个事务槽去提交,这叫事务槽的争用,事务槽英文叫ITL(Interested Transaction List),ITL是oracle数据块内部的一个组成部分,位于数据块头部。 如果事务槽的争用这种情况发生,可以把pct_free增加一些。再比如某个表一个批量的插入操作,表分配一个区,这个区有8个块,有三个事务同时往一个块里面插入的话,这时oracle需要额外的增加两个事务槽,在原有有一个事务槽的基础上,在一个数据块上增加两个事务槽,就会额外的占用空间,针对这种情况,往一个表中多个事务并行插入数据的时候,第一个事务使用一个块,第二个事务使用第二个块,第三个事务使用第三个块。oracle为了避免事务槽的争用,尽量对insert操作分布到多个块,减少事务槽的争用。但是对于delete update就往往无能为力了,因为一个事务更新在这个块里,另外一个事务更新也在这个块里,所以发生事务槽争用主要集中在update和delete操作上。   3、dump事务槽 新建表并插入数据:
create table t10(id number(5),name char(2000));
insert into t10 values(1,'aa');
insert into t10 values(2,'bb');
insert into t10 values(3,'bb');
insert into t10 values(4,'cc');
insert into t10 values(5,'dd');
commit;
select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid),id from t10;
4    534    1
4    534    2
4    534    3
4    535    4
4    535    5
dbms_rowid.rowid_relative_fno(rowid)是所在文件号
dbms_rowid.rowid_block_number(rowid)是所在块号
ID对应元彪的ID列,ID为1是第一行,第一行所在的块就是4号文件的第534号块。
然后更新下表:
update t10 set name='abcd' where id=1;
--查询下事务信息如下
select ubafil,ubablk,xidusn,xidslot,xidsqn,start_scnb from v$transaction;
    UBAFIL     UBABLK     XIDUSN    XIDSLOT     XIDSQN START_SCNB
---------- ---------- ---------- ---------- ---------- ----------
         3        444          5         27        945    1358180

查询下这个事务,更新一行就更新一个数据块,UBAFIL和UBABLK代表这回滚块信息,3号文件444号块,XIDUSN段号(select * from v$rollname查找段号),XIDSLOT事务槽号,XIDSQN945次被覆盖。这些信息都可以在事务表可以找到,分别是xid跟UBA地址。

3.1、dump回滚段头块

根据如上的5号段的段头块,根据上节内容可以导出段头块的trace文件查看。

3.2、dump回滚块

上述语句的3号文件的444号块的回滚块,alter system dump datafile 3 block 444;

3.3、dump导出数据块

上面有语句查找t2表的存储情况:
select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid),id from t10;

alter system dump datafile 4 block 534;
Itl           Xid                               Uba                          Flag   Lck   Scn/Fsc 0x01   0x0001.015.000002f6  0x00c02e95.00a4.3a  C---    0  scn 0x0000.0014b964 0x02   0x0005.01b.000003b1  0x00c001bc.00b0.0a  ----    1  fsc 0x0000.00000000 上面内容主要是XID、uba地址,flag是是否提交标志,lck为锁标记。   4、详解ITL事务槽 Itl           Xid                               Uba                          Flag   Lck   Scn/Fsc 0x01   0x0001.015.000002f6  0x00c02e95.00a4.3a  C---    0  scn 0x0000.0014b964 0x02   0x0005.01b.000003b1  0x00c001bc.00b0.0a  ----    1  fsc 0x0000.00000000 ITL是事务槽ITL编号,xid是事务ID,并指向回滚段段头块里面的事务表信息里的记录,格式是: xidusn.xidslot.xidsqn。分为三部分:该事务回滚段号,在回滚段头事务表里的记录位置,这个事务被覆盖的次数。 uba指向具体的回滚段回滚块的记录,格式是:undo block address、ubasqn、ubarec。对应的三段内容是:(1)回滚数据块的地址,就是回滚段内的块号;(2)undo块被重用的次数;(3)在undo块的第几条记录。 flag为事务标志位,记录了这个事务的状态,是否已提交,C---是已提交,----为未提交。 lck为这个事务拥有的锁的数量,可理解为影响的记录数。 Scn/Fsc提交方式为普通提交标记为scn,快速提交标记为fsc,然后这两个后面跟着一个提交时间点。 undo段头 事务表里面也类似有这些信息,一个事务即在事务表里有信息,在事务槽里也有信息。   5、数据块中存放事务信息的意义 一个事务开始以后,首先在回滚段段头的事务表里找到一个槽位,同时分配一个回滚块,事务表指向回滚块,事务表中这条信息有XID,是否已提交标志。然后修改一个数据块,数据块分配一个事务槽,事务槽指向undo段段头事务表中的一条记录,数据块事务槽的uba指向对应的undo块,数据块的事务槽还有是否已提交标志,然后在数据块中修改数据行,数据行的头部指向数据块的事务槽,修改了三个数据行,这三个数据行的头部都指向数据块头部的这个事务对应的事务槽,我要修改一个数据块首先在事务槽分配事务,如这个事务的事务槽是1号事务槽,这个事务修改了三行,在这个事务每个被修改的行的头部都写上1,就是都指向1号事务槽,一个事务正在修改这个数据块,事务的信息在事务槽里面写了,这个事务修改了3行,这三行都指向事务槽,槽里面有事务提交标志。 另外一个事务再来修改这个块的时候,它有获取另外一个事务槽,如获取了2号事务槽,2号事务槽也可能修改一号事务修改过的一行数据, 当它修改数据行的时候,发现这个数据行的头部写的1 ,它就知道目前有一个事务,1号事务槽的事务正在修改这个行,对这个行,1号事务有两种情况,已提交和未提交。如果以提交的话,对应事务的事务槽会写了已提交标志,它就可以修改这个行了。如果发现已提交标志没写的话,它就认为对应的事务没有提交,两个事务不能同时修改一个行。 在一个数据块里面,有事务信息及是否已提交标志是非常有意义的,假设这个数据块里面没有这个信息的话,也就是说对oracle数据库来将,关于某个事务只有回滚段段头块里有,这时我要读这个数据块的时候,要修改这个数据块的时候,这个行到底有没有被事务修改,只能查undo段头块里的事务表,查它才能知道这个行被谁修改了、事务是否提交,所有的修改都会查回滚段段头,这样的话会出现段头的争用,不现实。 这就是为什么要把事务信息,即写在回滚段段头里面,也要写在每个数据块的事务槽里,就是因为将来访问数据块某个行的时候,看这个行有没有被事务修改、被哪个事务修改、修改它的事务是否提交,这个信息在数据块中就有,直接查就行,不需要再去查回滚段段头块了。事务信息在两个地方都是有意义的。   6、快速提交 6.1、产生的原因 这个地方有个问题涉及到oracle的一个提交方式,假设一个事务修改了1000个块,修改了一个块以后提交,提交这个事务必须把事务的提交标志置成已提交,一个事务修改了1000个块,意味着关于这个事务信息在1001个地方有,因为每个数据块里面有事务信息,还有undo段头有事务信息,在1001个地方有这个事务信息,提交后,在1001个地方把事务提交状态都给写上,会出现COMMIT提交的速度会很慢,因为要访问1001个块,况且还有一种情况,修改了1001个块,这个事务的时间很长,在这个过程,这1001个块里面可能有很多已经写到磁盘上了,因为dbwr写的时候就什么也没管,它不管你有没有提交,写到磁盘,内存中原有的块就是干净块了,就可能被覆盖,也就是说提交的时候,发现修改了1000块,只有200或者300个块在内存里,还有另外800个块在磁盘上,为了提交这个事务,还要把那800个块读到内存,如果这样的话,提交速度会非常非常慢的。所以我们把事务信息写到数据块和undo段头里面,写两份是有好处的,将来oracle更新数据行的时候,找这个数据行对应的事务信息的时候就很方便,但是涉及到另外一个问题,oracle提交的时候要在很多地方修改事务信息,这时候带来提交慢,所以为了解决这个问题,oracle推出一个技术叫快速提交。   6.2、快速提交原理 oracle提交的时候,如果发现这个事务这次所修改的数据块过多,这时只会更新undo段段头块里的事务表里的事务信息,数据块里的那些事务槽,不更新或者更新少量,这样提交速度就很快,也就是说回滚段段头里的事务一定最能准确的代表这这个事务是否已提交,但是普通数据块的事务槽信息,那里面的是否已提交信息不怎么准确。    6.3、举例说明 回滚段里有事务信息,数据块里也有事务信息,数据块里有三个行被修改了,这时另外一个事务要修改这个数据块,给它分配了另外一个事务槽,这个事务也要修改这个数据块三行中的一行,但是被修改的数据行指向第一个事务槽,第二个事务读了下第一个事务槽,检查第一个事务是否已提交,如果已提交的话,第二个事务就可以直接修改这个行了,因为已提交的肯定是准确的;如果发现这个事务未提交,它就可能怀疑了,怀疑不一定未提交,根据第一条事务的xid找到第一个事务在回滚段段头块里的事务表里的事务信息,一查发现第一个事务已经提交了,第二个事务相信undo段头的事务表而不相信数据块中的事务槽,这时它不但要更新要修改的行,这行更新后,这个数据行指向第二个事务槽,而且,它一块把第一个事务槽的事务状态改成已提交。----第二个事务还帮第一个事务进行了扫尾工作。   7、事务信息的指向总结 undo段头事务表数据块事务槽和回滚块的关系,上节已经讲过,再次总结提醒自己 (1)undo段头事务表指向这个事务最新的undo块,并且对应的所有undo块是由新指向旧的串起来。 (2)数据块头部事务槽直接指向这个事务对数据块修改对应的最新的回滚块 (3)数据块的事务槽也指向undo段头块事务表,和提交方式有关   8、行级锁和事务信息的清理关系 最彻底的提交:undo段头块事务表已提交标志,数据中对应事务槽已提交标志,被修改数据行指向事务槽的标记被清楚。如果数据行这个标志是空的,这个行就是没有被修改中或者占用,如果这个行被修改了,它就写一个事务槽标记指向数据块的事务槽,相当于在这个数据行加了一个锁。一个事务修改这个数据块,获取一个事务槽1号事务槽,修改这个数据行,就在这个数据行的前部加一个数据1指向1号事务槽,相当于在这个数据行加了一个锁。 第二个事务要修改这个数据行的时候,一看上面有一个锁,它就根据锁找到数据块中锁对应的事务槽,看它是否提交,如果事务没有提交,,它不能修改这个行,所以,在这个数据行的前面加了一个事务槽的编号就相当于加了个锁,它就是oracle里面非常有名的行级锁。 在行的级别上加了一行锁,这个开销不打,直接加个数字就行。oracle和sql server不一样,sql server是修改数据块的时候在块上加一个锁加块级锁,不能两个事务同时修改一个块,但是oracle可以,因为oracle是行级锁,两个事务可以同时修改一个数据块,只要行不冲突就行,所以oracle并发性好些。行级锁并发行好些,而且没有多大的开销很灵巧。 所以对oracle来说,最彻底的提交方式是:undo段头事务表、数据块中的事务槽还有数据行的锁标记,如果这三个都清理,那就是最彻底的提交方式。但是oracle如果修改的数据块过多,它会值清undo段头和少量的数据块,一般情况它第一次提交的时候只会清undo段头事务表和数据块的事务槽,数据行的锁标记经常不清,访问的时候再清一次,这就看这次事务修改多少块了,这就是oracle的一个机制。   9、行锁、事务槽、事务表中事务的提交状态关联性 9.1、行锁未清,事务槽标记已提交 oracle的select查询有时候也会产生redo,很奇怪。我们之前的认知里只有增删改产生redo,我们来看查询产生redo的原理。当oracle的select要访问一个数据块,访问这个数据块的一行,发现这个行前面有锁标记,说明有可能某个事务正在修改这个块,这个select不能直接查询,要根据要根据数据行这个锁定标记找到对应的事务槽,发现事务槽里有信息,事务槽里事务是否已提交的信息,有可能这个事务已提交但是没有清理数据行上的锁,这时这个select会把这个锁标记给清理了,就是把原来的那个数据行指向事务槽的链打断,select做了一件事,查这个行的同时把这个行的锁也清理了,因为它发现这个行对应的事务已提交了,知道锁没有清,select读取数据,但是结棍它对数据行进行了修改,所以有时select也会产生redo日志。   9.2、锁未清、事务槽标记未提交、事务表标记已提交  select还有一种情况,读行时有行级锁,它就找指向的事务槽,发现数据块上的事务槽也没有提交,再去undo段头块上的事务表查询,发现事务表已提交,这时select的进程就会把这个数据块上事务槽修改成已提交,并清理数据行的锁,这时select也产生redo了。   9.3、都未提交、构造CR块 select读取数据行时,发现有行锁,检查数据块的事务槽信息未提交,查看该事务槽指向的undo段头事务表信息,发现也没有提交,说明此时真是该事务对这个数据块的这一行进行修改操作。select不能查没有提交的数据。从锁找到事务槽找到对应的undo块,把这个undo块中的修改前的镜像和另外没有修改的行构造成一个新的块,拼成一个CR块,然后直接读这个CR块,就是一致性读。这个读没有出现脏读,不能读一个未提交的数据。   9.4、事务表中都没有这个事务信息了 另外还有一个情况以后也会遇到,我们指定一个数据块在磁盘里面很对没有读出来,这个数据块的一个行有锁标记,通过锁标记找到事务槽对应的事务,再找undo段头块的事务表,来确认是否已提交。假设这个块昨天被修改了提交了,结果块被写到磁盘了,之后再有没有访问过,然后今天select查询这个数据行,根据事务槽的XID去找事务表,XID有三块部分组成:回滚段的段号、段头事务表的槽位号、被覆盖的次数,假设是8号段,第15号槽位,被第13次覆盖,结果找到这个槽位时,发现已经是第30次被覆盖了,我要找的第13次覆盖所对应的事务是否已提交,找不出来了。 因为这个块已经很长时候没有被读出来了,段头块事务表这行槽位已经被多次覆盖了,这个时候oracle不会报错,果断的任务13次覆盖的事务已经提交了。因为没有提交的话,就不会出现第14次覆盖了,所有认定为已提交。这时查询将会修改数据块事务表的信息,清理数据行上的锁标记。  

相关