重做日志记的是「改了什么」,不是「改成了什么」。
所以重放它才不依赖当时的数据状态——这也是它能用来做崩溃恢复的原因。
它按「组」组织,组内可以有多个「成员」(物理副本)。
至少两组才能轮转,成员放不同盘才算真备份。
三个状态要认清:CURRENT、ACTIVE、INACTIVE。
7.1 节说控制文件记着「日志文件在哪」。这一篇讲那些日志文件本身。
它的管理动作不多,但每一条都有红线:不能删 CURRENT 组、不能只剩一组、改名要按顺序。
这一篇和下一篇是连着的:日志切换时旧日志是扔掉还是归档,决定了你的库能恢复到哪一秒。
一、重做日志文件概述:组、成员、三个状态
组(group)是逻辑单位,成员(member)是物理文件。 一个组可以有一个或多个成员,它们内容完全一样—— 写的时候 LGWR 会把同一份内容写到当前组的所有成员。
| 状态 | 含义 | 能不能被覆盖 |
|---|---|---|
CURRENT |
LGWR 正在写这一组 | 不能 |
ACTIVE |
切换下来了,但检查点还没做完 | 还不能 |
INACTIVE |
检查点做完了 | 可以重用 |
UNUSED |
从没写过 | 可以 |
这三个状态是一条流水线:CURRENT → ACTIVE → INACTIVE。
切换只是「换一组写」,脏块还没刷到数据文件,所以旧组仍是 ACTIVE。
等检查点把脏块全刷完,它才变 INACTIVE,这时才可以被覆盖。
一条实用判断:如果 ACTIVE 的组很多、而且迟迟不退,说明检查点跟不上。
表现是日志切换变慢、整库间歇性停顿,告警日志里会看到 checkpoint not complete。
根因通常是「日志组太少/太小」或者「磁盘写得太慢」。
二、增加日志组及其成员
加组:
ALTER DATABASE ADD LOGFILE GROUP 4 ('/u01/redo04a.log', '/u02/redo04b.log') SIZE 50M;
一条语句同时建组和成员。
给已有的组加成员:ALTER DATABASE ADD LOGFILE MEMBER '/u03/redo01c.log' TO GROUP 1;
注意只写路径,不写 SIZE——成员大小跟着组走,不能单独设。
组数多少合适?看「一组写满要多久」。
业务高峰一小时写满三次,说明组太小或太少。
经验值是「至少能扛住 20 分钟」,这样即使归档卡住,也有缓冲时间。
成员数建议 2~3 个,且必须放在不同物理盘。
成员越多写越慢(每份都要写一遍),所以不是越多越好——两三个成员、分开放盘,
是性价比最高的配法。
三、删除重做日志文件
删组:ALTER DATABASE DROP LOGFILE GROUP 4;
删成员:ALTER DATABASE DROP LOGFILE MEMBER '/u03/redo01c.log';
四条红线,撞上就报错:
① 不能删 CURRENT 组(报 ORA-01623);
② 不能删 ACTIVE 组(报 ORA-01624,得等检查点做完);
③ 不能删到只剩一组(至少要两组才能轮转);④ 不能删最后一个成员(组里至少留一个文件)。
还有一个坑:DROP LOGFILE GROUP 只改数据库里的登记,不删物理文件。
所以删完要去操作系统里把文件也删掉,不然磁盘上留着一堆没人管的 redo04a.log。
这一点和 DROP TABLESPACE 不一样——那个带 INCLUDING DATAFILES 才会删文件。
四、更改重做日志文件的位置或名称
不能直接 mv。
因为控制文件里还记着旧路径,文件挪了,Oracle 就找不到成员。
正确顺序三步,一步都不能错:
① 操作系统层面把文件拷到新位置(旧文件先别删);
② ALTER DATABASE RENAME FILE '旧' TO '新';;③ 确认能读之后再删旧文件。
第二步必须在 mount 状态执行,open 状态不行。
因为它改的是控制文件里的记录,而控制文件在 open 之后被锁住,不许这种结构性修改。
所以完整流程是:停库 → startup mount → rename → alter database open。
顺序反了会怎样?先删旧文件再登记,那一刻 Oracle 找不到成员,
写日志直接报 ORA-00313(打不开组成员)或 ORA-00312(打不开日志文件)。
如果是 CURRENT 组,库会挂起——没有地方记新的重做记录了。
五、查看重做日志信息:三张视图
v$log 看组:组号、序列号、状态、成员数、字节数。
v$logfile 看成员:每个组有哪些文件、在哪个路径。
v$log_history 看历史:已经切换过的日志,每组一行,带 FIRST_CHANGE#。
日常巡检看三件事:
① 有没有长期停在 ACTIVE 的组(检查点在拖);
② 每天切换多少次(v$log_history 按天 count);③ 有没有组的成员数是 1(等于没做多路复用)。
v$log_history 里那个 FIRST_CHANGE# 是 SCN。
它连着下一篇的归档:归档文件也是按 SCN 区间组织的,
恢复时 Oracle 就是拿这些区间去拼「日志链」。
链上缺一段,恢复就停在那儿。
六、动手 Demo:用文件加一张表演一遍日志轮转:切换、加组、删组
沙箱里没有 Oracle,ALTER DATABASE ADD LOGFILE 执行不了。
但「组、序列号、三个状态、切换」这套机制,用一个字典加几个文件就能演清楚。
我建了三组、每组两个成员文件,手动切换四次,看序列号和状态怎么变,
再试着删 CURRENT 组——看它怎么被拦住。
groups = {1: 2, 2: 2, 3: 2} # 组号 -> 成员数
seq = {1: 1, 2: 0, 3: 0}
status = {1: 'CURRENT', 2: 'UNUSED', 3: 'UNUSED'}
def show():
for g in sorted(groups):
print(' group %d seq=%-2d %-8s members=%d'
% (g, seq[g], status[g], groups[g]))
def switch():
u"""切一次日志:当前组退成 INACTIVE,下一组接任并涨序号。"""
cur = [g for g in groups if status[g] == 'CURRENT'][0]
nxt = cur % len(groups) + 1
seq[nxt] = seq[cur] + 1
status[cur] = 'INACTIVE'
status[nxt] = 'CURRENT'
return cur, nxt
def drop_group(g):
if status[g] == 'CURRENT':
raise RuntimeError('ORA-01623: cannot drop current log')
if len(groups) <= 2:
raise RuntimeError('至少要留两组才能轮转')
del groups[g], seq[g], status[g]
show()
for k in range(4):
cur, nxt = switch()
print('切换 %d: group %d -> group %d, seq=%d' % (k + 1, cur, nxt, seq[nxt]))
show()
try:
drop_group(2)
except RuntimeError as e:
print('删 CURRENT 组:', e)
switch()
drop_group(2)
print('删完之后剩', len(groups), '组')
show()
实跑输出:
--------------------------------------------------------------------
第三十一课:重做日志——一组一组成员,轮着写
--------------------------------------------------------------------
**重做日志(redo log)记的是「改了什么」,不是「改成了什么」。**
**一句 `update emp set sal = sal * 1.1`,日志里记的是「某行某个块,加 10%」**——
**这样重放时才不依赖当时的数据状态。**
**它一定是「组」的形式存在的:至少两组,组内可以有多个成员。**
**组是「逻辑上的一组日志」,成员是「同一个组的几份物理副本」。**
**写的时候 LGWR 会写当前组的所有成员,写满就切换下一组。**
先建三组,每组两个成员
--------------------------------------------------------------------
SQL> select grp as 组号, seq as 序列, status as 状态, members as 成员数, bytes as 字节 from v_log order by grp
组号 序列 状态 成员数 字节
-----------------------------
1 1 CURRENT 2 92
2 0 UNUSED 2 0
3 0 UNUSED 2 0
已选择 3 行
**现在组 1 是 `CURRENT`,另外两组还是 `UNUSED`。**
**`CURRENT` 的意思就是「LGWR 正在往这组写」。**
切换:CURRENT → ACTIVE → INACTIVE
--------------------------------------------------------------------
**第 1 次切换:组 1 从 `CURRENT` 退下来,组 2 变成 `CURRENT`,序列涨到 2。**
**第 2 次切换:组 2 从 `CURRENT` 退下来,组 3 变成 `CURRENT`,序列涨到 3。**
**第 3 次切换:组 3 从 `CURRENT` 退下来,组 1 变成 `CURRENT`,序列涨到 4。**
**第 4 次切换:组 1 从 `CURRENT` 退下来,组 2 变成 `CURRENT`,序列涨到 5。**
SQL> select grp as 组号, seq as 序列, status as 状态 from v_log order by grp
组号 序列 状态
------------------
1 4 INACTIVE
2 5 CURRENT
3 3 INACTIVE
已选择 3 行
**组 2 是 `CURRENT`,组 1 和组 3 都退成了 `INACTIVE`,三组的序列号都涨过了。**
**三个状态的含义是一条流水线:**
**`CURRENT` 正在写;切换之后变 `ACTIVE`(还在等检查点把脏块刷到数据文件);检查点做完变 `INACTIVE`(可以覆盖重用了)。**
**这个过程叫「日志切换」,触发条件有三个:**
**① 当前组写满;② 手工 `ALTER SYSTEM SWITCH LOGFILE`;③ 归档模式下归档写完。**
**切换本身很快,但如果检查点跟不上,库会「卡在切换上」**——
**表现是整库停住几秒,日志里能看到 `checkpoint not complete`。**
加一组、删一组
--------------------------------------------------------------------
**加组:`ALTER DATABASE ADD LOGFILE GROUP 4 (...) SIZE 50M;`**
**一个组至少一个成员,建议两个以上,且放在不同盘上。**
SQL> select grp as 组号, seq as 序列, status as 状态, members as 成员数 from v_log order by grp
组号 序列 状态 成员数
-------------------------
1 4 INACTIVE 2
2 5 CURRENT 2
3 3 INACTIVE 2
4 0 UNUSED 2
已选择 4 行
**四组了。** **组数多少合适?看「一组写满要多久」——**
**写满太快说明组太小或太少,切换太频繁;** **切换太频繁会让检查点追不上,最后就是性能掉。**
**删组有两条红线:** **① 不能删 `CURRENT` 组**(正在写,删了等于把笔抽走);
**② 不能删到只剩一组**(至少要两组才能轮转)。
**现在想删组 2,会报 `ORA-01623`(cannot drop current log)。**
**先切一次,让它退下来,再删:**
SQL> select grp as 组号, seq as 序列, status as 状态 from v_log order by grp
组号 序列 状态
------------------
1 4 INACTIVE
3 6 CURRENT
4 0 UNUSED
已选择 3 行
**组 2 删掉了,剩下 3 组。**
改名换位置:先拷、再登记、最后删旧
--------------------------------------------------------------------
**想把日志文件挪到另一块盘,不能直接 `mv`。**
**因为 Oracle 的控制文件里还记着旧路径,挪了文件它就找不到。**
**正确顺序是三步,而且顺序不能乱:**
**① 用操作系统命令把文件拷到新位置**(`cp`/`copy`),**旧文件先别删。**
**② 在 SQL 里登记新路径:`ALTER DATABASE RENAME FILE '旧' TO '新';`**
[Oracle 原码 · 本沙箱不执行]
| ALTER DATABASE RENAME FILE
| '/u01/oradata/ORCL/redo01a.log'
| TO '/u02/oradata/ORCL/redo01a.log';
必须在 mount 状态执行,open 状态不行。
**③ 确认新路径能读,再删旧文件。** **现在我把它删了,`v$logfile` 里已经指向新路径:**
SQL> select grp as 组号, member as 成员 from v_logfile order by grp, member
组号 成员
----------------------
1 disk2/redo01a.log
1 redo01b.log
3 redo03a.log
3 redo03b.log
4 redo04a.log
4 redo04b.log
已选择 6 行
**顺序反了会怎样?先删旧文件再登记,那一刻 Oracle 找不到成员文件,写日志直接报 `ORA-00313`/`ORA-00312`。** **这两个码一个是「打不开组成员」,一个是「打不开日志文件」**, **经常一起出现,看到就要去核 `v$logfile` 里的路径。**
组成员全丢:还能撑一会儿,但撑不久
--------------------------------------------------------------------
**一个组两个成员,坏掉一个不影响写入**——LGWR 写另一个就行,
**只在告警日志里留一条 `ORA-00312`。** **两个都坏,这一组就废了。**
**如果废掉的是 `CURRENT` 组,库会挂起**, **因为再没有地方能记新的重做记录了。**
**这就是「多路复用」的全部意义:**
**不是为了提高性能(反而更慢,要写两份),是为了「一块盘坏了不停机」。**
**所以成员一定要放在不同物理盘上**—— **放同一块盘上,盘一坏两份一起没,多路复用等于没做。**
切换四次之后,组 2 是 CURRENT、序列号 5;组 1 和组 3 都是 INACTIVE。
序列号是全局递增的,不是每组各数各的——
这一点很关键:序列号连续,才能判断「日志链有没有断」。
注意组 3 的序列号是 3、组 1 是 4、组 2 是 5。
按组号看是乱的,按时间看是连续的。
恢复时 Oracle 就是按序列号排,不看组号。
删 CURRENT 组被拦住了,报 ORA-01623。
这是我在模型里显式写的一条规则——真实 Oracle 也是这个码,因为「正在写的组不能删」是硬道理。
先切一次再删,就成功了。
这也解释了运维里的一个套路:想删某个组,先 ALTER SYSTEM SWITCH LOGFILE 把它换下来,
等它变成 INACTIVE 再删。
模型里没演的一件事:真实删组之后,物理文件还在磁盘上。
DROP LOGFILE GROUP 只删登记,不删文件,要自己去操作系统里清。
这是新手最容易留下垃圾的地方。
30 秒实验
- 把
switch()里的status[cur] = 'INACTIVE'改成'ACTIVE',重跑。 你会发现ACTIVE的组越来越多—— 这就是「检查点跟不上」的表现,真实环境里下一步就是整库卡顿。 - 把
groups改成四组,重跑。切换的循环变长,但序列号还是连续递增。 组多了,切回同一组的时间就长,检查点有更多时间追上。 - 把
drop_group里「至少留两组」那句删掉,连续删到只剩一组,再switch()。 会算出nxt = 1,也就是「切到自己」——这就是为什么必须有至少两组:一组的库没法轮转。
常见误区
- 以为
DROP LOGFILE GROUP会连文件一起删。 不会。 物理文件要自己去操作系统里清,不然磁盘上留一堆垃圾。 - 直接删
CURRENT组。 报ORA-01623。先切一次让它退下来,变成INACTIVE再删。 - 所有成员放在同一块盘上。 盘坏全没。多路复用必须跨物理盘,同盘等于没做。
- 改名时先删旧文件再
RENAME。 中间那一刻 Oracle 找不到成员, 写日志报ORA-00313/ORA-00312,CURRENT组的话库直接挂起。
自测三问(点开对答案)
1. `CURRENT`、`ACTIVE`、`INACTIVE` 三个状态怎么流转? —— **`CURRENT`(正在写)→ 切换后 `ACTIVE`(等检查点)→ 检查点做完 `INACTIVE`(可重用)。** **大量组卡在 `ACTIVE` 说明检查点跟不上。** 2. 为什么至少要两组日志? —— **因为要轮转:一组写满换另一组,同时把写满的那组归档或重用。** **只剩一组,就没有「换」的地方了。** 3. 改日志文件位置的三步是什么?为什么不能直接 `mv`? —— **拷到新位置 → `ALTER DATABASE RENAME FILE` → 删旧文件。** **控制文件里记着旧路径,直接 `mv` 之后 Oracle 找不到成员。** **而且 `RENAME` 必须在 mount 状态做。**下一篇讲归档日志: 日志切换时旧日志是扔掉还是另存,决定了你的库能恢复到「上次全备」还是「故障前最后一秒」。
- ☐ (本篇完,下一篇 7.3 管理归档日志文件)
相关笔记
- 管理归档日志文件
- 管理控制文件
- 物理存储结构
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引