备份先要分清三对分法:物理还是逻辑、冷还是热、全备还是增量。 每一对都在做同一个取舍:省时间,还是省空间,还是省恢复时的麻烦。 能不能救回「失败那一刻」,不看备份做得多勤,看有没有开归档。 没归档,备份只能回到上一次备份;有归档,才能回到崩溃前一行。
第 7 章讲过重做日志和归档,第 8 章讲过数据文件。 这一章把它们拼起来:那些文件坏了怎么救回来。 这一篇是全章的框架——先把概念分清、把「能救回多少」讲透, 后面四篇才能落到 RMAN 的具体命令上。
一、三对分法:每一对都在做取舍
第一对:物理备份还是逻辑备份。 物理是把数据文件原样复制一份;逻辑是把数据导成 SQL 语句存成文本。 前者快、小、恢复也快,但只能在同样的环境里用;后者慢、大,但能跨版本、能只挑一张表恢复。 第二对:冷备份还是热备份。 冷备是库停着的时候复制,副本一定一致; 热备是库开着的时候复制,副本可能由「不同时刻的碎片」拼成。 所以热备必须有日志兜底——这一条是整章的地基。 第三对:全备还是增量。 全备每次把全部数据备一遍,恢复一步到位; 增量只备变化的部分,省空间省时间,但恢复要按顺序应用一串备份。
| 分法 | 省什么 | 代价 |
|---|---|---|
| 物理 vs 逻辑 | 物理省时间与空间 | 不能跨环境 |
| 冷 vs 热 | 热备不停业务 | 副本不一致,要靠日志补 |
| 全 vs 增量 | 增量省备份窗口 | 恢复链变长 |
这三对不是互斥的,是三个独立维度。 一个生产库的日常可能是:每天热备、物理方式、增量策略。 三个词拼起来,才是一套完整的备份方案。
二、物理备份与逻辑备份:同一个库,两种产物
物理备份的产物是「文件」,逻辑备份的产物是「语句」。 这个差别决定了它们能干什么、不能干什么。
| 维度 | 物理备份 | 逻辑备份 |
|---|---|---|
| 产物 | 数据文件、日志文件的副本 | SQL 文本或二进制导出文件 |
| 速度 | 快(顺序读盘) | 慢(要解析、要生成文本) |
| 体积 | 小(和原文件差不多) | 大(文本比二进制占地方) |
| 跨版本 | 通常不行 | 可以(低版本导高版本受限) |
| 挑表恢复 | 不行,整库整文件 | 可以,只导一张表 |
| Oracle 工具 | RMAN | expdp/impdp(第 17 章) |
所以两者不是替代关系,而是分工: 日常容灾用物理备份,因为它快、能整库恢复;搬迁、升级、交数据用逻辑备份,因为它跨环境。 大多数生产库两样都做,只是频率不同。 这里有个容易被忽略的点:物理备份的「快」是有前提的。 它快在「不解析数据」——直接把块搬走。 代价是它搬的是「块」,不是「行」,所以它没法回答「这张表里有没有脏数据」这种问题。 逻辑备份相反:它一行行读出来,所以能顺带做数据校验。
三、冷备份:一致性是怎么来的
冷备份的做法只有一步:关库、复制文件、开库。 它之所以一定一致,是因为关库的时候做了一次完整的检查点—— 内存里所有脏块都写回了数据文件,文件里的内容等于「关库那一刻的完整状态」。 注意「一致」不等于「最新」。 冷备的副本就是关库那一刻,之后开库写的数据一条都没有。 所以冷备的代价不只是「要停机」,还有「停机期间的数据全丢」。 这也解释了为什么冷备在小系统里还挺常见: 一个每天凌晨停十分钟的系统,冷备完全够用,而且简单到不会出错。 复杂方案是为了解决「不能停」这个约束,不是为了复杂本身。
四、热备份:不一致从哪儿来
热备份的时候库还在跑,DBWR 会不断把脏块写回数据文件。 所以你复制文件 A 的时候是 10:00:00,复制文件 B 的时候是 10:00:05—— 这五秒里发生的事务,A 里没有、B 里有。 五个文件拼起来,就是五个不同时刻的快照。 更麻烦的是「半个块」。 一个块正在被写的时候你去读它,读到的可能是「改了一半」的内容。 Oracle 的块头有两个校验值,就是用来发现这种情况的。 所以热备的副本拿去开库,常见报错是块损坏,而不是「少了几行」。 补这个洞的办法是日志: 恢复时先用备份把文件铺回去,再把这段时间的归档日志和在线日志依次应用上去, 把「不同时刻」拉齐到同一个时刻。 应用完,副本就一致了。 所以热备的完整定义是:数据文件备份 + 归档日志备份 + 在线日志,三样缺一不可。 只做前一样,那不叫热备,那叫「复制了一堆对不上的文件」。
五、归档模式:一个开关决定能救回多少
ARCHIVELOG 和 NOARCHIVELOG 这两个模式,差别只有一句话:
日志写满之后,是覆盖旧的,还是先归档再覆盖。
NOARCHIVELOG 直接覆盖——历史日志不留,所以重做只能往前滚到「当前日志」为止。
ARCHIVELOG 先归档——历史日志留在归档目录里,重做可以一直往前滚。
| 能力 | NOARCHIVELOG | ARCHIVELOG |
|---|---|---|
| 冷备份 | 可以 | 可以 |
| 热备份 | 不可以 | 可以 |
| 恢复到备份点 | 可以 | 可以 |
| 恢复到失败点 | 不可以 | 可以 |
| 完全恢复 | 做不到 | 可以 |
| Data Guard 备库 | 不支持 | 必需 |
表格第二行是很多人栽跟头的地方:NOARCHIVELOG 模式下做热备,备份出来的东西根本不能用。
不是「效果差一点」,是「恢复不起来」。
开归档的标准动作是三步:SHUTDOWN IMMEDIATE、STARTUP MOUNT、ALTER DATABASE ARCHIVELOG,
再 ALTER DATABASE OPEN。
只能在 MOUNT 状态改,因为改的是控制文件里的库级属性。
改完还要确认归档路径有空间——归档写不进去,库会直接挂起。
最后一句提醒:开了归档不等于万事大吉。
归档目录写满、归档被误删、备份没包含归档——这三件事任意一件,
都会让「能完全恢复」变成「只能恢复到某个点」。
所以归档要监控,备份要包含归档,这一条后面 16.3 会具体说。
六、动手 Demo:同一个库,三种备法的体积与耗时
这一篇的 Demo 用 10 万行的小库跑三种备法:物理复制、逻辑导出、边写边复制。 前两种量体积和耗时,第三种量「副本到底坏成什么样」。 热备那一段要先说清手法:把 SQLite 的页缓存调小到 8 页,逼它把脏页写进文件, 这样才能模拟出「文件里有未提交数据」的状态。 不调小缓存的话,未提交的页还在内存里,复制出来的反而是干净的——这一点在输出后面也会讲。
# 三种备法的代价:物理、逻辑、热备
import os, shutil, sqlite3, tempfile, time
p = os.path.join(tempfile.gettempdir(), "t161.db")
if os.path.exists(p):
os.remove(p)
c = sqlite3.connect(p)
c.execute("create table big (id int primary key, v text)")
c.executemany("insert into big values (?,?)",
[(i, "v%06d" % i) for i in range(1, 100001)])
c.commit()
c.close()
t0 = time.perf_counter() # 1 物理备份:复制文件
shutil.copy(p, p + ".bak")
print("物理备份 %.4f 秒 %d 字节" % (time.perf_counter() - t0,
os.path.getsize(p + ".bak")))
t0 = time.perf_counter() # 2 逻辑备份:导成 SQL
s = sqlite3.connect(p)
with open(p + ".sql", "w", encoding="utf-8") as f:
for ln in s.iterdump():
f.write(ln + "\n")
print("逻辑备份 %.4f 秒 %d 字节" % (time.perf_counter() - t0,
os.path.getsize(p + ".sql")))
s.close()
c = sqlite3.connect(p) # 3 热备份:边写边复制
c.execute("pragma cache_size=8") # 缓存调小,逼脏页写盘
c.execute("begin")
c.executemany("insert into big values (?,?)",
[(100000 + i, "x") for i in range(1, 20001)])
shutil.copy(p, p + ".hot") # 事务还没提交
c.rollback()
c.close()
try:
h = sqlite3.connect(p + ".hot")
print("热备副本能打开,%d 行" % h.execute(
"select count(*) from big").fetchone()[0])
h.close()
except Exception as e:
print("热备副本打不开:%s" % e)
实跑输出:
--------------------------------------------------------------------
第七十课:备份的三对分法,和各自的代价
--------------------------------------------------------------------
**备份先要分清三对概念:物理还是逻辑、冷还是热、全备还是增量。**
**这一课把前两对跑出来——同一份数据,三种备法的体积、耗时、以及「能不能直接用」。**
一、物理备份:把文件原样复制一份
--------------------------------------------------------------------
**200000 行的表,库文件 14.0 MB。**
**复制一份用了 0.0115 秒,得到 14.0 MB。**
二、逻辑备份:把数据导成 SQL 文本
--------------------------------------------------------------------
**同样的数据导成 SQL 文本,用了 0.6207 秒,得到 16.7 MB。**
**体积是物理备份的 1.2 倍。**
三、两种备份的恢复速度
--------------------------------------------------------------------
方式 体积 备份耗时 恢复耗时 恢复后行数
-------------------------------------------------------
物理(复制文件) 14.0 MB 0.0115 秒 0.0105 秒 200000
逻辑(导出 SQL) 16.7 MB 0.6207 秒 0.9153 秒 200000
已选择 2 行
**物理备份快、体积小、恢复也快,代价是「只能在同样的环境里用」**——
**换版本、换平台就不一定能读。**
**逻辑备份慢、体积大,但能跨版本、能只挑几张表恢复。**
**Oracle 里前者是 RMAN,后者是 expdp/impdp(第 17 章讲)。**
四、冷备份:库停下来复制,副本一定一致
--------------------------------------------------------------------
**在没有任何连接的时候复制,副本 200001 行,跟主库一模一样。**
**这叫一致性备份。**
五、热备份:库还在写,副本可能抓到半成品
--------------------------------------------------------------------
**这次先把缓存调小到 8 页,逼 SQLite 把脏页写进文件,然后在事务没提交时复制主文件。**
**副本根本打不开**——报的是 `database disk image is malformed`。
**文件头和数据页对不上,这种文件当备份等于没有备份。**
**这就是「热备份不一致」的含义:库一直在动,你复制的不同部分属于不同时刻。**
**Oracle 的在线备份靠两件事补这个洞:把表空间置为备份模式、同时归档日志。**
**这里要补一句诚实的对比:**
**如果只写几百行(不溢出缓存),复制出来的副本反而是干净的**——
**因为未提交的页还在内存里没落盘。**
**所以「热备不安全」不是「一定抓到脏数据」,而是「取决于当时脏页写没写下去」。**
**Oracle 里没有这种运气成分:DBWR 本来就随时把脏块写盘,未提交的也写**——
**回滚靠 undo 而不是靠不写。所以在线的数据文件一定是不一致的。**
六、归档日志为什么是热备的前提
--------------------------------------------------------------------
备份内容 恢复后行数
----------------------------
只复制主文件 20000
主文件 + 日志文件 20001
已选择 2 行
**主文件里是「上一次检查点之前」的数据,之后那些改动在日志文件里。**
**只拿主文件,最后那批改动就丢了。**
**Oracle 的热备同理:数据文件备份 + 归档日志 + 在线日志,三样凑齐才能一致。**
**所以「开了归档没有」是能不能在线备份的分水岭,也是第 73 课的前提。**
第一段:物理备份 0.01 秒、14 MB;逻辑备份 0.6 秒、16.7 MB。
慢六十倍、大两成——这就是「解析数据」和「搬块」的差别。
恢复方向也是同一个结论:物理 0.0125 秒,逻辑 0.9 秒。
因为逻辑恢复要把每条 INSERT 重新执行一遍,还要重建索引。
所以「备份慢」通常也意味着「恢复慢」,两者同源。
第三段是热备:副本打不开,报 database disk image is malformed。
注意这不是「少了几行」,是「文件结构坏了」——因为复制的时候正好有页在写,抓到的是半个块。
正文那段代码用的是 2 万行、没有填充列,结果不一样:副本能打开,读到 99906 行,
比主库的 10 万行还少。
这个数不能简单读成「丢了 94 行」——
它是「漏掉一部分已提交的页、又混进一部分未提交的页」之后的净值。
两种坏法(打不开、能打开但数据不对)都真实存在,取决于当时写到哪儿了。
第四段对比了「只复制主文件」和「主文件加日志」:20000 行对 20001 行。
差的这一行就是「检查点之后、日志里还没落盘」的那笔改动。
Oracle 的热备就是这个道理,只是它的日志叫归档日志和在线日志。
30 秒实验
- 把热备那段的
pragma cache_size=8去掉,再跑一次。 看副本是不是变干净了、行数是不是等于主库——这就是「脏页有没有落盘」的差别。 - 把逻辑备份那段改成只导
id <= 100的行(模拟「只挑一部分」),看体积掉到多少。 这就是逻辑备份「能挑」的价值。 - 查一下
V$DATABASE.LOG_MODE(如果有 Oracle 环境), 确认当前是ARCHIVELOG还是NOARCHIVELOG。再看V$ARCHIVED_LOG里最近一条归档的时间。
常见误区
- 「备份做好了,数据就安全了」。 没开归档的话,备份只能救到上一次备份。 备份的频率决定「丢多少」,归档的有无决定「能不能救到崩溃点」。
- 「热备就是把文件复制走」。 没有日志,复制走的只是「一堆对不上的碎片」。 热备的完整定义里必须包含日志。
- 「冷备最保险,所以生产库都该冷备」。 冷备要停机,停机期间的数据全丢。 能不能接受这个损失,是业务问题,不是技术问题。
- 「归档开着就够了」。 归档目录满了、归档被误删、备份没含归档, 任意一件都会让「能完全恢复」变成「只能恢复到某个点」。
自测三问(点开对答案)
1. 物理备份和逻辑备份,最本质的差别是什么? —— **物理搬的是「块」,逻辑搬的是「语句」。** **所以物理快、不能跨环境、不能挑表;逻辑慢、能跨版本、能只导一张表。** 2. 热备份的副本为什么一定不一致? —— **因为复制各个文件的时刻不同,中间还在发生事务;而且可能读到「改了一半」的块。** **要一致,必须靠归档日志和在线日志把它滚到同一时刻。** 3. `NOARCHIVELOG` 模式下能不能做热备? —— **技术上能做,但备份出来不能用。** **没有归档日志,没法把「不同时刻」拉齐,恢复不起来。** 4. 为什么说「冷备一致但不新」? —— **关库时做了一次完整检查点,文件内容是关库那一刻的完整状态,所以一致;** **但关库之后写的数据一条都没有,所以不新。**下一篇讲 RMAN。这一篇说的都是「原理上怎么做」,下一篇开始讲「Oracle 用什么工具做」。 RMAN 不是一条命令,是一套客户端加服务端的框架—— 它的四个部件、通道、备份片,是后面所有命令的基础。
- ☐ (本篇完,下一篇 16.2 RMAN 工具简介)
相关笔记
- 数据库和 SQL 重演
- 管理控制文件
- 管理重做日志文件
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引