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 秒实验
- 把内存库的代码复制一份,改成
sqlite3.connect('mem.db'), 再跑一次——这次close()之后文件还在,数据也还在。 同一段代码,只换了一个参数,行为完全不同。 - 在文件库上执行
delete from t但不commit,然后直接close()。 重新打开看看数据还在不在——这就是「未提交的改动丢失」,对应 Oracle 里「日志没写就崩了」。 - 用
os.path.getsize记录建表前后的文件大小,算出差了多少字节。 一页 4096 字节,两张表会多几页?
常见误区
- 把「实例」和「数据库」当同义词。 这是 Oracle 文档里最不能混的一对词, 后面所有运维命令都要靠这个区分:「startup 到 mount」是实例的事, 「recover datafile」是文件的事。
- 以为
commit之后数据一定在数据文件里。 commit 只保证日志写到了盘(LGWR),数据块什么时候落盘由 DBWn 决定。这也是为什么提交很快。 - 以为实例一崩数据就全丢。 只要重做日志完好, 已提交的事务一条都不会丢——实例恢复会把它们重做一遍。丢的只可能是「没提交的」。
自测三问(点开对答案)
1. 实例崩了,已提交的数据会丢吗? —— **不会。** `commit` 时 LGWR 已经把日志写到盘上了, **实例恢复会用日志把数据文件补到崩溃前那一刻。** **丢的只有未提交的事务。** 2. 为什么 SQLite 没有「实例」这一层? —— **因为它是嵌入式库,进程就是应用自己**,没有独立的后台进程要管内存。 **没有常驻,就没有「实例」这个概念。** 3. 监听、实例、库,三层各报什么错? —— **监听没起:连不上(ORA-12541);实例没起:ORA-12560;库没打开:ORA-01033。** 三个错码对应三个层次。下一篇把「数据库」那一半拆开:块、区、段、表空间四级逻辑结构。 那里会实测「一页能放几行」,把「块」这个抽象概念量成一个具体的数。
- ☐ (本篇完,下一篇 2.2 逻辑存储结构)