先来看看有关redo的东西:
redo 向量被记录在redo 日志文件中,oracle使用日志文件的目的就是介质错误时,可以恢复数据库 。oracle有两种日志文件,分别是在线日志文件和归档日志文件。每个数据库至少还有两组日志文件,当其中一个日志文件写满时,oracle就会切换到下一个日志文件继续写,此时,当日志再次写满时,如果oracle没有启用归档日志,oracle会直接覆盖原来的日志文件,再次写入1号日志文件。如果oracle启用了归档日志,oracle会先将1号日志写入归档日志,再写入日志。以下是oracle官方文档的一些解释,在这就不翻译了
Redo entries record data that you can use to reconstruct all changes made to the database, including the undo segments. Therefore, the redo log also protects rollback data. When you recover the database using redo data, the database reads the change vectors in the redo records and applies the changes to the relevant blocks.
Redo records are buffered in a circular fashion in the redo log buffer of the SGA and are written to one of the redo log files by the Log Writer (LGWR) database background process. Whenever a transaction is committed, LGWR writes the transaction redo records from the redo log buffer of the SGA to a redo log file, and assigns a system change number (SCN) to identify the redo records for each committed transaction. Only when all redo records associated with a given transaction are safely on disk in the online logs is the user process notified that the transaction has been committed.
Redo records can also be written to a redo log file before the corresponding transaction is committed. If the redo log buffer fills, or another transaction commits, LGWR flushes all of the redo log entries in the redo log buffer to a redo log file, even though some redo records may not be committed. If necessary, the database can roll back these changes.
有关commit的一些事情:
不管事务的大小是多少,commit是一个非常快速的动作。有时候我们以为一个非常大的事务在commit的时候的会非常的慢。很多开发人员认为commit一个小的事务会比提交一个大的事务要占用更少的系统的资源,但是实际上这种做法是在增加系统资源的消耗。在适合的时候提交事务(比如说事务已经全部完成),你不仅提高性能,而且可以减少共享资源的争用(日志文件,很多内部的latch等等)。下面来看一个有关的例子:
SQL> create table t (x int);
表已创建。
SQL> set serveroutput on
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(i);
7 end loop;
8 COMMIT;
9 dbms_output.put_line( dbms_utility.get_time-l_start || ' hsecs' );
10 END;
11 /
32 hsecs
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(i);
7 COMMIT;
8 end loop;
9 dbms_output.put_line( dbms_utility.get_time-l_start || ' hsecs' );
10 END;
11 /
67 hsecs
PL/SQL 过程已成功完成。
我们可以看到不断的提交事务,耗用的时间要比事务完成后在提交要大的的多,所以说不用使用频繁的提交来减少系统资源的争用。
但是,为什么commit会非常的迅速,因为在commit之前oracle已经完成了99%的工作,而commit要做的事非常的少。以下这些就是oracle在commit之前已经完成的:
-
在SGA中生存回滚段记录
-
在SGA中生成已经修改的数据块
-
在SGA中生成上述两条记录的重做日志记录
-
根据上述记录的大小,一些上述的记录的组合会被刷到磁盘上
-
得到所有相关的锁
当我们发出commit命令时,oracle做了以下的一些事情
-
对当前事务生成一个scn号
-
LGWR将剩余的日志信息刷到磁盘上, transaction entry会被移除,V$TRANSACTION中的相关的记录会消失
-
释放的所有的的锁资源。
-
所有修改的BUFFER CACHE中的数据块会被标为clean,也就是说其它事务可以覆盖这个数据块中的内容。
所以通过以上,可以看到,commit要做的事情并不多。其中最长的操作就是执行LGWR将日志刷到磁盘上。但是,LGWR占用的时间也非常的有限,事实上,在提交之前,重做日志的大多数已经被循环的刷到磁盘上了,这样就可以避免提交事务因为刷redo到磁盘上而消耗很多的时间。当redo满足以下条件时,oracle会刷出redo到磁盘上:
-
每隔3秒中
-
当redo达到buffer的三分之一时
-
发出commit命令之后。
我们来看一个例子,来证明commit是一个很平滑的操作
SQL> create table t as select * from all_objects;
表已创建。
SQL> insert into t select * from t;
已创建74429行。
SQL> insert into t select * from t;
已创建148858行。
SQL> commit;
CREATE OR REPLACE PROCEDURE DO_COMMIT(P_ROWS IN NUMBER) AS
L_START NUMBER;
L_AFTER_REDO NUMBER;
L_BEFORE_REDO NUMBER;
BEGIN
SELECT V$MYSTAT.VALUE
INTO L_BEFORE_REDO
FROM V$MYSTAT, V$STATNAME
WHERE V$MYSTAT.STATISTIC# = V$STATNAME.STATISTIC#
AND V$STATNAME.NAME = 'redo size';
L_START := DBMS_UTILITY.GET_TIME;
INSERT INTO T
SELECT * FROM T WHERE ROWNUM < P_ROWS;
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' rows created');
DBMS_OUTPUT.PUT_LINE('Time to INSERT: ' || TO_CHAR(ROUND((DBMS_UTILITY.GET_TIME - L_START) / 100,5),'999.99') || ' seconds');
L_START := DBMS_UTILITY.GET_TIME;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Time to COMMIT: ' || TO_CHAR(ROUND((DBMS_UTILITY.GET_TIME - L_START) / 100,5),'999.99') || ' seconds');
SELECT V$MYSTAT.VALUE
INTO L_AFTER_REDO
FROM V$MYSTAT, V$STATNAME
WHERE V$MYSTAT.STATISTIC# = V$STATNAME.STATISTIC#
AND V$STATNAME.NAME = 'redo size';
DBMS_OUTPUT.PUT_LINE('Generated ' ||TO_CHAR(L_AFTER_REDO - L_BEFORE_REDO,'999,999,999,999') || ' bytes of redo');
DBMS_OUTPUT.NEW_LINE;
END;
现在我们调用上述过程:
SQL> BEGIN
2 FOR I IN 1 .. 5 LOOP
3 DO_COMMIT(POWER(10, I));
4 END LOOP;
5 END
6 ;
7 /
9 rows created
Time to INSERT: 3.45 seconds
Time to COMMIT: .00 seconds
Generated 2,484 bytes of redo
99 rows created
Time to INSERT: .03 seconds
Time to COMMIT: .00 seconds
Generated 9,356 bytes of redo
999 rows created
Time to INSERT: .24 seconds
Time to COMMIT: .00 seconds
Generated 103,336 bytes of redo
9999 rows created
Time to INSERT: 1.15 seconds
Time to COMMIT: .00 seconds
Generated 1,052,000 bytes of redo
99999 rows created
Time to INSERT: 6.31 seconds
Time to COMMIT: .00 seconds
Generated 11,500,372 bytes of redo
PL/SQL 过程已成功完成。
SQL> show parameter log_buffer;
NAME TYPE VALUE
------------------------------------ ----------- ---------------
log_buffer integer 8593408
可以看到,不管事务生成的redo多大,commit用的时间总是很短,当redo达到cache的三分之一时,lgwr会在后台将redo刷到磁盘上,所以当提交时,剩下的redo不是很多,所以说redo对commit的影响是很小的。
有关rollback的一些东西:
就上面的这个案例,假如我们把commit换成rollback又会是怎么样的了:
9 rows created
Time to INSERT: .00 seconds
Time to ROLLBACK: .00 seconds
Generated 1,384 bytes of redo
99 rows created
Time to INSERT: .00 seconds
Time to ROLLBACK: .00 seconds
Generated 9,768 bytes of redo
999 rows created
Time to INSERT: .03 seconds
Time to ROLLBACK: .00 seconds
Generated 108,052 bytes of redo
9999 rows created
Time to INSERT: .09 seconds
Time to ROLLBACK: .02 seconds
Generated 1,106,744 bytes of redo
99999 rows created
Time to INSERT: .87 seconds
Time to ROLLBACK: .03 seconds
Generated 12,208,564 bytes of redo
PL/SQL 过程已成功完成。
所以可以看到,事务的大小对rollback的影响还是比较大的,原因在于oracle要完成恢复rollback之前的所有工作,让数据库恢复到一个一致的状态 。rollback 之后oracle要完成以下的一些操作:
-
读取undo段的信息,对之前所有操作做逆操作,比如说执行了insert,那么此时就要执行delete。
-
释放所有的锁。
可以看出rollback是个很消耗数据库资源的事情,所以在oracle中一般不要在比较大的事务中使用rollback。