如果当前联机日志文件写完时,这时就需要转换到另外一个可写的联机日志文件上去,这个过程叫做日志切换。日志切换的大致过程包括:
1) 从控制文件中得到下一个可用的联机日志文件。
2) 记录写入当前联机日志文件的最后一个日志块的SCN(叫做high SCN)。关闭当前联机日志文件。
3) 增加SCN,再次操作控制文件,将下一个联机日志文件标志为CURRENT,判断前一个联机日志文件里包含的重做记录所对应的脏数据块是否都已经写入数据文件,如果没有则标记为ACTIVE;如果是则标记为INACTIVE。如果数据库是归档模式,那么LGWR将前一个联机日志文件加入归档列表中,等待归档。
4) 打开新的联机日志文件组中的所有成员,记录当前日志序列号(log sequence)和第一个日志块的SCN(叫做low SCN),新一轮的重做记录开始。
我们经常看到当前日志文件的状态为CURRENT,而前一个日志文件的状态为ACTIVE的情况。实际上,这是由于内存中存在很多脏数据块,而脏数据块的写入是通过DBWR进程完成的。如果脏数据块没有积累到一定的量,DBWR是不会将它们写入数据文件的。所以,ACTIVE状态的日志文件表示该日志文件里包含的重做记录所对应的脏数据块还没有被DBWR进程写入数据文件。事实上,日志切换时触发增量检查点(incremental checkpoint),CKPT将会触发DBWR写脏数据块。增量检查点只是在控制文件中记录这时在检查点队列上的脏数据块在第一次修改时所对应的日志块在日志文件中的地址,这个地址叫做检查点位置(checkpoint position)。但是DBWR启动并不表示立即写脏数据块,除非脏数据块的数量达到一定程度,或超过一定时间等。我们来模拟一下这种情况。
SQL> select group#,status from v$log;
GROUP# STATUS
---------- ----------------
1 CURRENT
2 INACTIVE
3 INACTIVE
SQL> create table t2 as select * from dba_objects;
SQL> select group#,status from v$log;
GROUP# STATUS
---------- ----------------
1 ACTIVE
2 CURRENT
3 INACTIVE
我们可以看到,当创建了表t2的时候,发生了日志切换。如果没有看到日志切换,可以再删除表t2然后再创建表t2,如此反复几次,就能够引起日志切换。这时我们看到2号日志文件为当前打开的日志文件,其状态为CURRENT。而1号日志文件为上次打开的日志文件,状态为ACTIVE,就说明其对应的脏数据块还没有写入数据文件。这时我们可以等待一段时间,等待增量检查点的完成,也可以手工触发完全检查点(complete checkpoint),触发完全检查点时,DBWR将把所有脏数据块写入磁盘上的数据文件里。
SQL> alter system checkpoint;
SQL> select group#,status from v$log;
GROUP# STATUS
---------- ----------------
1 INACTIVE
2 CURRENT
3 INACTIVE
可以看到,1号日志文件的状态从ACTIVE变成了INACTIVE,说明其对应的脏数据块都已经写入了联机日志文件中。
这里要说明一点。增量检查点触发的DBWR写和完全检查点触发的DBWR写的优先级是不一样的,这也就是为何在上面的例子中进行日志切换以后,前一个日志的状态为ACTIVE,而且要等一段时间以后其状态才能变为INACTIVE,而我们发出alte system checkpoint以后立即变为INACTIVE的原因。因为日志切换触发增量检查点,而增量检查点通知DBWR启动,但是由于这时触发DBWR启动的条件的优先级较低,所以DBWR不会立即去写脏数据块,而是要等一段时间才会实际的写脏数据块。所以我们等待日志状态变为INACTIVE的时间就是等DBWR开始真正写的时间加上DBWR实际写入数据文件所花费的时间。而alte system checkpoint触发完全检查点,其优先级很高,所以通过它触发的DBWR会立即去写脏数据块,所以我们等待日志状态变为INACTIVE的时间就是DBWR实际写入数据文件所花费的时间。
我们可以设定初始化参数:log_checkpoints_to_alert为true,从而将检查点启动和结束的时间记录到跟踪文件里去。这里是所记录的信息的一个例子:
Wed Dec 13 18:27:48 2006
Beginning log switch checkpoint up to RBA [0x85.2.10], SCN: 2164686
Thread 1 advanced to log sequence 133
Current log# 3 seq# 133 mem# 0: /oracle/oradata/ora10g/redo03.log
Wed Dec 13 18:32:45 2006
Completed checkpoint up to RBA [0x85.2.10], SCN: 2164686
……
Wed Dec 13 19:02:15 2006
Beginning global checkpoint up to RBA [0x85.883.10], SCN: 2165818
Completed checkpoint up to RBA [0x85.883.10], SCN: 2165818
从上面可以看到,由于日志切换而发生的增量检查点从18:27:48开始,到18:32:45结束,用了5分钟的时间。而我们强制进行完全检查点,则只用了大概1秒钟不到的时间。实际上,DBWR在实际写入数据文件所花费的时间都是一样的,也就是不到1秒。5分钟的差别就在于DBWR等了5分钟才实际开始写数据文件。
在大致了解了日志缓冲区的操作过程以后,我们来深入了解一下其内部的工作原理是怎样的。
当用户发出DML语句,请求更新某些表里的数据时,在日志缓冲区中生成重做记录的过程如下所示。
1) 服务器进程判断buffer cache中是否存在要求被更新的数据块,如果没有,则从数据文件中将数据块调入buffer cache。然后以排他(Exclusive)模式将该数据块钉住(ping)。注意,这里的数据块包括回滚段段头(事务表)、回滚段数据块以及表所对应的数据块等。
2) 服务器进程构造一组改动向量(change vector)来描述对数据块所做的变化过程。这组改动向量会放在session的PGA中。实际上,这组改动向量也就是重做记录了。
3) 根据重做记录的大小,判断在日志缓冲区中需要多少空间。
4) 判断当前的SCN值,并将其存放到重做记录中。注意,并不是每个重做记录都具有不同的SCN值,不同的重做记录可能会共享相同的SCN值。
5) 进程获得名为redo copy的latch。
6) 进程获得名为redo allocation的latch。
7) 检查当前是否有其他进程生成了比当前持有的SCN更高的SCN值。如果是,则生成一个新的SCN值,并代替第四步所持有的SCN值。
8) 判断是否有足够的空间容纳当前的重做记录,这里足够的空间既包括日志缓冲区,也包括联机日志文件。这时的逻辑比较复杂。
a) 如果联机日志文件中有足够的空间,则判断日志缓冲区是否有足够的空间。
I. 如果日志缓冲区中没有足够的空间,则进程释放redo copy latch和redo allocation latch。然后在下面的两个选项中做一个选择:
(1)如果这时已经有其他进程或其他条件触发了LGWR,则等待LGWR的完成;
(2)如果这时LGWR没有启动,则触发LGWR启动。为了防止多个进程同时触发LGWR,oracle还引入了名为redo writing的latch。当LGWR进程在写重做记录到联机日志文件的过程中,会一直持有redo writing latch,这时任何试图获得该latch的进程都必须等待。

