完全恢复就是恢复到「失败那一刻」,一行不丢。它要求三件事:
归档模式、日志完整、备份可用。少一样就只能退化成不完全恢复。
命令是两步:RESTORE 把文件铺回去,RECOVER 把日志应用上去。
NOARCHIVELOG 模式下只能做第一步——所以它救不回备份之后的数据。
16.3 讲怎么备。这一篇讲怎么恢复——备份的价值只在这一刻体现。 注意这一篇和下一篇的分工:完全恢复往前滚到底,不完全恢复停在某个点。前者治「文件坏了」, 后者治「数据被误改了」。
一、完全恢复的前提:三个都要满足
① 库在归档模式。没有归档日志,就没法「把备份之后的重做补上」,只能恢复到备份点。
② 日志完整。归档日志从备份点一直连续到失败点,中间不能缺。
缺一段就滚不过去,RECOVER 会报「找不到需要的归档日志」。
③ 备份可用。备份文件要能读、要包含需要的所有数据文件。
这就是 16.3 最后那段讲的「RESTORE ... VALIDATE」的意义。
三条里最容易出问题的是第 ②:日志连续性。常见断点有三种——归档目录满了没写下去、
有人手工删了旧归档、备份策略把归档一起删了。
所以归档目录要监控,DELETE ARCHIVELOG 之前要确认备份里有。
二、NOARCHIVELOG 模式下的恢复
这个模式下只能做「还原」,做不了「恢复」。因为重做日志一旦被覆盖,
就没法把备份之后的变化补回来。
标准步骤:SHUTDOWN ABORT、STARTUP MOUNT、RESTORE DATABASE、ALTER DATABASE OPEN。
注意最后没有 RECOVER,
也没有 RESETLOGS——因为还原出来的就是备份那一刻的一致状态,直接开库就行。
它的代价要算清楚:备份之后写的所有数据全丢。如果每天凌晨备一次、下午三点出事,
那就丢十五个小时。
这个损失能不能接受,是业务问题——所以 NOARCHIVELOG 一般只用在测试库、
或者「数据能重新导入」的库上。
三、ARCHIVELOG 模式下的恢复
标准步骤:RESTORE DATABASE、RECOVER DATABASE、ALTER DATABASE OPEN。
RECOVER 会自动去归档目录里找需要的日志,从备份的 SCN 一路应用到最新。
如果只是某个数据文件坏了,库还开着:ALTER DATABASE DATAFILE 4 OFFLINE、
RESTORE DATAFILE 4、RECOVER DATAFILE 4、ALTER DATABASE DATAFILE 4 ONLINE。
这四步可以在线做,不影响其他表空间——这是归档模式最大的好处之一。
RECOVER 结束的标志是看到「media recovery complete」。
如果它卡在某一句「找不到归档序列 1234」,
那就是日志断链——要么去备份里找那一段归档还原出来,要么只能改成不完全恢复。
还有一个细节:RECOVER DATABASE 默认会应用到「最新」,也就是连在线日志一起应用。
所以如果在线日志还在,不需要先归档再恢复——RMAN 会直接用。
四、常见故障与对应动作
| 故障 | 库的状态 | 怎么做 |
|---|---|---|
| 一个数据文件损坏 | 还开着 | 文件脱机,restore 加 recover,再联机 |
| 一个数据文件损坏 | 已关库 | mount,restore 加 recover,open |
| 系统表空间损坏 | 已关库 | 只能整库恢复,不能只恢复该文件 |
| 控制文件损坏 | 已关库 | 有备份就还原,没备份要重建 |
| 非当前在线日志损坏 | 还开着 | 用归档补,或清掉该组重建 |
| 当前在线日志损坏 | 已崩溃 | 只能不完全恢复,会丢数据 |
第二行和第一行的区别只是「库关没关」,命令几乎一样,但顺序不同。库开着的时候要先脱机——
不然文件正在被用,还原会失败。
第四行值得单独说:控制文件损坏是最麻烦的一种。有备份(BACKUP CURRENT CONTROLFILE)就简单,
还原回来再 RECOVER 就行。
没备份就要用 CREATE CONTROLFILE 重建——得把每个数据文件、每组日志的位置一个个写进去,
写错一个就起不来。这也是为什么要多路复用控制文件,配三个路径。
最后一行是硬伤:当前在线日志损坏,意味着「最新那一段重做」没了。只能恢复到上一个归档点,
中间的已提交事务全丢。所以在线日志要多路复用,而且要放在不同的物理盘上。
五、恢复不等于保留所有事务
这一节要纠正一个很常见的误解:完全恢复会把「崩溃时正在跑的事务」也保留下来。 不对。恢复重放的是【已提交】的改动,未提交的部分会被回滚掉。 机制是这样的:重做日志里既有「改」也有「回滚」的记录。 恢复时先把所有改动都重放一遍(包括未提交的),这叫「前滚」; 然后打开库,用 undo 把未提交的撤掉,这叫「回滚」。 所以恢复之后看到的,是「崩溃那一刻的已提交状态」。 这个区别在业务上很关键:如果崩溃时有个大事务正在插一百万行、还没提交, 恢复之后这一百万行是不存在的。 业务方如果问「我那批数据回来了吗」,答案是「没有,因为当时还没提交」。
六、动手 Demo:两个模式各演一遍:丢一半,和一行不丢
这一篇的 Demo 造一个真实的灾难:10 万行做完备份,之后又写五批、每批 1 万行, 然后主库文件没了。 然后分两路恢复:一路只有备份,一路备份加重做日志。看两条路各能救回多少行。
# 完全恢复:备份点之后又写了五批,文件坏了能救回多少
import os, shutil, sqlite3, tempfile
db = os.path.join(tempfile.gettempdir(), "t164.db")
bak = db + ".bak"
if os.path.exists(db):
os.remove(db)
c = sqlite3.connect(db)
c.execute("create table t (id int primary key)")
c.executemany("insert into t values (?)",
[(i,) for i in range(1, 100001)])
c.commit()
c.close()
shutil.copy(db, bak) # 备份点:10 万行
c = sqlite3.connect(db)
redo = []
for b in range(5): # 备份之后又写五批
rows = [(100000 + b * 10000 + i,) for i in range(1, 10001)]
c.executemany("insert into t values (?)", rows)
c.commit()
redo.append(rows)
n_now = c.execute("select count(*) from t").fetchone()[0]
c.close()
os.remove(db) # 灾难:主库文件没了
print("崩溃时主库 %d 行" % n_now)
shutil.copy(bak, db + ".noarch") # 只有备份
a = sqlite3.connect(db + ".noarch")
print("NOARCHIVELOG 恢复后:%d 行"
% a.execute("select count(*) from t").fetchone()[0])
a.close()
shutil.copy(bak, db + ".arch") # 备份 + 重做日志
a = sqlite3.connect(db + ".arch")
for rows in redo:
a.executemany("insert into t values (?)", rows)
a.commit()
print("ARCHIVELOG 恢复后:%d 行"
% a.execute("select count(*) from t").fetchone()[0])
a.close()
实跑输出:
--------------------------------------------------------------------
第七十三课:完全恢复——NOARCHIVELOG 丢一半,ARCHIVELOG 一行不丢
--------------------------------------------------------------------
**「完全恢复」的意思是:把库恢复到【失败那一刻】,一行不丢。**
**能不能做到,全看有没有归档日志。**
**这一课造一个真实的灾难:备份之后又写了五批数据,然后文件坏了。**
一、造数据:备份之后还有五批写入
--------------------------------------------------------------------
**全备完成时 10 万行。之后五批各 1 万行,每批提交。**
**现在表里 150000 行。**
**这五批改动另存成一份重做日志,共 5 条记录。**
二、灾难:主库文件坏掉
--------------------------------------------------------------------
**主库文件没了。手上只有两份东西:全备文件、重做日志。**
三、NOARCHIVELOG:只能回到备份那一刻
--------------------------------------------------------------------
**恢复完成,表里 100000 行**——**就是备份那一刻的样子。**
四、ARCHIVELOG:备份加重做日志,回到崩溃点
--------------------------------------------------------------------
**恢复完成,表里 150000 行**——**五批全回来了。**
模式 用了什么 恢复后行数 丢失行数
------------------------------------------------
NOARCHIVELOG 只有全备 100000 50000
ARCHIVELOG 全备 + 归档日志 150000 0
已选择 2 行
**第一行丢的 50000 行,就是「备份之后写的」全部。**
**这也是为什么生产库几乎都开归档:不开,备份只能救到上一次备份。**
**注意第二行「丢失 0 行」有前提:重做日志必须是完整的。**
**如果日志文件也一起坏了,那就只能恢复到上一个归档点,退化成不完全恢复(第 74 课)。**
五、恢复不等于「保留所有事务」
--------------------------------------------------------------------
**一个事务插了 1000 行没提交就崩了。重开之后表里 1000 行。**
**恢复重放的是【已提交】的改动,未提交的那部分会被回滚掉。**
**所以「完全恢复」是「恢复到崩溃那一刻的已提交状态」,不是「把没提交的也算上」。**
第一段:崩溃时主库 15 万行,备份里只有 10 万行。差的这 5 万行,就是「备份之后写的」。
第二段:NOARCHIVELOG 恢复后 10 万行,丢 5 万行,丢了三分之一。
注意这里没有任何报错——恢复过程很顺利,只是数据回到了备份那一刻。
这就是 NOARCHIVELOG 的危险之处:它不报错,它只是悄悄少了一半数据。
第三段:ARCHIVELOG 恢复后 15 万行,一行不丢。做法就是「备份铺回去加重做依次应用」,
也就是 RESTORE 加 RECOVER。
第四段那 1000 行没提交的插入,恢复后是不存在的。
这不是「恢复失败」,是「恢复的正确行为」——未提交的事务本来就不该被保留。
这一点要跟业务说清楚,不然容易被误解成「数据丢了」。
补一句这套 Demo 的边界:这里用「重做记录文件」代替了归档日志,
用 executemany 代替了真正的日志应用。思路是一样的——「把已提交的改动按顺序重放」——
但真实环境还要处理块级重做、undo 回滚、检查点推进这些细节。
30 秒实验
- 把
redo里第 3 批的那行删掉(模拟「归档缺了一段」),再跑一次 ARCHIVELOG 恢复。 看最后少了多少行——这就是「日志断链」的后果。 - 把备份点改到「第一批之后」,再跑一次。
看
NOARCHIVELOG丢的行数变成多少——备份越晚,丢得越少,但永远不会是零。 - 在第二段恢复完之后,试着
select max(id)。 看最大 id 是多少、和主库崩溃前差多少——这比看行数更直观。
常见误区
- 「
NOARCHIVELOG也能完全恢复,只是慢一点」。 不能。 没有归档日志,就没有「备份之后那段重做」,物理上补不回来。 - 「恢复会把崩溃时没提交的事务也保留」。 不会。恢复只保留已提交的,未提交的会被回滚掉。
- 「控制文件有备份就万事大吉」。 控制文件要多路复用,放在不同物理盘上。 三个副本同时坏的机率远小于一个。
- 「在线日志坏了不要紧,有归档」。 当前在线日志坏了就是硬伤。 最新那一段重做没了,只能不完全恢复。
自测三问(点开对答案)
1. 完全恢复的三个前提是什么? —— **归档模式、日志完整连续、备份可用。**三者缺一,就只能退化成不完全恢复。 2. `RESTORE` 和 `RECOVER` 各做什么? —— **`RESTORE` 把备份文件铺回原位,`RECOVER` 把日志应用上去。**`NOARCHIVELOG` 只能做前者, 所以叫「还原」不叫「恢复」。 3. 一个数据文件坏了但库还开着,怎么处理? —— **先把该文件脱机,`RESTORE` 加 `RECOVER`,再联机。**这四步不影响其他表空间。 4. 为什么说「恢复不等于保留所有事务」? —— **因为恢复重放的是已提交的改动,未提交的会在开库后被 undo 回滚掉。**崩溃时正在跑的大事务, 恢复后不存在。下一篇讲不完全恢复。 完全恢复是「往前滚到底」,不完全恢复是「停在某个点」。后者的典型场景是误删数据—— 你得回到「删之前」,而不是「最新」。
- ☐ (本篇完,下一篇 16.5 使用 RMAN 工具部分恢复)
相关笔记
- 使用 RMAN 工具备份
- RMAN 工具简介
- 备份与恢复概述
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引