🏺 第7章 管理控制文件和日志文件
📄 一句话概括

日志模式只有两种:NOARCHIVELOG 和 ARCHIVELOG。 差别一句话:日志切换时,旧日志是扔掉还是另存一份。 扔掉就只能回到上次全备,另存就能前滚到故障前最后一秒。 这就是为什么生产库必须开归档——不是为了性能,是为了「丢得起多少数据」。

📝 本章和前面几章的关系

7.2 节讲到日志会「切换」。切换之后那组日志要么被覆盖重用,要么被归档留档。 这一篇讲后一种,也是生产库唯一该选的那种。 这一篇和备份恢复(第 16 章)是同一个话题的两半: 这篇讲「日志怎么留下来」,那一章讲「怎么用它恢复」。 没有归档,第 16 章的不完全恢复根本做不了。

一、日志模式分类:两种,选一种

模式 切换时旧日志 能恢复到
NOARCHIVELOG 直接覆盖重用 上一次全备的点
ARCHIVELOG 先归档再重用 故障前最后一秒

默认是 NOARCHIVELOG。 测试库、练手库用它没问题,反正数据不重要。 生产库开 NOARCHIVELOG 等于「随时准备丢一天的数据」—— 这不是技术水平问题,是能不能接受的业务问题。 开归档的完整步骤四句,一句都不能省: SHUTDOWN IMMEDIATE → STARTUP MOUNT → ALTER DATABASE ARCHIVELOG; → ALTER DATABASE OPEN; 必须在 mount 状态改,open 状态改不了。 开归档之后有两件事跟着变: ① 切日志时会等归档写完(ARCn 进程干活),所以切换变慢一点; ② 归档目录会一直长,必须配清理。 第二件是最容易出事的地方——归档写不进去,库会挂起,表现是「所有 DML 都卡住」。

二、管理归档操作:自动与手工

平时是自动的:日志一满、一切换,ARCn 进程就把刚写满的那组拷到归档目录。 开了归档却忘了配归档目录,日志会停在 ACTIVE 不消失,最后把日志空间占满,库挂起。 手工归档三句,用途不同:

命令 干什么
ALTER SYSTEM ARCHIVE LOG CURRENT; 切一次日志并立即归档(最常用)
ALTER SYSTEM ARCHIVE LOG ALL; 把所有未归档的组都归档掉
ALTER SYSTEM ARCHIVE LOG GROUP 3; 只归档指定的组

ARCHIVE LOG CURRENT 在运维里非常常用——因为它能「立刻把当前日志固化下来」, 做大动作之前先跑一句,等于多了一层保险。 比如改结构、做全备之前,都值得先归档一次。 还有一个开关: LOG_ARCHIVE_START(老版本)或者 ARCn 进程数量(LOG_ARCHIVE_MAX_PROCESSES)。 归档跟不上时,加 ARCn 进程数能缓解,但根因往往是磁盘慢,加进程只是缓解症状。

三、设置归档文件位置:两种放法

第一种是显式指定目录:LOG_ARCHIVE_DEST_1 = 'LOCATION=/u02/arch'。 配 LOG_ARCHIVE_FORMAT = 'arch_%t_%s_%r.arc' 决定文件名。 %t 线程号、%s 序列号、%r incarnation 号。 第二种是扔进快速恢复区(FRA):DB_RECOVERY_FILE_DEST。 归档、RMAN 备份、闪回日志全放这儿,由一个参数统一管空间。 好处是省心,坏处是「一个区满了所有东西一起满」。 生产上常见做法是:归档单独指目录,备份进 FRA,两件事分开管。 %r 那个 incarnation 号很多人不知道是什么。 每次 ALTER DATABASE OPEN RESETLOGS 都会让 incarnation 涨 1, 归档文件名里带上它,就能区分「重置日志之前的旧归档」和「之后的」。 不带这个字符,两次 incarnation 的同序号归档会撞名,恢复时可能拿错文件。

四、查看归档日志信息:四张视图

v$database 的 LOG_MODE 看当前模式;v$archive_dest 看归档目标与状态; v$archived_log 看已归档的文件清单;v$archive_processes 看 ARCn 进程。 SQL*Plus 里还有一句最省事的:ARCHIVE LOG LIST。 一行就告诉你:模式、是否自动归档、归档目录、最老的日志序列号。 日常巡检第一句就跑它。 v$archived_log 里两个字段最重要:SEQUENCE# 和 FIRST_CHANGE#。 前者判断「链有没有断」——序列号必须连续;后者判断「该从哪个归档开始重放」。 归档缺一个会怎样? 恢复到那儿就停,报 ORA-00308(打不开归档日志)或 ORA-10879(归档日志找不到)。 这种时候只能做不完全恢复,恢复到缺失点之前的那个时刻—— 也就是「主动放弃之后的改动」,总比开不了库强。

五、动手 Demo:演一遍:不开归档丢两行,开了归档两行全回来

沙箱里没有 Oracle,ALTER DATABASE ARCHIVELOG 执行不了。 但「归档到底救回了什么」这件事,可以用 sqlite 文件加文本日志演得非常实。 我用一个 sqlite 文件当数据文件、一份文本当重做日志, 走两遍同样的事务:一遍不开归档(切换即丢),一遍开归档(切换就留副本),然后各崩一次, 看能恢复出几行。

import os, shutil, sqlite3

DBF, BAK, ARC = 'data.dbf', 'bak.dbf', 'arch'
os.makedirs(ARC, exist_ok=True)
for p in (DBF, BAK):
    if os.path.exists(p):
        os.remove(p)

db = sqlite3.connect(DBF)
db.execute('CREATE TABLE emp (empno INT, ename TEXT)')
db.executemany('INSERT INTO emp VALUES (?,?)', [(1, 'SMITH'), (2, 'JONES'), (3, 'SCOTT')])
db.commit()
print('全备前:', db.execute('select count(*) from emp').fetchone()[0], '行')
db.close()
shutil.copyfile(DBF, BAK)          # 全备

def tx(n):
    db = sqlite3.connect(DBF)
    db.execute('INSERT INTO emp VALUES (?,?)', (n, 'NEW%d' % n))
    db.commit()
    db.close()

tx(4)
tx(5)
db = sqlite3.connect(DBF)
print('两笔事务后:', db.execute('select count(*) from emp').fetchone()[0], '行')
db.close()

# 归档模式下:每切一次日志,先把当前日志留一份再清空
redo = 'redo01.log'
def arch_switch(n):
    shutil.copyfile(redo, os.path.join(ARC, 'arch_1_%d.arc' % n))
    open(redo, 'w').close()

open(redo, 'w').write('INSERT INTO emp VALUES (4, \'NEW4\');\n')
arch_switch(1)
open(redo, 'w').write('INSERT INTO emp VALUES (5, \'NEW5\');\n')
arch_switch(2)
print('归档文件:', sorted(os.listdir(ARC)))

# 故障:数据文件被旧备份覆盖,然后按序重放归档
shutil.copyfile(BAK, DBF)
db = sqlite3.connect(DBF)
print('还原后:', db.execute('select count(*) from emp').fetchone()[0], '行')
db.close()

for name in sorted(os.listdir(ARC)):
    db = sqlite3.connect(DBF)
    db.executescript(open(os.path.join(ARC, name)).read())
    db.commit()
    db.close()

db = sqlite3.connect(DBF)
print('前滚后:', db.execute('select count(*) from emp').fetchone()[0], '行')
db.close()

实跑输出:

--------------------------------------------------------------------
第三十二课:归档日志——为什么生产库必须开归档
--------------------------------------------------------------------

  **日志模式只有两种:`NOARCHIVELOG` 和 `ARCHIVELOG`。**
  **差别只有一句话:日志切换时,旧的日志是「扔掉」还是「另存一份」。**
  **扔掉,就只能恢复到「上一次全备」那个点**——中间的改动全丢。
  **另存,就能一路前滚到「故障前最后一秒」。**  **这就是为什么生产库必须开归档。**

  先做一次全备,再跑两次事务
  --------------------------------------------------------------------

  SQL> select count(*) as 行数 from emp
  行数
  ----
  3
  已选择 1 行
  **全备做完了:`data.dbf` 被整个拷成 `data_backup.dbf`。**
  **注意全备不备份「日志」,只备份数据文件**——
  **日志是另一种备份,两样凑一起才叫「能恢复到任意时刻」。**
  **事务一:插入 JAMES。**
  **事务二:插入 MILLER。**

  SQL> select empno, ename, sal from emp order by empno
  EMPNO ENAME  SAL
  -----------------
  7369  SMITH  800
  7566  JONES  2975
  7788  SCOTT  3000
  7900  JAMES  950
  7934  MILLER 1300
  已选择 5 行
  **现在表里 5 行,重做日志里有 2 条记录。**  **备份还是那个 3 行的旧备份。**

  NOARCHIVELOG:切换日志等于把重做记录扔掉
  --------------------------------------------------------------------
  **假设这时候切了一次日志。**  **`NOARCHIVELOG` 模式下,切走的那个日志文件会被直接覆盖重用**——
  **里面那 2 条记录,从此不存在了。**
  **重做日志现在是 0 字节——空的。**
  **这时候磁盘坏了,只能用全备还原:**

  SQL> select count(*) as 恢复后行数 from emp
  恢复后行数
  ----------
  3
  已选择 1 行
  **3 行。丢了两行。**  **这就是 `NOARCHIVELOG` 的代价:只能回到上次全备,中间全丢。**
  **如果是昨天做的全备,今天就丢了整整一天的交易。**

  开归档:ALTER DATABASE ARCHIVELOG
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | SHUTDOWN IMMEDIATE
  | STARTUP MOUNT
  | ALTER DATABASE ARCHIVELOG;
  | ALTER DATABASE OPEN;
    必须停库到 mount 状态才能改,open 状态改不了。

  **开归档不是「设个参数」,是「改库的模式」**,所以要停库、mount、改、再 open。
  **改完之后每一组日志被切走之前,ARCn 进程会先把它拷到归档目录。**
  **归档目录由 `LOG_ARCHIVE_DEST_1` 决定,格式由 `LOG_ARCHIVE_FORMAT` 决定。**
  **常见格式是 `arch_%t_%s_%r.arc`:**
  **`%t` 线程号、`%s` 序列号、`%r` 是 incarnation 号(`RESETLOGS` 之后会变)。**

  再做一遍:这次能恢复到故障前
  --------------------------------------------------------------------
  **同样的起点:全备 3 行,然后开归档。**
  **事务一之后切一次日志,归档出了 `arch_1_1_1098765432.arc`。**
  **事务二之后再切一次,归档出了 `arch_1_2_1098765432.arc`。**
  **两条记录都在归档里,日志文件被清空重用也没关系。**

  故障恢复:还原 + 前滚
  --------------------------------------------------------------------
  **现在模拟磁盘坏了:数据文件被旧备份覆盖。**
  **这一步对应 `RESTORE DATABASE`——把数据文件放回去。**

  SQL> select count(*) as 还原后行数 from emp
  还原后行数
  ----------
  3
  已选择 1 行
  **3 行。备份之后的两笔事务还没回来。**
  **接着做 `RECOVER DATABASE`:按序列号顺序重放归档日志。**
  **重放的不是「数据快照」,是「当时那条 SQL」**——  **所以叫「重做」(redo),不叫「复制」。**
  **重放 `arch_1_1_1098765432.arc`**
  **重放 `arch_1_2_1098765432.arc`**

  SQL> select empno, ename, sal from emp order by empno
  EMPNO ENAME  SAL
  -----------------
  7369  SMITH  800
  7566  JONES  2975
  7788  SCOTT  3000
  7900  JAMES  950
  7934  MILLER 1300
  已选择 5 行
  **5 行回来了。**  **两笔「切换日志之前」的事务,靠归档日志补回来了**——  **这就是开归档的意义。**
  **注意还有个尾巴:最后一笔事务如果「还没切换日志」就崩了,它还在当前日志里。**
  **这种叫「实例恢复」,Oracle 开库时自动做,不用你操心。**
  **`RECOVER DATABASE` 管的是「归档 + 备份」,实例恢复管的是「当前日志」。**

  查归档信息
  --------------------------------------------------------------------

  SQL> select dest_id as 序号, status as 状态, target as 目标, archiver as 进程 from v_archive_dest
  序号 状态     目标                      进程
  --------------------------------------------
  1    VALID    USE_DB_RECOVERY_FILE_DEST ARCH
  2    INACTIVE /u02/arch2                ARCH
  已选择 2 行

  SQL> select seq as 序列, name as 归档文件, bytes as 字节, first_change as 起始SCN, next_change as 结束SCN from v_archived_log
  序列 归档文件                字节 起始SCN 结束SCN
  -------------------------------------------------
  1    arch_1_1_1098765432.arc 45   1100    1199
  2    arch_1_2_1098765432.arc 47   1200    1299
  已选择 2 行
  **归档目录现在 2 个文件、一共 92 字节。**
  **92 字节是模型文件的大小,真实归档一个就有几十 MB。**
  **而这个东西会一直长——生产库一天几十 GB 很常见。**
  **所以归档必须配两样东西:**  **① 定时备份归档并删除**(`RMAN` 的 `DELETE ARCHIVELOG`);
  **② 空间监控**——归档写不进去,库会直接挂起,`ARCH` 进程卡死。
  **`v$archived_log` 里的 `FIRST_CHANGE#` 和 `NEXT_CHANGE#` 是 SCN 区间。**
  **恢复时 Oracle 就是靠这个区间判断「这个归档该不该重放」**——
  **序列号连续、SCN 不断档,才叫「日志链完整」。**
  **中间缺一个归档,恢复就卡在那儿,报 `ORA-00308`/`ORA-10879`。**

先看第一段:全备 3 行,跑两笔事务变 5 行。 然后「切日志」——不开归档时那个动作就是清空重用,两条重做记录从此不存在。 崩溃后用全备还原,只剩 3 行。 这两行不是「丢了找不回来」,是「根本没有留下任何能找回的线索」—— 重做记录被覆盖了,谁都变不出来。 第二段开了归档,同样是 3 行起步、两笔事务、两次切换。 区别只有一个:每次切换前先把当前日志拷成 arch_1_N.arc。 归档目录里现在有 2 个文件、一共 92 字节。 92 字节是模型文件大小,真实归档一个几十 MB。 恢复时先还原(回到 3 行),再按序列号顺序重放 2 个归档,回到 5 行。 注意重放的是「当时那条 SQL」,不是「数据快照」——这就是 redo 和「复制文件」的本质区别。 最后两行要说清一件事:RECOVER DATABASE 管的是「归档 + 备份」, 而「最后一笔还没切日志的事务」由实例恢复负责,开库时自动做。 两者合起来,才能做到「故障前最后一秒都不丢」。

30 秒实验

  1. 把归档模式那段注释掉(不生成 arch_1_N.arc),重跑。 最后一行会从 5 变成 3——这就是「不开归档」和「开归档」的全部差别。
  2. 把 arch_switch 里 shutil.copyfile 之后那句 open(redo,'w').close() 删掉,重跑。 归档文件会变成两条 SQL 都写进去(因为日志没清空), 重放时第二笔事务会被执行两次—— 真实 Oracle 里这不是「重复插入」,而是「重做记录被重放到一致状态」,幂等。 这个差别说明:模型能演顺序,演不了幂等。
  3. 把 tx(4)、tx(5) 改成三笔事务,只切换两次,重跑。 第三笔在「还没切日志」时崩,还原后只补回前两笔—— 剩下那笔要靠实例恢复(真实 Oracle 开库时自动做)。

常见误区

  1. 以为「做了备份」就等于「能恢复到任意时刻」。 不能。没有归档,只能回到上次全备。 备份 + 归档 + 日志,三样齐了才行。
  2. 生产库开 NOARCHIVELOG 图省事。 省的是归档空间,丢的是一整个备份周期内的全部交易。
  3. 开了归档不管归档空间。 归档写不进去, 库会挂起——表现是所有 DML 都卡住,很难第一眼想到是归档盘满了。
  4. 以为归档文件名里 %r 无所谓。 不带它,两次 incarnation 的同序号归档会撞名, 恢复时可能拿到上一次 RESETLOGS 之前的旧文件。
自测三问(点开对答案) 1. `NOARCHIVELOG` 和 `ARCHIVELOG` 最本质的区别是什么? —— **日志切换时,旧的日志是「覆盖重用」还是「先归档再重用」。** **前者只能恢复到上次全备,后者能前滚到故障前最后一秒。** 2. 开归档需要几步?为什么不能直接 `ALTER DATABASE`? —— **停库 → `STARTUP MOUNT` → `ALTER DATABASE ARCHIVELOG` → `OPEN`。** **因为它改的是库的模式,不是参数,必须在 mount 状态做。** 3. 归档日志序列号断了会怎样? —— **恢复停在那儿,报 `ORA-00308`/`ORA-10879`。** **只能做不完全恢复,恢复到缺失点之前**——**主动放弃后面的改动,总比开不了库强。**

第 7 章到此为止。控制文件、日志文件、归档日志——库的「可恢复性」三件套齐了。 下一篇进第 8 章:表空间和数据文件。 话题从「库的外壳」转到「数据到底存在哪」。

相关笔记