oracle redo与undo_笔记3:提交和回滚处理 COMMIT做什么
提交和回滚处理
COMMIT做什么
COMMIT通常是一个非常快的操作,而不论事务大小如何。可以认为,一个事务越大(它影响的数据越多),COMMIT需要的时间就越长。不是这样的。不论事务有多大,COMMIT的响应时间一般都很“平”(flat)。这是因为COMMIT并没有太多的工作去做,不过它所做的确实至关重要。
为什么COMMIT的响应时间相当“平”,而不论事务大小呢?在数据库中执行COMMIT之前,困难的工作都已经做了。我们已经修改了数据库中的数据,所以99.9%的工作都已经完成。例如,已经发生了如下操作。
.已经在SGA中生成了undo块
.已经在SGA中生成了已修改数据块
.已经在SGA中生成了对应前两项的缓存redo
.取决于前三项的大小,以及这些工作花费的时间,前面的某个数据(或某些数据)可能已经刷新输出到磁盘
.已经得到了所需的全部锁
执行COMMIT时,余下的工作只是:
.为事务生成一个SCN(System Change Number,系统改变号)。SCN是Oracle使用的一种简单的计时机制,用于保证事务的顺序,并支持失败恢复。SCN还用于保证数据库中的读一致性和检查点。可以把SCN看作一个钟摆,每次有人COMMIT时,SCN都会增1。
.LGWR将所有余下的缓存重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中。这一步就是真正的COMMIT。如果出现了这一步,即已经提交。事务条目会从V$TRANSACTION中“删除”,这说明我们已经提交。
.v$LOCK中记录着我们的会话持有的锁,这些锁都将被释放,而排队等待这些锁的每一个人都会被唤醒,可以继续完成他们的工作。
.如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并“清理”。块清除(block cleanout)是指清除存储在数据库块首部的与锁相关的信息。实质上讲,我们在清除块上的事务信息,这样下一个访问这个块的人就不用再这么做了。我们采用一种无需生成重做日志信息的方式来完成块清除,这样可以省去以后的大量工作。
可以看到,处理COMMIT所要做的工作很少。其中耗时最长的操作要算LGWR执行的活动,因为这些磁盘写是物理磁盘I/O。不过,这里LGWR花费的时间并不会太多,之所以能大幅减少这个操作的时间,原因是LGWR一直在以连续的方式刷新输出重做日志缓冲区的内容。在你工作期间,LGWR并非缓存着你做的所有工作;实际上,随着你的工作的进行,LGWR会在后台增量式地刷新输出重做日志缓冲
区的内容。这样做是为了避免COMMIT等待很长时间来一次性刷新输出所有的redo。
因此,即使我们有一个长时间运行的事务,但在提交之前,它生成的许多缓存重做日志已经刷新输出到磁盘了(而不是全部等到提交时才刷新输出)。这也有不好的一面,COMMIT时,我们往往必须等待,直到尚未写出的所有缓存redo都已经安全写到磁盘上才行。也就是说,在默认情况下,对LGWR的调用是一个同步(synchronous)调用。尽管LGWR本身可以使用异步I/O并行地写至日志文件,但是我们的事务通常会一直等待LGWR完成所有写操作,并收到数据都已在磁盘上的确认才会返回。
为了说明COMMIT是一个“响应时间很平”的操作,下面将生成不同大小的redo,并测试插入(INSERT)和提交(COMMIT)的时间。在我们完成这些INSERT和COMMIT的同时,会使用下面的实用函数测量会话产生的redo数量:
sys@ORCL>create or replace function get_stat_val(p_name in varchar2) return number
2 as
3 l_val number;
4 begin
5 select b.value
6 into l_val
7 from v$statname a,v$mystat b
8 where a.statistic#=b.statistic#
9 and a.name=p_name;
10
11 return l_val;
12 end;
13 /
Function created.
我们会使用DBMS_UTILITY包例程GET_CPU_TIME和GET_TIME,测量提交事务所用的CPU和耗用时间。
用来生成工作负载和相关报告的具体PL/SQL块如下:
sys@ORCL>@big_table.sql
Table created.
Table altered.
Enter value for 1: 10
old 3: l_rows number := &1;
new 3: l_rows number := 10;
Enter value for 1: 10000000
old 9: where rownum <= &1;
new 9: where rownum <= 10000000;
PL/SQL procedure successfully completed.
Table altered.
PL/SQL procedure successfully completed.
sys@ORCL>create table t
2 as select rw ownum id,a.*
3 from all_objects a
4 where 1=0
5 /
Table created.
sys@ORCL>@20131121_qc_script.sql
- Rows Redo CPU Elapsed
- 10 1,556 0 1
-100 10,080 0 0
- 1,000109,688 0 1
- 10,000 1,122,720 0 24
- 100,000 8,880,764 1 1
- 1,000,000 8,850,804 0 1
PL/SQL procedure successfully completed.
sys@ORCL>start 20131121_qc_script.sql
- Rows Redo CPU Elapsed
- 10 1,356 1 1
- 100 9,880 1 1
- 1,000 105,416 0 0
- 10,000 1,078,864 1 1
- 100,000 8,708,852 0 1
- 1,000,000 8,854,560 1 1
PL/SQL procedure successfully completed.
?两次执行结果不一样
可以看到,使用一个精确度为百分之一秒的计时器度量时,随着生成不同数量的redo(从1356字节到8MB),却几乎测不出COMMIT时间的差异。在我们处理和生成重做日志时,LGWR也没有闲着,它在后台不断地将缓存的重做信息刷新输出到磁盘上。所以,我们生成8MB的重做日志信息时,LGWR一直在忙着,可能每1MB左右刷新输出一次。等到COMMIT时,剩下的重做日志信息(即尚未写出到磁盘的redo)已经不多了,可能与创建10行数据生成的重做日志信息相差无几。不论生成了多少redo,结果应该是类似的。
20131121_qc_script.sql
declare
l_redo number;
l_cpu number;
l_ela number;
begin
dbms_output.put_line
('-' || ' Rows' || ' Redo' ||
' CPU' || ' Elapsed');
for i in 1..6
loop
l_redo :=get_stat_val('redo size');
insert into t select * from big_table where rownum <=power(10,i);
l_cpu :=dbms_utility.get_cpu_time;
l_ela :=dbms_utility.get_time;
commit work write wait;
dbms_output.put_line
('-' ||
to_char( power(10,i),'9,999,999') ||
to_char((get_stat_val('redo size')-l_redo),'999,999,999') ||
to_char((dbms_utility.get_cpu_time-l_cpu),'999,999') ||
to_char((dbms_utility.get_time-l_ela),'999,999'));
end loop;
end;
/
big_table.sql
create table big_table
as
select rownum id, a.*
from all_objects a
where 1=0
/
alter table big_table nologging;
declare
l_cnt number;
l_rows number := &1;
begin
insert /*+ append */
into big_table
select rownum, a.*
from all_objects a
where rownum <= &1;
l_cnt := sql%rowcount;
commit;
while (l_cnt < l_rows)
loop
insert /*+ APPEND */ into big_table
select rownum+l_cnt,
OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID,
OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS,
TEMPORARY, GENERATED, SECONDARY, NAMESPACE, EDITION_NAME
from big_table
where rownum <= l_rows-l_cnt;
l_cnt := l_cnt + sql%rowcount;
commit;
end loop;
end;
/
alter table big_table add constraint
big_table_pk primary key(id);
exec dbms_stats.gather_table_stats( user, 'BIG_TABLE', estimate_percent=> 1);
本文来源 我爱IT技术网 http://www.52ij.com/jishu/5256.html 转载请保留链接。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
