redo 介绍

重做日志主要记录对数据所做的所有更改,包括未提交和已提交的更改。

Oracle通过Redo来保证数据库的事务可以被重演,从而使得在故障之后,数据可以被恢复。在数据库中,Redo的功能主要通过3个组件来实现:Redo Log Buffer、LGWR后台进程和Redo Log File(在归档模式下,Redo Log File最终会经由ARCn进程写出为归档日志文件)。

在数据发生commit时,将更改的SQL脚本写入在线重做日志(ONLINE REDO LOG)。主要用于数据库的增量备份和增量恢复。体系图如下所示:

Redo 核心组件

Redo Log Buffer

位于SGA之中,是一块循环使用的内存区域,其保存数据库变更的相关信息。这些信息以重做条目(Redo Entries)形式存储(Redo Entries也经常称为Redo Records)。

Redo Entries包含重构、重做数据库变更的重要信息,这些变更包括INSERT、UPDATE、DELETE、CREATE、ALTER或者DROP等。

Redo Entries的内容被Oracle数据库进程从用户的内存空间(PGA)复制到SGA中的Redo Log Buffer之中。Redo Entries在内存中占用连续的顺序空间,由于Redo Log Buffer是循环使用的,Oracle通过一个后台进程LGWR不断把Redo Log Buffer的内容写出到Redo Log File中,Redo Log File同样是循环使用的。

Redo Log Buffer:如果数据需要写到在线重做日志中,则在写至磁盘之前要在Redo Buffer中临时缓存这些数据。由于内存到内存的传输比内存到磁盘的传输快得多,因此使用重做日志Buffer可以加快数据库的操作。数据在重做缓冲区的停留时间不会太长。

LGWR

LGWR是Log Writer的缩写,日志写进程。主要负责将日志缓冲内容写到磁盘的在线重做日志文件或组中。

DBWn将dirty块写到磁盘之前,所有与buffer修改相关的redo log都需要由LGWR写入磁盘的在线重做日志文件(组),如果未写完,那么DBWn会等待LGWR,也会产生一些相应的等待事件(例如:log file prarllel write。

总之,这样做的目的就是为了当crash时,可以有恢复之前操作的可能,也是Oracle在保持交易完整性方面的一个机制。

相关知识点:
1、LGWR写日志是顺序写,这就解释了一个Orace Server只能有一个LGWR进程,不能像DBWR那样可以有多个,否则就无法保证顺序写的机制,而且可能会产生锁的问题。
2、用户进程每次修改内存数据块时,都会在日志缓冲区(redo buffer)中构造一个相应的重做条目(redo entry),它记录了被修改数据块修改之前和之后的值。
3、LGWR将redo entry写入联机日志文件的情况可以概括为两种:后台写和同步写,或者说异步写和同步写。

二、LGWR进程刷磁盘机制

1、当用户进程提交一事务时写入一个提交记录

2、每3秒将日志缓冲区写入日志文件

3、当日志缓冲区满1/3时,将日志缓冲刷出

4、当DBWR将修改缓冲区写入磁盘前,将日志缓冲区刷出

日志缓冲区是一个循环缓冲区。当LGWR将日志缓冲区的日志写入日志文件后,服务器进程即可以将新的日志项写入到该日志缓冲区。LGWR写入速度很快,以确保日志缓冲区总有空间可以写入新的日志项。

ARCn

归档进程,ARCn的任务就是,当LGWR将在线日志文件填满时,就将其复制到另外一个位置。此后这些归档的重做日志文件可以用于完成介质恢复。

Redo Log Files

重做日志文件,以组出现。可以查询v$logfile找到对应的日志文件。

Redo 工作流程

每个Oracle数据库都至少有两个Online重做日志组,每个组中至少有一个重做日志文件,这些在线重做日志组以循环方式使用。(用户可以通过视图操作添加/修改/删除日志组和日志文件来自定义在线重做日志),每组内的日志文件的内容完全相同,且保存在不同的位置,用于磁盘日志镜像,以做多次备份提高安全性。

默认情况这n组通常只有一组处于活动状态,不断地同步写入已操作的脚本,当日志文件写满时(达到指定的空间配额),如果当前数据库处于归档模式,则将在线日志归档到硬盘,成为归档日志;若当前数据库处于非归档模式,则不进行归档操作,而当前在线日志的内容会被下一次重新写入覆盖而无法保存。

因此,通常对于生产环境的数据库在运行时,是要处于归档模式下的,以保存数据更新的日志。当前归档日志组写满后,Oracle会切换到下一日志组,继续写入,就这样循环切换;当处于归档模式下,切换至原已写满的日志组,若该日志组归档完毕则覆盖写入,若没有则只能使用日志缓冲区,等待归档完毕之后才能覆盖写入。

所以在生产环境中,要求存放 Redo 文件和归档文件的 IO 性能不能相差太多。

当然,处于非归档模式下是直接覆盖写入的。归档重做日志文件实际上就是已填满的“旧”的在线重做日志文件的副本。系统将日志文件填满时,ARCH进程会在另一个位置建立重做日志文件的一个副本,也可以在本地或者远程位置上建立多个另外的副本。

如果由于磁盘损坏或者其他物理故障而导致失败,就会用这些归档重做日志文件来执行介质恢复。默认情况下,一个数据库默认为非归档模式,如果是非归档模式的话,也就说明我们没有办法通过日志来对数据库做一个恢复。

查看REDO LOG状态信息(V$LOG)

查询V$LOG动态性能视图后的结果,这里我们主要关注红色字体部分,这里表示REDO LOG组状态。

可以看到我们这里已经有两种状态为INACTIVE和CURRENT

那么我们把REDO LOG组所有的状态都列出来。并给出相应解释:

  • UNUSED - 从未对联机重做日志组进行写入,这种状态的日志文件要么是刚增加的,要么是当日志不是current redo log时RESETLOGS操作后的状态

  • CURRENT - 当前的联机重做日志组,这意味着该联机重做日志组是活动的。

  • ACTIVE - 联机重做日志组是活动的,但是并非当前联机重做日志组,实例崩溃恢复需要该状态的日志,它可能用于块恢复,它可能已经归档也可能未归档。

  • CLEARING - 在ALTER DATABASE CLEAR LOGFILE 命令后正在将该日志重建为一个空日志,日志清除后其状态更改为UNUSED。

  • CLEARING_CURRENT - 正在清除当前日志文件中的已关闭线程,如果切换时发生某些故障,如写入新日志标题时的I/O错误,则该日志可以停留在该状态。 

  • INACTIVE - 实例恢复不再需要联机重做日志组,它可能已经归档也可能未归档。

查看REDO LOG状态信息(V$LOGFILE)

查询V$LOGFILE动态性能视图后的结果,这里我们主要关注红色字体部分,这里表示REDO LOG文件状态。

可以看到我们这里STATUS(状态)列下并无数据,但实际上现在这个列中的值为NULL,那么我们把REDO LOG文件所有的状态都列出来。并给出相应解释:

  • INVALID – 该文件不可访问

  • STALE - 该文件内容不完全,例如正在添加一个日志文件成员

  • DELETED - 该文件已不再使用

  • NULL – 该文件正在使用中

REDO日志切换命令

REDO组可以用两种方法去切换:

正常切换: (推荐使用)

alter system switch logfile;

用发生检查点的方式去切换:(切换相关状态)

alter system checkpoint;

归档模式 vs 非归档模式

  1.非归档模式       

不适用与生产数据库       

  • 创建数据库时,缺省的日志管理模式为非归档模式       

  • 当日志切换,检查点产生后,联机重做日志文件即可被重新使用       

  • 联机日志被覆盖后,介质恢复仅仅支持到最近的完整备份       

  • 不支持联机备份表空间,一个表空间损坏将导致整个数据库不可用,需要删除掉损坏的表空间或从备份恢复       

  • 对于操作系统级别的数据库备份需要将数据库一致性关闭       

  • 应当备份所有的数据文件、控制文件(单个)、参数文件、密码文件、联机日志文件(可选)    

2.归档模式        

能够对联机日志文件进行归档,生产数据库强烈建议归档       

  • 在日志切换时,下一个即将被写入日志组必须归档完成之后,日志组才可以使用       

  • 归档日志的Log sequence number信息会记录到控制文件之中       

  • 必须有足够的磁盘空间用于存放归档日志              

  • 备份与恢复           

  • 支持热备份,且当某个非系统表空间损坏,数据库仍然处于可用状态,且支持在线恢复           

  • 使用归档日志能够实现联机或脱机时间点恢复(即可以恢复到指定的时间点、指定的归档日志或指定的SCN)

生产常用的 redo 监控查询脚本

#查看日志组和日志文件信息
select * from v$logfile;
select * from v$log;

#果数据库启用了归档模式,可以通过以下查询查看归档日志:
SELECT * FROM v$archived_log;

#通过查看在线重做日志 redo log 每小时的切换次数,可以知道数据库的繁忙程度
SELECT TRUNC(first_time) "Date",
       TO_CHAR(first_time, 'Dy') "Day",
       COUNT(1) "Total",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '00', 1, 0)) "h0",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '01', 1, 0)) "h1",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '02', 1, 0)) "h2",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '03', 1, 0)) "h3",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '04', 1, 0)) "h4",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '05', 1, 0)) "h5",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '06', 1, 0)) "h6",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '07', 1, 0)) "h7",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '08', 1, 0)) "h8",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '09', 1, 0)) "h9",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '10', 1, 0)) "h10",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '11', 1, 0)) "h11",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '12', 1, 0)) "h12",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '13', 1, 0)) "h13",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '14', 1, 0)) "h14",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '15', 1, 0)) "h15",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '16', 1, 0)) "h16",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '17', 1, 0)) "h17",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '18', 1, 0)) "h18",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '19', 1, 0)) "h19",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '20', 1, 0)) "h20",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '21', 1, 0)) "h21",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '22', 1, 0)) "h22",
       SUM(DECODE(TO_CHAR(first_time, 'hh24'), '23', 1, 0)) "h23",
       ROUND(COUNT(1) / 24, 2) "Avg"
  FROM v$log_history
 GROUP BY TRUNC(first_time), TO_CHAR(first_time, 'Dy')
 ORDER BY 1;

小实验

redolog

默认情况下 redo 有三组,每组有一个 logfile,我们可以新增多个 redofile。

#查看日志文件的位置
select member from v$logfile;
#新增日志文件
alter database add logfile member 'xxxxxxxxxxxxx/redo01.rdo' to group 1;
#删除组内的日志文件,至少要有一个日志文件。
#注意的是,删除只是更新控制文件,并不涉及物理操作,该文件还在磁盘上。
#该组若处于CURRENT状态,则不允许删除。
alter database drop logfile member 'xxxxxxxxxxxxx/redo01.rdo';

redo 组

#新增redo组
ALTER DATABASE ADD LOGFILE GROUP 4 (filelog位置) SIZE 300M;
#删除redo组
ALTER DATABASE DROP LOGFILE GROUP 4;