四、计算方法及测试脚本下面介绍事务中各种DML语句(insert,update,delete)使用UNDO记录的计算方法,每种操作会介绍估算公式并简单示例解释:
4.1、delete 操作
4.1.2、一般删除
计算公式:USED_UREC=删除表记录数+删除表索引记录数(每个索引每行记录算一条记录)
假设表有2个索引,删除10条记录
USED_UREC=10+2*10=30
SQL>deletefromt1whererownum<=10;
10rowsdeleted
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
30
SQL>commit;
Commitcomplete4.1.2、通过索引范围条件删除记录
USED_UREC=删除表记录数+更新索引块数
假设表有1个单字段普通索引,通过索引范围查询10000条记录并删除,每个索引块大块保存200条记录
USED_UREC=10000+10000/200=10050
SQL>deletefromt1whereobject_idbetween10000and20000;
19871rowsdeleted
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
20242
SQL>commit;
Commitcomplete4.2、update 操作
4.2.1、一般更新
USED_UREC=更新表记录数+更新索引记录变更数*2(每行索引变更有2个记录,一个是记录原索引指针,另外是记录新索引指针)
假设表有2个索引,更新10条记录的2个字段,其中要更新1个是普通字段,1个是索引字段
USED_UREC=10+2*10=30
SQL>updatet1setobject_name='test',owner='MK'whererownum<=10;
10rowsupdated
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
30
SQL>commit;
Commitcomplete注:在语句中,普通字段不管更新前与更新后是否发生变化,都会产生UNDO记录,但是索引字段只有发生了变化才会产生UNDO记录,如下测试,object_name做了更新操作,但是没有发生变化,所以索引记录不会发生变更。
SQL>updatet1setobject_name=object_name,owner='MK'whererownum<=10;
10rowsupdated
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
10
SQL>commit;
Commitcomplete4.2.2、通过索引范围条件更新该索引字段
USED_UREC=更新表记录数+更新索引块数*2
假设表有1个单字段普通索引,通过索引范围查询10000条记录并更新对应的索引字段,每个索引块大块保存200条记录
USED_UREC=10000+2*(10000/200)=10100
SQL>updatet1setobject_id=object_id+1whereobject_id>10000;
19584rowsupdated
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
19862
SQL>commit;
Commitcomplete注:可以看出这种通过索引范围访问并更新该索引字段的情况非常少,要求也非常特殊。假设刚才的语句做一点小变化都不满足要求,如下加了一个rownum条件,实际更新的记录数都是一样的,但是使用的UNDO记录数只能按一般更新计算。
SQL>updatet1setobject_id=object_id+1whereobject_id>10000andrownum<1000000;
19584rowsupdated
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
58752
SQL>commit;
Commitcomplete4.3、insert 操作
4.3.1、单条insert (insert into t1 values ...)
USED_UREC=新增记录数+表索引个数*新增记录数
假设表有2个索引,新增3条记录
USED_UREC=3+3*2=9
SQL>insertintot1(owner,object_name,object_id)values('MK','test1',123456);
1 row inserted
SQL>insertintot1(owner,object_name,object_id)values('MK','test2',1234567);
1 row inserted
SQL>insertintot1(owner,object_name,object_id)values('MK','test3',12345678);
1 row inserted
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
9
SQL>commit;
Commitcomplete4.3.2、批量insert(insert into t1 select ...)
这个非常难准确计算,因为新增记录会利用以前空闲的数据块,只有数据块有记录变化都需要保存数据块对应的回滚记录,同时也保存索引的回滚记录,所以
USED_UREC≈新增记录变更表数据块数+∑每个变更表数据块对应变更的索引块数
假设表有2个索引,新增1000条记录,每个数据块大约可保存600条记录,新增第一个数据块保存了600条记录,同时变更了第1个索引30个索引块,第2个索引40个数据块,新增第2个数据块保存了400条记录,同时变更了第1个索引20个索引块,第2个索引60个数据块
USED_UREC≈2+(30+40+20+60)=152
SQL>insertintot1select*fromdba_objectswhererownum<=10000;
10000rowsinserted
SQL>selectUSED_URECfromv$transaction;
USED_UREC
----------
7837
SQL>commit;
Commitcomplete五、总结
以上脚本是在Oracle9.2上测试,Oracle对UNDO的处理非常复杂,这里介绍只是常用的一些DML产生UNDO估算方法,从估算公式可以看出,索引对DML操作的影响非常大,当一个表有索引比没索引时做DML操作花费的UNDO开销非常具大, 因为数据库要保证事务回滚的可行性,需要对索引做许多额外的事情,更新索引字段及批量INSERT操作尤其明显。在有索引和没索引的表上做批量数据导入,性能有可能相关好几倍。通过估算Oracle的DML操作需要的UNDO记录数,也可以间接估算一个DML还需要的时间。工作中有时会遇到一个DML操作时间非常长,如果v$session_longops视图也没有可以跟踪的信息,这时就可以通过v$transaction的USED_UREC信息估算SQL的进度。DML在操作时,USED_UREC是一直在增加的,当事务开始回滚时USED_UREC会开始下降,直到等于0,则回滚完成,因此我们也可以根据这个字段的变化判断回滚进度。