🏺 第2章 Oracle 体系结构
📄 一句话概括

逻辑结构讲「怎么组织」,物理结构讲「盘上放了什么」。 Oracle 的库由五类文件组成:数据文件、控制文件、重做日志、参数文件、密码/警告/跟踪文件。 少一个都起不来,但重要程度差得远。

📝 本章和前面几章的关系

上一篇把「文件里的东西」拆成四级逻辑单位,这一篇反过来,看盘上实实在在的那几个文件。 逻辑与物理不是两套东西,是同一个库的两个视角。 这一篇是第 7 章(控制文件与日志)和第 16 章(备份恢复)的引子。 那两章讲的每一件事,都落在这一篇列出的某类文件上。

一、五类文件,五种命

文件 记什么 丢了会怎样
数据文件 .dbf 表、索引的真实数据 丢数据,但有备份加日志能重做
控制文件 .ctl 库的骨架:有哪些文件、日志、SCN 起不来,要靠备份或重建
重做日志 .log 「改了什么」,用于恢复 最近的改动救不回来
参数文件 spfile/pfile 内存怎么分、行为怎么定 起不来,得手写一份
密码/警告/跟踪文件 账号、告警、会话跟踪 能起来,但出事时没线索

这个排序很反直觉: 最不显眼的控制文件最要命(它才几 MB),最大的数据文件反而最好救。 原因很简单:控制文件描述「库是什么」,没有它实例就不知道去哪儿找数据; 数据文件描述「数据是什么」,有备份就能重建。

二、控制文件为什么最要命

它里面记着: 数据文件清单、重做日志清单、当前日志序列号、数据库名、创建时间、归档模式、检查点信息。 所以它必须是多份的。Oracle 默认就给你放两三份(control_files 参数里列几个就有几份), 写在不同的盘上,一份坏了用另一份。 第 7.1 节会讲怎么加、怎么恢复。 它还是个二进制小文件,通常只有几 MB——但它是唯一一个「丢了就得重建整个库」的文件。

三、重做日志:循环使用的账本

日志不是一个文件写到底,是一组文件轮流写。写满一个就切到下一个(日志切换), 切过去的那个如果开了归档,就拷一份出去留着。 所以日志的容量决定了「能回退多久」: 只有三组日志、每组 50 MB,写得又快,可能十分钟就转一圈—— 转一圈之后最早的日志就被覆盖了,那之前的改动就救不回来了。 这就是为什么生产库要开归档模式(7.3 节): 归档之后,被覆盖的日志已经在别处存了一份,随时能拿回来重做。

四、参数文件:spfile 优先于 pfile

pfile 是文本,能拿记事本改,改完要重启; spfile 是二进制,只能 alter system 改,改完能立刻生效(看 scope)。 两者都存在时用 spfile。 这是「改了参数不生效」最常见的原因——改的是 initORCL.ora,系统读的是 spfileORCL.ora。 它们之间能互相生成:create pfile from spfile; 和 create spfile from pfile;。 spfile 坏了、库起不来的时候,这条命令是救命的那一步。

五、动手 Demo:在 SQLite 上找这五类文件的对应物

SQLite 只有一种文件:主库文件。 但它切到 WAL 模式后会多出两个,这三个文件正好把「数据文件/重做日志/共享内存」演全了。 控制文件它没有单独的,塞在文件头里——这一篇会直接把文件头那几十个字节读出来给你看。

import os, sqlite3

f = sqlite3.connect('phys.db')
f.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)')
f.executemany('INSERT INTO t VALUES (?,?)', [(i, 'x' * 30) for i in range(1, 200)])
f.commit()
os.listdir('.')                  # ['phys.db']:只有一个文件

# 切成预写日志模式,多出 -wal 与 -shm
f.execute('PRAGMA journal_mode=WAL').fetchone()
f.execute("INSERT INTO t VALUES (999,'wal test')")
f.commit()
os.listdir('.')                  # phys.db / phys.db-wal / phys.db-shm
os.path.getsize('phys.db-wal')   # WAL 攒了多少改动

# 控制文件:SQLite 把它塞在文件头里
head = open('phys.db','rb').read(100)
int.from_bytes(head[16:18], 'big')   # 页大小 4096
int.from_bytes(head[28:32], 'big')   # 页数

实跑输出:

--------------------------------------------------------------------
第八课:物理存储结构——盘上到底有哪几类文件
--------------------------------------------------------------------

  **逻辑结构讲「怎么组织」,物理结构讲「盘上放了什么」。**
  **Oracle 的库由五类文件组成,各有各的活,少一个都起不来。**


  五类文件各管一件事
  --------------------------------------------------------------------

  SQL> select name, job from fl
  NAME                  JOB
  -----------------------------------------------------------
  数据文件 .dbf         存表、索引的真实数据
  控制文件 .ctl         记库的骨架:有哪些数据文件、日志、SCN
  重做日志 .log         记「改了什么」,用于恢复与重做
  参数文件 spfile/pfile 启动时按它分配内存、定行为
  密码/警告/跟踪文件    账号口令、运行告警、会话跟踪
  已选择 5 行
  **重要程度排个序:**
  **控制文件最要命**(没了库就不知道自己是啥)、**重做日志其次**(没了最近的改动就丢了)、
  **数据文件反而最好救**(有备份加日志就能重做)。

  在 SQLite 上找对应的文件
  --------------------------------------------------------------------
  **默认 journal 模式(回滚日志)**
    phys.db               16384 字节


  切成 WAL 模式,多出两个文件
  --------------------------------------------------------------------
  `PRAGMA journal_mode=WAL` → **wal**
  **WAL 模式(预写日志)**
    phys.db               16384 字节
    phys.db-shm           32768 字节
    phys.db-wal            4152 字节

  **`-wal` 就是重做日志的对应物:**
  **改动先写进它,再慢慢合并回主文件。**  **`-shm` 是共享内存索引,给多进程读 WAL 用的。**
  **看大小就知道 WAL 攒了多少改动**——
  **Oracle 的 `v$log` 看的是同一件事:当前日志组写了多少、什么时候该切换。**

  控制文件在 SQLite 里是哪一段
  --------------------------------------------------------------------
  **SQLite 没有单独的控制文件,它把「库的骨架」写在第 1 页的文件头里:**
  **页大小、页数、schema 版本号、编码、文件变更计数。**
  文件头前 16 字节(十六进制):**53514c69746520666f726d6174203300**
  第 16~17 字节是页大小(大端):**4096**
  第 28~31 字节是页数:**4**
  **这三个数就相当于「控制文件里记的那点事」:块多大、库多大、结构什么样。**
  **只不过 Oracle 把它单独存成文件、还给你多路复用(7.1 节讲),SQLite 直接塞在数据文件的头里。**

默认模式(rollback journal)下只有一个 .db 文件, 写的时候才临时出现一个 -journal,提交完就删掉。 这也是一种日志,只是用完即弃。 切成 WAL 之后多出 -wal 和 -shm: -wal 是预写日志(改动先写它,再慢慢合并回主库), -shm 是给多进程读 WAL 用的共享内存索引。 对应 Oracle:-wal 像重做日志,-shm 像 SGA 里的一块。 -wal 的大小就是「还没合并的改动有多少」——这个数会周期性回落(合并完就截断)。 Oracle 的 v$log 看的是同一件事:当前日志组写了多少。 文件头那三个数值得记: 前 16 字节是 SQLite format 3\0(魔数),第 16~17 字节是页大小,第 28~31 字节是页数。 Oracle 把这类信息单独存成控制文件,还给你多路复用;SQLite 图省事,直接写进数据文件第一页。

30 秒实验

  1. 把 journal_mode 切回 delete, 再看目录——-wal 和 -shm 会自动消失。这说明它们是「模式带来的」,不是「库自带的」。
  2. 往 t 里插 5000 行,再看 -wal 的大小。 它会涨得很大——这就是「日志攒着没合并」,对应 Oracle 里日志组快写满、要切换了。
  3. 用 dbstat(如果编进去了)或 page_count 估一下主库文件的实际数据占比,和文件大小比一比。 文件通常比数据大——因为空闲页留在里面没还。

常见误区

  1. 以为删掉 .dbf 只是少一张表。 那是整个表空间的数据, 而且控制文件里还记着它——库会起不来,报 ORA-01157。
  2. 以为日志文件可以随便删。 删当前日志组,实例直接崩; 删未归档的旧日志,那段时间的改动永远救不回来。
  3. 改了参数不生效,就去改 pfile。 先 show parameter spfile 看一眼, 系统在用 spfile 的话,改 pfile 一点用都没有。
自测三问(点开对答案) 1. 五类文件里,哪个丢了最要命?为什么? —— **控制文件。** 它记着「这个库由哪些文件组成」,**没了它实例根本不知道去哪儿找数据**, 只能靠备份恢复或者重建。 2. 重做日志为什么要「循环使用」? —— **因为它会一直长**。写满就切、切了就覆盖最早的,**这样空间可控**; 代价是**覆盖之后的改动就救不回来了**,所以要开归档(7.3 节)。 3. `-wal` 文件对应 Oracle 的什么? —— **重做日志(redo)。** 都是「改动先写日志,再合并/刷到主文件」,**目的都是崩溃后能重做。**

下一篇看「算」的那一半:SGA、PGA、前后台进程。 我会自己写一个 128 块的模拟盘加 LRU 缓存,把「缓冲池命中率」从一句口号量成一条曲线。

相关笔记