“Undo前镜像”是Oracle早期推出的特性之一,也是在诸多数据库产品中异军突起的致胜法宝。当我们在一个会话中启动事务,对数据进行增加、修改和删除操作的时候,只要没有提交事务,其他会话只能看到数据的旧版本,也就是事务会话修改之前的版本。所以,在Oracle中,select操作不会阻塞任何操作,也不会被任何操作所阻塞。
1、Undo与前镜像
这样的特性就是依赖Oracle推出的Undo前镜像机制。当我们开启事务,修改一个数据块的时候,Oracle首先会修改数据块块头的ITL(事务槽)信息,将当前事务信息(xid事务标识)写入到ITL中的一行。之后标记下这个事务对应的Undo空间地址。之后,才能进行数据块的修改。
在修改数据块的过程中,Oracle的Server Process会将数据块的原有内容(对Update和Delete操作而言),保存到Undo表空间上Undo段的位置上。
Undo段内容有很多的用途。当另外的会话需要访问数据块时,首先会去检查数据块的ITL事务槽信息,查看要访问的数据块是不是正在被修改。如果正在被修改,就根据ITL上面留下的事务槽信息访问Undo段。同时,如果只有一部分数据被修改,Oracle Server Process还要结合数据块中未被修改的内容进行结果集合拼装。
只有在事务正式完成,commit或者rollback之后,Undo段中的extent状态才不再是Active。非Active状态的Undo也有其价值,Oracle的“多版本一致读”、“Flashback”等特性,都是基于对非Active状态Undo的数据利用。
本篇,我们打算使用10046等待事件,监控一下Oracle是如何进行前镜像读取动作的。
2、实验环境准备
我们选择11gR2版本进行试验。
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for 32-bit Windows: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 – Production
创建实验数据表T。
SQL> create table t as select * from dba_objects;
Table created
SQL> select object_id from dba_objects where wner='SYS' and object_name='T';
OBJECT_ID
----------
188217
SQL> select HEADER_FILE, HEADER_BLOCK, BYTES, BLOCKS, EXTENTS from dba_segments where segment_name='T' and wner='SYS';
HEADER_FILE HEADER_BLOCK BYTES BLOCKS EXTENTS
----------- ------------ ---------- ---------- ----------
1 89344 10485760 1280 25
对应分区信息。
SQL> select extent_id, file_id, block_id, blocks from dba_extents where segment_name='T' and wner='SYS';
EXTENT_ID FILE_ID BLOCK_ID BLOCKS
---------- ---------- ---------- ----------
0 1 89344 8
1 1 89352 8
2 1 89384 8
(篇幅原因,省略部分内容……)
22 1 94592 128
23 1 94720 128
24 1 95616 128
25 rows selected
系统当前采用自动化Undo管理。
SQL> show parameter undo;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 900
undo_tablespace string UNDOTBS1
3、Trace文件获取
我们现在一个会话中,开启事务删除所有的数据。
SQL> select sid from v$mystat where rownum<2;
SID
----------
145
SQL> delete t;
84331 rows deleted
在另一个会话中,我们启动实验。注意,首选需要排除缓存脏块的影响,将脏块写入到数据文件。
--另一个会话
SQL> select sid from v$mystat where rownum<2;
SID
----------
21
SQL> alter system checkpoint;
System altered
SQL> alter system flush shared_pool;
System altered
SQL> alter system flush buffer_cache;
System altered
SQL> select value from v$diag_info where name='Default Trace File';
VALUE
------------------------------------------------------------------------------
d:\app\bspdev\diag\rdbms\ora11gw\ora11gw\trace\ora11gw_ora_2208.trc
开启跟踪操作过程。
SQL> alter session set events '10046 trace name context forever, level 12';
会话已更改。
SQL> select count(*) from t;
COUNT(*)
----------
84331
SQL> alter session set events '10046 trace name context off';
会话已更改。
我们可以在指定目录上找到对一个trace文件ora11gw_ora_2208.trc。
4、结果分析
我们从trace原始文件中,发现了比普通FTS(Full Table Scan)操作的不同。更多的操作细节和资源消耗。从直观上感觉,进行select操作消耗更多的时间。
Trace文件细节信息。
=====================
PARSING IN CURSOR #3 len=22 dep=0 uid=0 ct=3 lid=0 tim=156068168118 hv=2763161912 ad='30303208' sqlid='cyzznbykb509s'
select count(*) from t
END OF STMT
PARSE #3:c=234375,e=1924863,p=525,cr=4754,cu=0,mis=1,r=0,dep=0,og=1,plh=2966233522,tim=156068168115
EXEC #3:c=0,e=43,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=156068168297
WAIT #3: nam='SQL*Net message to client' ela= 3 driver id=1413697536 #bytes=1 p3=0 obj#=188217 tim=156068168350
WAIT #3: nam='db file scattered read' ela= 14472 file#=1 block#=89345 blocks=3 obj#=188217 tim=156068182967
WAIT #3: nam='db file sequential read' ela= 14293 file#=3 block#=7522 blocks=1 obj#=0 tim=156068197389
WAIT #3: nam='db file sequential read' ela= 2580 file#=3 block#=7521 blocks=1 obj#=0 tim=156068200612
WAIT #3: nam='db file sequential read' ela= 2353 file#=3 block#=7520 blocks=1 obj#=0 tim=156068204231
WAIT #3: nam='db file sequential read' ela= 2355 file#=3 block#=7524 blocks=1 obj#=0 tim=156068207835
WAIT #3: nam='db file sequential read' ela= 2353 file#=3 block#=7523 blocks=1 obj#=0 tim=156068211050
WAIT #3: nam='db file sequential read' ela= 2352 file#=3 block#=7526 blocks=1 obj#=0 tim=156068215311
(省略部分内容……)
FETCH #3:c=2265625,e=11162122,p=3131,cr=87951,cu=0,mis=0,r=1,dep=0,og=1,plh=2966233522,tim=156079330523
STAT #3 id=1 cnt=1 pid=0 pos=1 bj=0 p='SORT AGGREGATE (cr=87951 pr=3131 pw=0 time=0 us)'
STAT #3 id=2 cnt=84331 pid=1 pos=1 bj=188217 p='TABLE ACCESS FULL T (cr=87951 pr=3131 pw=0 time=21345344 us cost=329 size=0 card=86741)'
WAIT #3: nam='SQL*Net message from client' ela= 785 driver id=1413697536 #bytes=1 p3=0 obj#=0 tim=156079331506
FETCH #3:c=0,e=3,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,plh=2966233522,tim=156079331569
WAIT #3: nam='SQL*Net message to client' ela= 3 driver id=1413697536 #bytes=1 p3=0 obj#=0 tim=156079331615
*** 2013-02-18 08:20:42.187
WAIT #3: nam='SQL*Net message from client' ela= 43041122 driver id=1413697536 #bytes=1 p3=0 obj#=0 tim=156122372771
CLOSE #3:c=0,e=26,dep=0,type=0,tim=156122372940
=====================
系统操作消耗最大的部分,在于对数据块的读操作等待。从SQL的情况来看,Oracle只有全表扫描一条道路可以选择。而Oracle进行FTS操作的过程我们是清晰的。
在之前的文章中,我们讨论过FTS的操作过程。Oracle在进行FTS的时候,首先会从数据字典中定位数据头块位置,并且以单块读方式获取到头块。在头块分析中,Server Process获取到了数据段所有extents的分区信息,包括起始头块位置和块数量。之后根据这些信息进行一系列的多块读操作。
但是从我们看到的raw结果,Oracle在反复进行对file_id=1和file_id=3文件块的读取。对1号文件的读取主要是多块读,但是大小远远小于我们的经验值。对3号文件完全是点读动作,一次只会读取一个数据块。
下面我们截取出一个片段进行分析。
WAIT #3: nam='db file scattered read' ela= 14472 file#=1 block#=89345 blocks=3 obj#=188217 tim=156068182967
WAIT #3: nam='db file sequential read' ela= 14293 file#=3 block#=7522 blocks=1 obj#=0 tim=156068197389
WAIT #3: nam='db file sequential read' ela= 2580 file#=3 block#=7521 blocks=1 obj#=0 tim=156068200612
WAIT #3: nam='db file sequential read' ela= 2353 file#=3 block#=7520 blocks=1 obj#=0 tim=156068204231
WAIT #3: nam='db file sequential read' ela= 2355 file#=3 block#=7524 blocks=1 obj#=0 tim=156068207835
WAIT #3: nam='db file sequential read' ela= 2353 file#=3 block#=7523 blocks=1 obj#=0 tim=156068211050
WAIT #3: nam='db file sequential read' ela= 2352 file#=3 block#=7526 blocks=1 obj#=0 tim=156068215311
WAIT #3: nam='db file sequential read' ela= 2467 file#=3 block#=7525 blocks=1 obj#=0 tim=156068218786
WAIT #3: nam='db file scattered read' ela= 6824 file#=1 block#=89349 blocks=3 obj#=188217 tim=156068229398
WAIT #3: nam='db file sequential read' ela= 2356 file#=3 block#=7531 blocks=1 obj#=0 tim=156068231844
WAIT #3: nam='db file sequential read' ela= 2457 file#=3 block#=7530 blocks=1 obj#=0 tim=156068234728
从标红部分可以看到,Oracle首先到1号文件读取了3个数据块,启示块号是89345。这个块是什么呢?
SQL> select owner, segment_name, EXTENT_ID, FILE_ID, BLOCK_ID, BLOCKS from dba_extents where file_id=1 and block_id<=89345 and block_id+blocks-1>89345;
OWNER SEGMENT_NAME EXTENT_ID FILE_ID BLOCK_ID BLOCKS
---------- -------------------- ---------- ---------- ---------- ----------
SYS T 0 1 89344 8
显然,这三个块是数据表T第一个extent对应的分区数据块。Oracle在读取了之后,从ITL上知道了对应的Undo空间地址,就连续进行了7次对file_id=3的块操作,而且每次都是一个数据块。
我们随机找一下一个块的属性。
SQL> select owner, segment_name, extent_id, file_id, block_id, blocks, status from dba_undo_extents where file_id=3 and block_id<=7522 and block_id+blocks-1>7522;
OWNER SEGMENT_NAME EXTENT_ID FILE_ID BLOCK_ID BLOCKS STATUS
---------- -------------------- ---------- ---------- ---------- ---------- ---------
SYS _SYSSMU8_1557854099$ 21 3 7424 128 ACTIVE
很明显,Oracle在读了三个数据块之后,非连续(随机)的访问了Undo段_SYSSMU8_1557854099$的第21分区中的一些数据块。目的很明显,就是获取前镜像,而且这些前镜像是进行单块读操作。
当完成所有的前镜像点块读之后,Oracle就可以合并出一致读的结果集。之后,在进行下3个块的读取动作。
5、结论
从上面的实验里面,我们可以得到几个想法。
首先,Oracle进行一致读的时候,每次进行多块读,获取的数据块数量是相对较少的。一般来说,多块读的上限规模和操作系统IO能力、Oracle内部多块读参数有关。但是在实例中,一次只会读出三块,猜想与一致读有关。
其次,在获取到数据块之后,Oracle会按照每个块中ITL信息,以单块的方式“点读”那些Undo块。并且进行合并结果集操作。
最后,从10046结构看,一致读是相当消耗资源的。在一致读状态下,检索性能要受到几个数量级别的影响。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17203031/viewspace-754233/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/17203031/viewspace-754233/