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

Oracle 的文档里,「实例」和「数据库」从来不说成一个。 实例=内存+进程,数据库=磁盘上的文件。这两样可以分开存在: 实例起着、库没打开;实例关了、文件还在。 这一篇就是把这根线划清。

📝 本章和前面几章的关系

第 1 章讲的是「怎么把库装出来、怎么开关」,这一章开始拆开看里面。 这一篇是总图,后面 2.2~2.5 是四个抽屉: 逻辑结构、物理结构、服务器结构、数据字典。 读这一篇只要求记住一句话: 实例负责算,数据库负责存。第 7 章管控制文件、第 8 章管表空间、第 16 章做备份, 管的都是「存」那一半。

一、一根线:从客户端到磁盘

一次查询的路是这样的:客户端 → 监听(1521)→ 服务进程 → SGA → 数据文件。 监听只负责「把电话转给谁」,转完就不管了; 真正干活的是服务进程,它拿着你的 SQL 去共享池里找有没有现成的执行计划,再去缓冲池里找数据块, 找不到才去读数据文件。 这条路上有三个可以断的地方: 监听没起(连不上)、实例没起(连上了但报 12560)、库没打开(报 01033)。 排障的第一步永远是判断断在哪一段。

二、实例:三块内存加一群进程

SGA(系统全局区)是所有会话共享的一块内存, 里面最大的两块是共享池(缓存 SQL 和字典)与数据库缓冲池(缓存数据块)。 PGA(程序全局区)是每个服务进程私有的,排序区、游标状态、绑定变量都在里面。 它不共享,所以人一多就吃内存。 后台进程是常驻的:DBWn 刷数据、LGWR 写日志、CKPT 记检查点、SMON 做恢复、PMON 清会话。 它们不等你敲命令,自己按节奏跑。

部件 共享还是私有 放什么
SGA 共享池 共享 SQL 文本、执行计划、数据字典缓存
SGA 缓冲池 共享 数据块(读过的、改脏的)
SGA 日志缓冲 共享 还没写进重做日志的改动
PGA 私有 排序区、游标、绑定变量

三、数据库:五类文件

数据文件存数据,控制文件记骨架,重做日志记改动,参数文件定启动行为, 密码/警告/跟踪文件管账号和排障。 这五类文件的地位不一样: 控制文件丢了,库直接不知道自己是啥;日志丢了,最近的改动救不回来; 数据文件丢了,反而最好救——有备份加日志就能重做。 所以备份的优先级不是「哪个大就备哪个」, 而是「哪个丢了最要命」。 第 7 章整章讲的就是前两类。

四、为什么非要分这么细

因为分开之后,每一层可以单独出问题、单独处理:实例崩了,重启就行,数据一点不少; 数据文件坏了,换一个文件恢复,实例不用重建。 如果做成「一个大文件」,任何一处坏掉整库就废了。 Oracle 这套分层,本质上是把「故障半径」切小。 代价是复杂。 装一个库要建几十个文件、记一堆参数、还得知道每个文件丢了会怎样。 这一章剩下的四篇,就是在还这笔复杂度。

五、动手 Demo:内存库与文件库:把「实例」和「数据库」分开演

sqlite3 有两个入口:':memory:' 和文件路径。 前者是纯内存,后者落在盘上——这正好对应「实例的内存」和「数据库的文件」。 实验很朴素:同样插五行,一个关掉就没了,一个关掉还在。 但这一下就把「为什么要分两层」讲明白了。

import sqlite3

# 一、纯内存库:等于「实例的内存」
mem = sqlite3.connect(':memory:')
mem.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)')
mem.executemany('INSERT INTO t VALUES (?,?)', [(i, 'v%d' % i) for i in range(1, 6)])
mem.commit()
mem.execute('select count(*) from t').fetchone()   # 5
mem.close()

# 二、文件库:等于「数据库的文件」
f = sqlite3.connect('data.db')
f.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)')
f.executemany('INSERT INTO t VALUES (?,?)', [(i, 'v%d' % i) for i in range(1, 6)])
f.commit()
f.close()

# 关掉再打开,数据还在
f2 = sqlite3.connect('data.db')
f2.execute('select count(*) from t').fetchone()     # 5
import os; os.path.getsize('data.db')              # 8192

实跑输出:

--------------------------------------------------------------------
第六课:体系结构——实例活在心里,数据库躺在盘上
--------------------------------------------------------------------

  **这一章是全书的地基。**
  **Oracle 的文档里,「实例(instance)」和「数据库(database)」是两个词,从来不说成一个。**
  **实例 = 内存 + 进程。数据库 = 磁盘上的一堆文件。**
  **两者可以分开存在:实例起着、库没打开(1.5 节的 mount);实例关了、文件还在盘上(数据没丢)。**


  先看两张表:谁在内存里,谁在盘上
  --------------------------------------------------------------------

  SQL> select name, where_, lose_on_restart from part
  NAME         WHERE_           LOSE_ON_RESTART
  --------------------------------------------------------------
  共享池       内存(SGA)      会丢,重启要重新解析 SQL
  数据库缓冲池 内存(SGA)      会丢,但数据本身在盘上,重读就有
  日志缓冲区   内存(SGA)      会丢,未提交的改动随之消失
  PGA          内存(进程私有) 会丢,排序结果、游标状态都没了
  后台进程     内存中的进程     会丢,重启重新拉起
  数据文件     磁盘             不会丢,这是数据的家
  控制文件     磁盘             不会丢,记着「库由哪些文件组成」
  重做日志     磁盘             不会丢,实例恢复靠它
  参数文件     磁盘             不会丢,启动时按它分配内存
  已选择 9 行

  用 SQLite 把「内存 vs 文件」演一遍
  --------------------------------------------------------------------
  **SQLite 有两个入口:`':memory:'` 和文件路径。**
  **前者就是一个纯内存库,后者落在盘上。**
  **这正好把「实例的内存」和「数据库的文件」分开演。**

  SQL> select count(*) as n from t
  N
  -
  5
  已选择 1 行
  **关掉这个连接,内存库就整块消失了——没有文件,没有残留。**
  **这就是「实例的内存」:拔电即无。**

  **换成文件库,同样关掉连接,再打开一次:**

  SQL> select count(*) as n from t
  N
  -
  5
  已选择 1 行
  **数据还在。**
  **因为它写进了文件——这份文件就是「数据库」。**
  文件大小 **8192** 字节

  所以「连接」到底连的是什么
  --------------------------------------------------------------------
  **连 Oracle,连的是实例**(要经过监听,由监听把会话交给实例);
  **连 SQLite,连的是文件**(没有中间人,直接开文件)。
  **这个差别解释了两件事:**
  - **Oracle 允许多人同时连一个实例**,因为实例只有一个、会话有很多个;
  - **SQLite 的并发靠文件锁**,写的时候整库一把锁,**所以它是「嵌入式」,不是「服务端」。**

  **一句话收尾:**
  **实例负责「算」,数据库负责「存」。**
  **算的那部分随时可以重建,存的那部分必须保护好**——
  **后面第 7、8、16 章讲的全是「怎么保护存的那部分」。**

内存库关掉就没了,这不是「丢数据」,是「本来就没存」。 Oracle 的 SGA 也一样:里面的脏块如果还没被 DBWn 刷进数据文件,实例一崩就没了—— 所以 Oracle 要靠重做日志把它补回来。SQLite 的内存库没有日志,崩了就是崩了。 文件库关掉再开,数据还在,行数还是 5。 注意它只有 8192 字节——两个页(4096 一页),表结构占一页、数据占一页。 「连接」连的是谁,两边完全不同: Oracle 连实例(多人共用一个实例,各有各的会话);SQLite 连文件(没有服务端,靠文件锁协调)。 这就是「服务端数据库」与「嵌入式数据库」的分界。 别把这段当成「SQLite 就是 Oracle 的简化版」。 它没有实例、没有后台进程、没有表空间——本课后半本讲的东西,它一大半演不了。 能用它演的,只有「SQL 层面」和「文件层面」的那部分。

30 秒实验

  1. 把内存库的代码复制一份,改成 sqlite3.connect('mem.db'), 再跑一次——这次 close() 之后文件还在,数据也还在。 同一段代码,只换了一个参数,行为完全不同。
  2. 在文件库上执行 delete from t 但不 commit,然后直接 close()。 重新打开看看数据还在不在——这就是「未提交的改动丢失」,对应 Oracle 里「日志没写就崩了」。
  3. 用 os.path.getsize 记录建表前后的文件大小,算出差了多少字节。 一页 4096 字节,两张表会多几页?

常见误区

  1. 把「实例」和「数据库」当同义词。 这是 Oracle 文档里最不能混的一对词, 后面所有运维命令都要靠这个区分:「startup 到 mount」是实例的事, 「recover datafile」是文件的事。
  2. 以为 commit 之后数据一定在数据文件里。 commit 只保证日志写到了盘(LGWR),数据块什么时候落盘由 DBWn 决定。这也是为什么提交很快。
  3. 以为实例一崩数据就全丢。 只要重做日志完好, 已提交的事务一条都不会丢——实例恢复会把它们重做一遍。丢的只可能是「没提交的」。
自测三问(点开对答案) 1. 实例崩了,已提交的数据会丢吗? —— **不会。** `commit` 时 LGWR 已经把日志写到盘上了, **实例恢复会用日志把数据文件补到崩溃前那一刻。** **丢的只有未提交的事务。** 2. 为什么 SQLite 没有「实例」这一层? —— **因为它是嵌入式库,进程就是应用自己**,没有独立的后台进程要管内存。 **没有常驻,就没有「实例」这个概念。** 3. 监听、实例、库,三层各报什么错? —— **监听没起:连不上(ORA-12541);实例没起:ORA-12560;库没打开:ORA-01033。** 三个错码对应三个层次。

下一篇把「数据库」那一半拆开:块、区、段、表空间四级逻辑结构。 那里会实测「一页能放几行」,把「块」这个抽象概念量成一个具体的数。

相关笔记