2007年6月25日星期一

《Expert one on one Oracle》- 重做回滚- 笔记-1

“重做”允许Oracle重做事务。“回滚”则允许Oracle撤销或者回滚事务。

重做

重做日志文件是数据库的事务日志,只用来恢复数据库。Oracle有两种类型重做日志文件:在线重做日志和归档重做日志。在线重做日志以循环的方式来写入,当其中一个日志文件写满,就会切换写另外的日志文件,循环往复。归档日至是当在线日志写满的时候,将在线日志拷贝到另外一个地方。

COMMIT

COMMIT语句的执行并不消耗太多时间,而且并不是事务越大COMMIT所消耗的时间越多。实际的情况是,在COMMIT执行之前,Oracle已经完成所要提交事务的大部分工作。

SQL> create table t ( x int );
Table created.

SQL> set serveroutput on
SQL>
SQL> declare
2 l_start number default dbms_utility.get_time;
3 begin
4 for i in 1 .. 10000
5 loop
6 insert into t values ( 1 );
7 end loop;
8 commit;
9 dbms_output.put_line
10 ( dbms_utility.get_time-l_start ' hsecs' );
11 end;
12 /
135 hsecs

PL/SQL procedure successfully completed.

SQL>
SQL> declare
2 l_start number default dbms_utility.get_time;
3 begin
4 for i in 1 .. 10000
5 loop
6 insert into t values ( 1 );
7 commit;
8 end loop;
9 dbms_output.put_line
10 ( dbms_utility.get_time-l_start ' hsecs' );
11 end;
12 /
161 hsecs

PL/SQL procedure successfully completed.

从上面可以看到,插入多样的数据一次提交比进行多次提交消耗更少的时间。所以,提交的响应时间和事务大小并无太大关系。因为提交之前,Oracle已经完成99%的工作,具体为:
a. 回滚段的纪录已经在SGA中产生。
b. 更改的数据块已经在SGA中产生。
c. 上述两项的缓存的REDO已经在SGA产生。
d. 基于如上三项的的大小以及所消耗时间,上述数据的部分已经写入磁盘。
e. 已经获取所有需要的锁。

那么提交的时候,需要完成如下工作:
a. 为事务产生SCN。
b. LGWR将余下的缓存的重做日志写入磁盘,同时将SCN好记入到重做日志文件。完成这一步之后,事务实际上已经提交。事务实体被移除。在V$TRANSACTION表中找不到事物的相关纪录。
c. Session所持有的锁被释放。
d. 如果被事务所修改的快还在缓存中,那么大多数将被访问以及被“清除”。

真正消耗时间的在于LGWR将重做日志写入磁盘,因为这是物理的I/O访问。但是COMMIT之后,LGWR并不是将所有的 内容写入重做日志。在COMMIT之前,LGWR已经持续的写重做日志。当如下条件满足的时候,LGWR写重做日志:
a. 每三秒
b. 当达到1/3或者1MB大小
c. 当COMMIT的时候

SCN是ORACLE用户保证事务的顺序,确保数据库能被正确恢复。同时,它也用来保证数据库的读一至性以及检查点(checkpointing)。每当事务提交,SCN号增一。

SQL> create table t
2 as
3 select * from all_objects
4 /
Table created.

SQL> insert into t select * from t;
29257 rows created.

SQL> insert into t select * from t;
58514 rows created.

SQL> insert into t select * from t where rownum < 12000;
11999 rows created.

SQL> commit;
Commit complete.

SQL>
SQL> create or replace procedure do_commit( p_rows in number )
2 as
3 l_start number;
4 l_after_redo number;
5 l_before_redo number;
6 begin
7 select v$mystat.value into l_before_redo
8 from v$mystat, v$statname
9 where v$mystat.statistic# = v$statname.statistic#
10 and v$statname.name = 'redo size';
11
12 l_start := dbms_utility.get_time;
13 insert into t select * from t where rownum < name =" 'redo">
SQL> set serveroutput on format wrapped
SQL> begin
2 for i in 1 .. 5
3 loop
4 do_commit( power(10,i) );
5 end loop;
6 end;
7 /
9 rows created
Time to INSERT: .05 seconds
Time to COMMIT: .00 seconds
Generated 1,368 bytes of redo
99 rows created
Time to INSERT: .00 seconds
Time to COMMIT: .00 seconds
Generated 11,596 bytes of redo


999 rows created
Time to INSERT: .03 seconds
Time to COMMIT: .00 seconds
Generated 116,732 bytes of redo

9999 rows created
Time to INSERT: 1.34 seconds
Time to COMMIT: .00 seconds
Generated 1,079,844 bytes of redo


99999 rows created
Time to INSERT: 5.56 seconds
Time to COMMIT: .00 seconds
Generated 11,175,056 bytes of redo


PL/SQL procedure successfully completed.
从上述运行结果可以看到,随着插入数据量的增大,产生的重做日志也逐渐增大,插入所消耗的时间也增多,但是COMMIT所消耗的时间仍然相当的少。所以,也验证了上述结论:在COMMIT之前,ORACLE已经完成了绝大多数的工作,所以COMMIT锁耗用的时间是相当少的。

回滚

将上述程序的COMMIT(23,25行)替换成ROLLBACK,再执行察看回滚所消耗的时间。

Time to INSERT: .04 seconds
Time to ROLLBACK: .00 seconds
Generated 1,504 bytes of redo

99 rows created
Time to INSERT: .00 seconds
Time to ROLLBACK: .00 seconds
Generated 12,288 bytes of redo

999 rows created
Time to INSERT: .03 seconds
Time to ROLLBACK: .00 seconds
Generated 123,736 bytes of redo
9999 rows created
Time to INSERT: .42 seconds
Time to ROLLBACK: .00 seconds
Generated 1,146,652 bytes of redo


99999 rows created
Time to INSERT: 3.99 seconds
Time to ROLLBACK: 1.64 seconds
Generated 11,883,064 bytes of redo


PL/SQL procedure successfully completed.

从上述运行结果可知,与COMMIT不同,随着数据量的增大,回滚所消耗的时间也增加。这是因为回滚是一个消耗时间的操作。

回滚之前,数据库所完成的工作与提交部分(可以参考上述COMMIT部分)相同。
回滚时,ORACLE所需要完成的工作为:
a. 回滚所有的更改。从回滚段中读取数据,并执行与原来相反的操作。例如,如果原来插入一行,回滚时便删除一行。如果更新一行,回滚时候必须更新回去。
b. Session所持有的锁被释放。

测量产生的日志数量

创建如下的一个视图,可以方便的查询系统中的重做日志。
create or replace view redo_size
as
select value
from v$mystat, v$statname
where v$mystat.statistic# = v$statname.statistic#
and v$statname.name = 'redo size';

通过测试脚本,可以看到:通过一条语句更新N行,与通过N条语句来更新N行,所产生的重做日志的数量大致相同。对于删除操作,结果也类似。但对于更新操作,批量更新产生的重做日志更小一些。

还有,如果我们插入2000bytes的行,实际每行产生的重做日志要高于2000bytes。对于删除操作也类似。对于更新操作,将会是2000betys的两倍数量(需要记录数据以及回滚)。

触发器对重做日志的影响:
a. 删除操作的BEFORE和AFTER触发器不会增加额外的重做日志。
b. 插入操作的BEFORE和AFTER触发器都会增加额外的重做日志。
c. 更下操作的BEFORE触发器产生额外重做日志,而AFTER触发器不产生。
d. 行的大小影响插入过程额外重做日志,更新操作不受影响。

能否关闭重做日志

尽量不要关闭重做日志功能,有些语句NOLOGGING因子,但是实际上还是会产生重做日志。这些日志因为记录数据词典更改而产生的。多于NOLOGGING,有如下几点需要注意:
a. 即使使用NOLOGGING,还是会产生少量的重做日志,这用来保护数据词典。
b. NOLOGGING之影响当前操作。例如如果在创建表的语句中使用了NOLOGGING,那么只是在创建表的过程中不产生重做日志。后续对于标的插入删除以及更新还是会产生日志。其他的特殊操作例如INSERT /*+ APPEND */和SQLLDR插入数据不会产生日志。
c. 在以归档模式运行的数据库中,一旦使用NOLOGGING,那么尽快将受影响的文件备份。

待续....

没有评论: