上一篇文章写了undo(文章链接:聊聊UNDO),这篇和大家一起聊聊redo。redo如果按照我的傻瓜翻译,意为再次去做、重新去做。Oracle官方对于redo的描述是:记录对数据所做的所有更改,包括未提交和已提交的更改;通过REDO来保证数据库的事务可以被重演,从而使得在故障之后,数据可以被恢复。
事务重演,这个名词稍微抽象一点点;我个人在抽象事务理解上比较愚笨,最开始在学习的时候花了不少功夫才理解这个名词。所以也想在这里举个例子和大家分享下我的拙见:
2024-04-18 00:00:00,我启动了Oracle数据库实例ORCL;
从此刻开始,ORCL对应的应用系统开始正常运行,开始向ORCL中增删改查数据;
2024-04-18 18:05:20,ORCL所在服务器突然意外关机了;
在关机的此时此刻有个INSERT操作,已经提交,但数据还未来得及写入磁盘;
2024-04-18 18:13:14,服务器恢复,开始启动ORCL数据库实例(此时的这个操作,其实应该叫做恢复ORCL实例);但是关机时提交的INSERT数据并没有写入磁盘,这不就造成了数据丢失嘛!Oracle多聪明,它此时会根据redo日志(online redo log)里的内容,将INSERT操作重新执行一遍,这就避免了此问题。
上面将INSERT操作重新执行一遍的过程,就是事务重演。
上面的例子,其实还有个专业的叫法:前滚;即已提交未写入的数据再次写入。
前滚是发生在Oracle实例恢复时的一个步骤。
那么正好在此处简单描述下实例恢复的过程:Oracle实例崩溃,进行实例恢复;① 先发生前滚,② 再打开数据库,③然后发生回滚。
以上是实例恢复的三个阶段,可以发现除了redo以外,undo也参与了实例恢复,因为发生了回滚。这也是在上一篇文章 《聊聊UNDO》中没有提到的UNDO的一个功能。
下面开始和大家分享下redo相关的一些基本知识内容,先看目录。
目录
redo相关组件
redo日志循环写
归档模式
非归档模式
redo日志状态
redo日志切换
redo日志操作
redo相关组件
在数据库中,redo的功能主要通过3个组件来实现:Redo Log Buffer、LGWR后台进程和Redo Log File。下面分别对这3个组件进行介绍。
- Redo Log Buffer
Redo Log Buffer位于SGA中,是一块循环使用的内存区域,用来保存数据库变更的相关信息。这些信息以Redo Entries(重做条目)形式存储。
Redo Entries包含重构、重做数据库变更的重要信息(包括INSERT、UPDATE、DELETE、CREATE、ALTER或者DROP等)。而这些Redo Entries内容是被Oracle数据库进程从PGA复制到SGA中的Redo Log Buffer中的;多提一句的是SQL的语义解析,语法解析,执行计划都是在PGA中进行的,所以我们也就能推测到Redo Entries的信息格式大概是什么了。
Redo Entries主要是scn、时间戳、sql_redo等信息,这些信息可以通过Oracle的LogMiner工具去分析查看(配合v$logmnr_contents视图)。其中sql_redo就是产生redo的具体sql内容。
- 后台进程LGWR
LGWR的作用是循环把Redo Log Buffer中的内容写出到Redo Log File中。当满足以下条件时,会触发LGWR进程:
- 事务提交时
- 每3秒钟
- 彻底关机前
- 在DBWN写入之前
- Redo Log Files
重做日志文件,以组(group)出现。可以通过v$logfile查询相关日志文件信息。
select * from v$logfile
在这里重做日志文件又可以分为:ONLINE REDO LOG(在线重做日志)和 ARCHIVE REDO LOG(归档重做日志)
ONLINE REDO LOG
Oracle以SQL脚本的形式实时记录数据库的数据更新,换句话说,按特定的格式实时保存已执行的SQL脚本到在线日志文件中,这个格式其实就是前文提到Redo Entries。
ARCHIVE REDO LOG
归档重做日志,简称归档日志,指当条件满足时,Oracle将在线重做日志以文件形式保存到硬盘(持久化)
从实际情况来看 ARCHIVE REDO LOG 其实就是 ONLINE REDO LOG 的备份。
redo日志循环写
那么ONLINE REDO LOG(在线重做日志)和 ARCHIVE REDO LOG(归档重做日志) 是如何参与Oracle的工作的呢?
先不急,我们再进一步探究下Redo Log File的细节信息:
Ⅰ:每个Oracle数据库都至少有2个Online重做日志组,每个组中至少有1个重做日志文件,这些Online重做日志组以循环方式使用。
Ⅱ:每组内的日志文件的内容完全相同,且保存在不同的位置,用于磁盘日志镜像,以做多次备份提高安全性。
Ⅲ: 我们是可以根据实际情况自定义添加、修改、删除日志组和日志文件的。
Ⅳ:在默认情况下,Oracle只有1个重做日志组处于活动状态;
数据库在开启归档模式状态下 和 非归档模式下的重做日志工作方式是不一样的,我们先来一起看下归档模式下的重做日志是如何工作的。
归档模式
假设Oracle数据库有3个日志组:1#、2#、3#。
① LGWR进程循环地向 1#日志组的 ONLINE REDO LOG 中写入特定格式的SQL脚本;
② 当1#日志组的 ONLINE REDO LOG 写满时,则将在线日志归档到硬盘,成为 ARCHIVE REDO LOG;
③ 当1#日志组的 ONLINE REDO LOG 写满后,Oracle切换到2#日志组,开始向ONLINE REDO LOG写入特定格式的SQL脚本;
④ 如果1#日志组的 ARCHIVE REDO LOG 已经写完(归档结束),且2# 日志组ONLINE REDO LOG已经写满,那么2#日志组开始归档
⑤ 2# 日志组ONLINE REDO LOG已经写满,则切换至 3# 日志组,开始向ONLINE REDO LOG写入特定格式的SQL脚本;
⑥ 如果2#日志组的 ARCHIVE REDO LOG 已经写完(归档结束),且3# 日志组ONLINE REDO LOG已经写满,那么3#日志组开始归档
⑦ 3# 日志组ONLINE REDO LOG已经写满,则重新切换至 1# 日志组,开始向ONLINE REDO LOG写入特定格式的SQL脚本;
⑧ 循环以上过程
如果我们细心一点,可以发现一个细节:如果服务器磁盘性能差,归档速度慢,那么重做日志组切换就会出现问题。比如上述循环过程中,3#日志组的ONLINE REDO LOG写满后,1#日志组还在归档,那么此时是不会切换到1#日志组的,只能使用日志缓冲区,等待归档完毕之后才能覆盖写入。
非归档模式
至于非归档模式状态下的redo日志循环写流程,就比较简单了,因为没有归档过程,如下图所示。还是假设有3个日志组:1#、2#、3#。只不过这次流程十分精简:
① LGWR进程循环地向 1#日志组的 ONLINE REDO LOG 中写入特定格式的SQL脚本;
② 1#日志组写满后,切换至2#日志组,LGWR进程循环地向 2#日志组的 ONLINE REDO LOG 中写入特定格式的SQL脚本;
③ 2#日志组写满后,切换至3#日志组,LGWR进程循环地向 3#日志组的 ONLINE REDO LOG 中写入特定格式的SQL脚本;
④ 3#日志组写满后,重新切换至1#日志组,LGWR进程循环地向 1#日志组的 ONLINE REDO LOG 中写入特定格式的SQL脚本;
⑤ 循环以上过程
redo日志状态
关于redo日志状态,需要分成两块去讲。一块是redo日志组的状态,一块是redo日志文件的状态。
- redo日志组状态
可以通过v$log视图查询redo日志组状态。
select group#,bytes,status from v$log;
SQL> select group#,bytes,status from v$log;
GROUP# BYTES STATUS
---------- ---------- ----------------
1 209715200 INACTIVE
2 209715200 CURRENT
3 209715200 INACTIVE
通过上述输出结果可以看到这里已经有两种状态为INACTIVE和CURRENT,那么现在把REDO LOG组所有的状态都列出来。并给出相应解释:
Ⅰ:INACTIVE
实例恢复不再需要联机重做日志组,它可能已经归档也可能未归档。
Ⅱ:ACTIVE
联机重做日志组是活动的,但是并非当前联机重做日志组,实例崩溃恢复需要该状态的日志,它可能用于块恢复,它可能已经归档也可能未归档。
Ⅲ:CURRENT
当前的联机重做日志组,这意味着该联机重做日志组是活动的。
Ⅳ:UNUSED
从未对联机重做日志组进行写入,这种状态的日志文件要么是刚增加的,要么是当日志不是current redo log时RESETLOGS操作后的状态。
Ⅴ:CLEARING
在ALTER DATABASE CLEAR LOGFILE 命令后正在将该日志重建为一个空日志,日志清除后其状态更改为UNUSED。
Ⅵ:CLEARING_CURRENT
正在清除当前日志文件中的已关闭线程,如果切换时发生某些故障,如写入新日志标题时的I/O错误,则该日志可以停留在该状态。
- redo日志文件状态
可以通过v$logfile视图查询redo日志文件状态。
select group#,member,status from v$logfile;
SQL> select group#,member,status from v$logfile;
GROUP# MEMBER STATUS
---------- ---------------------------------------- -------
3 /u01/app/oracle/oradata/ORCL/redo03.log
2 /u01/app/oracle/oradata/ORCL/redo02.log
1 /u01/app/oracle/oradata/ORCL/redo01.log
通过上述输出结果,可以看到这里STATUS(状态)列下并无数据,但实际上现在这个列中的值为NULL,那么我们把REDO LOG文件所有的状态都列出来。并给出相应解释:
Ⅰ:INVALID
该文件不可访问
Ⅱ:STALE
该文件内容不完全,例如正在添加一个日志文件成员
Ⅲ:DELETED
该文件已不再使用
Ⅳ:NULL
该文件正在使用中
redo日志切换
redo日志切换,其实指的是redo日志组切换。redo日志组可以用两种方法去切换。但推荐的是用正常方式去切换。
- 正常切换
alter system switch logfile;
切换例子如下
- 用发生检查点的方式去切换
alter system checkpoint;
redo日志操作
- 添加组成员
假如需要给1#日志组添加组成员;
先通过 select member from v$logfile;查询出对应redo日志文件路径。
再替换到下列语句中。
ALTER DATABASE
ADD LOGFILE MEMBER '/u01/app/oracle/oradata/ORCL/redo01.rdo'
TO GROUP 1
添加组成员有2个需要注意的地方:
Ⅰ:新增组成员不需要指定大小,因为每个组内的成员日志内容是一样的,所以大小和已存在的组成员一致。
Ⅱ:当组成员刚刚被添加时,它的日志文件状态是INVALID不可访问状态,如下图。
那咋办嘞?
这时候就用到上文叙述过的redo日志组切换操作,操作结果如下图所示。
- 删除组成员
删除语法很简单,如下所示,删除之前添加的1#日志组新成员redo01.rdo。
ALTER DATABASE DROP LOGFILE MEMBER '/u01/app/oracle/oradata/ORCL/redo01.rdo';
删除组成员有两个需要注意的地方:
Ⅰ:上述的删除语法只是在控制文件中,将日志组成员删除了;未真正在物理磁盘上将redo01.rdo文件删除;所以需要手动去对应路径下删除对应的redo01.rdo文件。
Ⅱ: 不能删除CURRENT状态下的日志组成员。
- 添加组
还是和添加组成员一样,先找到其他组成员的存储路径,再替换到下面的代码中。
新建的组,可以指定组成员默认大小是多少。如下代码,指定的是组成员大小是200M。
ALTER DATABASE ADD LOGFILE GROUP 4 ('/u01/app/oracle/oradata/ORCL/redo04.log')
SIZE 200M
添加组有2个需要注意的点:
Ⅰ:无法改变已存在的日志组成员大小;
Ⅱ:刚创建的日志组状态是UNUSED的,也可以进行手动切换日志组,进行状态的调整。
以上是关于redo的基本知识内容,按我自己的拙见是比较全面了。但redo与备份和恢复的关系没有提到太多。因为觉得内容太多,一章说完比较困难,后面再补吧。
另外也记录下一件小事:昨天,我对象发现了我头上长了一根白头发;这是我第一次长白头发,我真的要开始变老了,欸。