控制文件是 Oracle 里最小的关键文件——通常几 MB,但没它库就打不开。
它记的不是数据,是「数据放在哪」:几个数据文件、几个日志组、检查点到哪、SCN 是多少。
两件事必须做到:多路复用(一份写成两三份,放不同盘),
以及定期 BACKUP CONTROLFILE TO TRACE——二进制丢了,那段纯文本还能重做一遍。
第 2 章讲物理存储结构时提过「三类关键文件:控制文件、数据文件、日志文件」。 第 6 章之前都在跟 SQL 打交道,从这一章起换身份:写代码的人变成管库的人。 这一章三篇是一条链:控制文件记着日志文件在哪,日志文件里存着重做记录, 归档日志决定你能恢复到哪一秒。三样凑齐,库才叫「可恢复」。
一、控制文件简介:几 MB,管着整个库的清单
它是一个二进制小文件,不许手工改,也不许用文本编辑器打开。 内容全是「记录段」(record section):数据文件段、日志文件段、日志成员段、备份段、 临时文件段……每段占固定大小的槽位。
| 里面记什么 | 为什么要记 |
|---|---|
| 数据文件清单 | 启动时照它开文件,少一个就报错 |
| 日志组与成员 | LGWR 照它找该往哪写 |
| 当前 SCN 与检查点 | 崩溃恢复从哪个点开始重放 |
| 备份与归档记录 | RMAN 靠它判断哪些备份可用 |
路径由参数 CONTROL_FILES 指定,库跑起来之后这个参数不能改。
想加副本、减副本,都得停库、改参数文件、再启动。
这就是它「位置固定」的原因——启动的第一秒就要读它,那时候还没有任何动态配置可用。
控制文件丢了会怎样?startup 直接失败,报 ORA-00205。
注意它连 mount 都进不去——
打开控制文件是启动的第一阶段(nomount 时读参数、mount 时读控制文件),
比读数据文件还早。所以数据文件再完好,也没人告诉 Oracle 该开哪些。
二、控制文件的多路复用:一份写成三份
做法很简单:CONTROL_FILES 里列三个路径,Oracle 就同时写三份。
三份内容永远一样(同一个事务里一起写),所以任何一份单独拿出来都能开库。
规范要求:三份放在不同物理盘上,最好不同控制器。
放同一块盘上,盘坏了三份一起没,多路复用等于没做。
这一点和日志文件的成员是同一个道理。
副本不一致会怎样?报 ORA-00214(control file inconsistent)。
常见原因是「改了库结构,但有一份盘写失败」,
或者「从旧备份恢复了一份控制文件,另一份还是新的」。
修法不是「删掉不一致的那份」——
要先确认哪一份最新(看 controlfile_sequence# 和时间戳),然后用它覆盖别的。
覆盖完再启动,就一致了。
这一步要在库停着的时候做,跑着的库不许碰这些文件。
三、创建控制文件:两条路
正常情况不需要「创建」,建库时自动生成。要手工创建只有两种场景:
① 三份全丢、没有二进制备份;② 要改 MAXDATAFILES 这类上限。
第一条路是 CREATE CONTROLFILE 语句。
它要你把数据文件、日志文件一个个列出来,还要给 NORESETLOGS 或 RESETLOGS 的选择。
列错一个文件,库就开不起来——所以这一步必须对着 v$datafile 的历史输出抄。
| 选项 | 什么时候用 |
|---|---|
NORESETLOGS |
日志文件都还在,序列能接上 |
RESETLOGS |
日志丢了或做了不完全恢复,序列归零 |
第二条路更常用:从 trace 备份里挖出那段 SQL。
平时跑一句 ALTER DATABASE BACKUP CONTROLFILE TO TRACE;,
Oracle 就把「怎么重建这个控制文件」写成一段完整的 SQL 存进 trace 目录。
控制文件二进制备份丢了,这段纯文本还在,照着敲一遍就行。
所以日常运维有一条硬规矩:每次改完库结构(加数据文件、加日志组),重新生成一次 trace 备份。
不然那段 SQL 里列的还是旧的文件清单,照着做出来的是个残缺的控制文件。
四、备份和恢复控制文件
三种备份方式,用途不一样:
| 方式 | 产物 | 适合什么 |
|---|---|---|
TO TRACE |
一段可读 SQL | 结构变更后必做,最抗丢 |
TO '文件' |
二进制副本 | 能直接拷回去,但要和全备一起管 |
| RMAN 自动备份 | 备份集里 | 开了 CONFIGURE CONTROLFILE AUTOBACKUP ON 就有了 |
恢复方式对应三种:
拷回二进制副本、跑那段 CREATE CONTROLFILE、或者让 RMAN 自己找。
恢复完还要 RECOVER DATABASE——
因为控制文件里的 SCN 可能比数据文件旧,得用日志把它推到一致。
一个容易忽略的点:控制文件里记着「备份做过哪些」。
如果用一份很旧的控制文件启动,RMAN 会不知道最近做过哪些备份,
于是「明明有备份却说找不到」。
这种时候要做 CATALOG START WITH 把备份重新登记进来。
五、删除控制文件:只能停库后用操作系统命令
没有 DROP CONTROLFILE 这种语句。
因为控制文件是「库的身份证」,删了库就不是库了。
要删的其实是「多余的副本」,做法是三步:
① SHUTDOWN IMMEDIATE 停库;② 改参数文件里的 CONTROL_FILES,把不要的路径去掉;
③ 用操作系统命令删那个文件。
顺序很重要:先停库、再改参数、最后删文件。
反过来的话,库还开着就删了文件,下一次写控制文件会报错。
注意改的是「参数文件」,不是「内存里的参数」。
ALTER SYSTEM SET control_files = ... 在库跑着的时候是改不动的,
因为它属于「静态参数」,必须改 spfile 或 pfile 再启动。
六、查询控制文件的信息:四张视图
v$controlfile 列副本路径和状态。
v$controlfile_record_section 列各记录段的用量。
v$database 给 controlfile_sequence# 和 controlfile_time。
v$parameter 里的 control_files 就是那个参数本身。
v$controlfile_record_section 里最该看的是 RECORDS_TOTAL 和 RECORDS_USED。
USED 接近 TOTAL 就该动手了——
满了之后加数据文件会报 ORA-00059(超出 DB_FILES 上限)一类。
扩的办法只有重建控制文件,把 MAXDATAFILES 写大。
controlfile_sequence# 是最实用的一个数:
每做一次结构变更它就涨一次。
判断几份副本谁新谁旧,比文件时间戳可靠——因为时间戳可能被拷贝操作改掉,序号不会。
七、动手 Demo:用三个文件演一遍控制文件:多路复用、不一致、全丢
沙箱里没有 Oracle,CREATE CONTROLFILE 一行都执行不了。
但控制文件的核心机制——「多份副本、内容一致、丢一份能开、
全丢打不开」——完全可以用文件系统演出来。
我把控制文件写成一份文本清单,拷成三份,比较指纹,再故意改坏一份,
最后三份全删,看「启动」会报什么。
输出里的字节数、指纹都是真的。
import hashlib, io, os, shutil
CTL = [
'DB_NAME=ORCL',
'DBID=1234567890',
'CONTROLFILE_SEQUENCE=1',
'DATAFILE 1 /u01/oradata/ORCL/system01.dbf 314572800',
'DATAFILE 2 /u01/oradata/ORCL/sysaux01.dbf 209715200',
'DATAFILE 3 /u01/oradata/ORCL/users01.dbf 5242880',
'LOGFILE 1 /u01/oradata/ORCL/redo01.log 52428800',
'LOGFILE 2 /u01/oradata/ORCL/redo02.log 52428800',
'LOGFILE 3 /u01/oradata/ORCL/redo03.log 52428800',
]
def md5(p):
with open(p, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()[:8]
def startup(paths):
u"""照控制文件的副本来「启动」:先看有没有,再看一不一致。"""
live = [p for p in paths if os.path.exists(p)]
if not live:
return 'ORA-00205: error in identifying control file'
if len(set(md5(p) for p in live)) > 1:
return 'ORA-00214: control file inconsistent'
return 'OK 读到 3 个数据文件、3 个日志组'
os.makedirs('ctl_demo', exist_ok=True)
p1 = 'ctl_demo/control01.ctl'
with io.open(p1, 'w', encoding='utf-8', newline='\n') as f:
f.write('\n'.join(CTL) + '\n')
shutil.copyfile(p1, 'ctl_demo/control02.ctl')
shutil.copyfile(p1, 'ctl_demo/control03.ctl')
copies = ['ctl_demo/control0%d.ctl' % i for i in (1, 2, 3)]
print([md5(p) for p in copies])
print(startup(copies))
# 把第 2 份改坏:多写一行
with io.open(copies[1], 'a', encoding='utf-8', newline='\n') as f:
f.write('DATAFILE 4 /u01/oradata/ORCL/oops.dbf 1024\n')
print([md5(p) for p in copies])
print(startup(copies))
# 用好的一份盖掉坏的
shutil.copyfile(copies[0], copies[1])
print(startup(copies))
# 三份全删
for p in copies:
os.remove(p)
print(startup(copies))
实跑输出:
--------------------------------------------------------------------
第三十课:控制文件——记着整个库长什么样的那个小文件
--------------------------------------------------------------------
**控制文件(control file)是 Oracle 里最小的一个关键文件**—— **通常几 MB,但没它库就打不开。**
**它记的不是数据,是「数据放在哪」:**
**有几个数据文件、几个日志组、当前检查点到哪、SCN 是多少、备份做过哪些。**
**库一启动,Oracle 第一件事就是读控制文件**, **照它列的清单去开数据文件和日志文件。**
**所以控制文件丢了,数据文件再完好也没用**—— **没人告诉 Oracle 该开哪些文件。**
先用文本文件把「控制文件里有什么」摆出来
--------------------------------------------------------------------
DB_NAME=ORCL
DBID=1234567890
CONTROLFILE_SEQUENCE=1
DATAFILE 1 /u01/oradata/ORCL/system01.dbf 314572800
DATAFILE 2 /u01/oradata/ORCL/sysaux01.dbf 209715200
DATAFILE 3 /u01/oradata/ORCL/users01.dbf 5242880
LOGFILE 1 /u01/oradata/ORCL/redo01.log 52428800
LOGFILE 2 /u01/oradata/ORCL/redo02.log 52428800
LOGFILE 3 /u01/oradata/ORCL/redo03.log 52428800
**真实控制文件是二进制,人不可读,也不许手工改。**
**这里用文本是为了让你看见「里面就那么几行清单」这件事。**
**这几行里最要紧的是 `DB_NAME` 和 `DBID`**——
**库名和库的唯一编号,认错库就靠这两个**(`ORA-01103` 就是这个意思)。
多路复用:一份写成三份
--------------------------------------------------------------------
SQL> select name as 副本, md5 as 指纹, bytes as 字节 from v_controlfile
副本 指纹 字节
---------------------------
control01.ctl 6dde1ee4 376
control02.ctl 6dde1ee4 376
control03.ctl 6dde1ee4 376
已选择 3 行
**三份指纹完全相同,字节数也一样——这就是「多路复用」。**
**Oracle 写控制文件时会同时写所有副本,所以理论上永远一致。**
**代价是磁盘多占两份,好处是坏一块盘还能开库。** **规范做法是放在不同物理盘上**,
**`CONTROL_FILES` 参数里列几个路径,就是几份副本。**
演练一:坏掉一份副本会怎样
--------------------------------------------------------------------
**我用文本编辑器「改坏」第 2 份——多写一行。** **模拟现实中某块盘写了半截、或者被人误改。**
SQL> select name as 副本, md5 as 指纹 from v_controlfile
副本 指纹
----------------------
control01.ctl 6dde1ee4
control02.ctl b2a8575d
control03.ctl 6dde1ee4
已选择 3 行
**现在指纹不再一致了:有一份不一样。**
**Oracle 启动时读到不一致的副本,报 `ORA-00214`:**
**「控制文件版本不一致」,并且告诉你哪一份的序列号大。**
**修法不是「删掉坏的份」就完事**——
**要先确认哪一份最新,然后用它覆盖别的:**
**`cp control01.ctl control02.ctl`,一份好的盖掉坏的。**
SQL> select name as 副本, md5 as 指纹 from v_controlfile
副本 指纹
----------------------
control01.ctl 6dde1ee4
control02.ctl 6dde1ee4
control03.ctl 6dde1ee4
已选择 3 行
**覆盖之后指纹又一致了,库能正常打开。**
演练二:三份全丢会怎样
--------------------------------------------------------------------
**三份都删掉,剩下 0 份。**
**这时候 `startup` 直接失败,报 `ORA-00205`:** **error in identifying control file。**
**注意它连 mount 都进不去**——
**因为「打开控制文件」是 `startup` 的第一阶段,比读数据文件还早。**
补救:从 trace 备份里把控制文件重建出来
--------------------------------------------------------------------
**平时该做的一件事:`ALTER DATABASE BACKUP CONTROLFILE TO TRACE;`**
**它把「怎么重建这个控制文件」写成一段 SQL 存进 trace 文件。**
[Oracle 原码 · 本沙箱不执行]
| ALTER DATABASE BACKUP CONTROLFILE TO TRACE AS '/u01/bak/ctl.sql';
转成 SQL 文本,比二进制备份更抗丢。
**生成的 `ctl.sql` 一共 521 字节,关键部分长这样:**
STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
MAXLOGFILES 16
MAXLOGMEMBERS 3
MAXDATAFILES 100
MAXINSTANCES 8
MAXLOGHISTORY 292
LOGFILE
GROUP 1 '/u01/oradata/ORCL/redo01.log' SIZE 50M,
GROUP 2 '/u01/oradata/ORCL/redo02.log' SIZE 50M,
GROUP 3 '/u01/oradata/ORCL/redo03.log' SIZE 50M
DATAFILE
'/u01/oradata/ORCL/system01.dbf',
'/u01/oradata/ORCL/sysaux01.dbf',
'/u01/oradata/ORCL/users01.dbf'
CHARACTER SET ZHS16GBK
**`NORESETLOGS` 表示「按原来的日志序列接着用」,`RESETLOGS` 表示「序列归零重来」。**
**做完不完全恢复必须用 `RESETLOGS`**, **因为日志链已经断了,只能从新的序列重新开始。**
**这段 SQL 的价值在于它是纯文本**——
**控制文件二进制备份丢了,这段文字还在,就能照着重做一遍。**
查控制文件信息:三张视图
--------------------------------------------------------------------
SQL> select type as 记录段, record_size as 单条字节, total as 总量, used as 已用 from v_cf_section
记录段 单条字节 总量 已用
----------------------------------
DATAFILE 428 100 3
REDO LOG 108 16 3
REDO LOG MEMBER 192 48 6
BACKUP DATAFILE 124 210 12
BACKUP REDOLOG 108 105 8
已选择 5 行
**`v$controlfile` 列副本路径,`v$controlfile_record_section` 列各记录段的用量。**
**`v$database` 里还有 `controlfile_sequence#` 和 `controlfile_time`**——
**这两个数每做一次结构变更就涨一次,比副本指纹更能判断新旧。**
**`used` 接近 `total` 就该扩了。** **这时候要改 `MAXDATAFILES` 之类的上限,**
**而改上限的办法就是重建控制文件**——绕回上面那段 `ctl.sql`。
三份指纹一样(6dde1ee4),字节数也一样(376)——这就是多路复用。
Oracle 写控制文件时是一个事务里写所有副本,所以理论上永远一致。
改坏第 2 份之后,指纹变成 b2a8575d,另外两份还是 6dde1ee4。
这时候「启动」报 ORA-00214——不一致。
这个码的完整说法是「control file inconsistent」,并且会同时报出两个文件名和各自的序列号。
修法是「好的盖坏的」,不是「删掉坏的」。
因为你不确定坏的那份是不是唯一反映了最新结构的那份——万一它才是新的,删了就丢了最新结构。
所以先比序列号,再覆盖。
三份全删之后报 ORA-00205。
注意这时候连 mount 都进不去——因为读控制文件发生在 mount 之前,
所以这个错的表现是「库根本起不来」,而不是「起来了但打不开表」。
还有一件事模型没演:真实控制文件是二进制,且带校验和。
所以「改坏」在现实里不是多一行,而是校验失败——
报的码也不一样(ORA-00201/ORA-00202 一类)。
但这个模型的结论是对的:副本之间必须一致。
30 秒实验
- 把
startup里判断一致性的那句改成「只看第一份」,重跑。 改坏第 2 份之后它会说 OK——这说明「只读一份」也能开库,但等于放弃了多路复用的保护。 - 把三份副本分别放到三个子目录里(
disk1/、disk2/、disk3/),重跑。 输出一样,但这才是真实部署该有的样子——同一目录下的三份,盘一坏全没。 - 在
CTL里加一行DATAFILE 4 /u01/oradata/ORCL/oops.dbf,重跑。startup会报「读到 3 个数据文件」——因为我在函数里把数量写死了。 真实 Oracle 是照内容数出来的,这说明「用一个写死的函数演机制」的边界在哪。
常见误区
- 把控制文件当成「配置文件」,想手工编辑。 它是二进制,改了就打不开。
要改内容,只能用
CREATE CONTROLFILE重建。 - 三份副本放在同一块盘上。 盘坏三份一起没。 多路复用的前提是「坏一处不影响」,同盘等于没做。
- 副本不一致时直接删掉那份。 可能删掉的是最新的。
先比
controlfile_sequence#,再用好的覆盖。 - 改完库结构不重新做 trace 备份。 那段 SQL 里还是旧的文件清单, 真要用的时候照着做出来是个残库。
自测三问(点开对答案)
1. 控制文件丢了,数据文件完好,库能打开吗? —— **不能。** 报 `ORA-00205`,**连 mount 都进不去**。 **因为「开哪些数据文件」这件事就记在控制文件里。** 2. 三份副本内容不一致,Oracle 报什么?怎么修? —— **报 `ORA-00214`。** **修法是先确认哪份最新,再用它覆盖其余的**——**不是删掉不一致的那份。** 3. `BACKUP CONTROLFILE TO TRACE` 有什么用? —— **它把「怎么重建控制文件」写成一段可读 SQL。** **二进制备份丢了,这段文本还在,照着重做一遍即可。** 4. 想加大 `MAXDATAFILES` 该怎么做? —— **只能重建控制文件**,把 `MAXDATAFILES` 写大。**所以建库时这个值要给足**,改起来代价不小。下一篇讲重做日志: 控制文件里记着「日志文件在哪」,这一篇讲那些日志文件怎么分组、怎么轮转、怎么改名。
- ☐ (本篇完,下一篇 7.2 管理重做日志文件)
相关笔记
- 管理重做日志文件
- 管理归档日志文件
- 物理存储结构
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引