Oracle 架构与存储
Oracle 里要先区分两个概念:实例 Instance 和 数据库 Database。
零基础最容易把 Oracle 理解成“一个数据库进程加一堆表”。这不够。Oracle 的核心模型是:
实例负责运行,数据库负责存储。实例包含内存和后台进程;数据库包含数据文件、控制文件、Redo 文件等物理文件。SQL 通过实例执行,数据最终保存在数据库文件里。
SQL 从连接到返回的总流程
flowchart TD
A["客户端连接"] --> B["Server Process"]
B --> C["Shared Pool 查找或解析 SQL"]
C --> D["优化器生成执行计划"]
D --> E["Buffer Cache 读取数据块"]
E --> F{"是否修改"}
F -- "查询" --> G["基于 SCN 和 Undo 一致性读"]
F -- "更新" --> H["生成 Undo 和 Redo"]
H --> I["commit 时 LGWR 刷 Redo"]
G --> J["返回结果"]
I --> J这条链路后面会反复出现:Shared Pool、Buffer Cache、PGA、Undo、Redo、SCN、LGWR、DBWR。
实例和数据库
flowchart TD
A["Oracle Instance"] --> B["SGA 共享内存"]
A --> C["后台进程"]
D["Oracle Database"] --> E["数据文件"]
D --> F["控制文件"]
D --> G["Redo Log 文件"]
A --> D| 概念 | 说明 |
|---|---|
| Instance | 内存结构 + 后台进程 |
| Database | 数据文件、控制文件、redo log 文件等 |
实例启动后加载数据库,用户连接实例执行 SQL。
为什么要区分实例和数据库
| 问题 | 如果不区分会怎样 |
|---|---|
| 数据在哪里 | 以为数据在进程内存里,无法理解数据文件 |
| 宕机怎么恢复 | 无法理解实例没了但文件还在,可以通过 redo 恢复 |
| RAC 是什么 | 无法理解多个实例访问同一个数据库 |
| Data Guard 是什么 | 无法理解主备通过 redo 保持数据库文件一致 |
实例是运行时能力,数据库是持久化文件。实例可以关闭再启动,数据库文件仍然存在。
常见后台进程
| 进程 | 作用 | 为什么重要 |
|---|---|---|
| DBWn | 把脏数据块写回数据文件 | commit 不直接刷所有数据块 |
| LGWR | 把 Redo Log Buffer 写入 redo log 文件 | commit 是否成功依赖 redo 持久化 |
| CKPT | 触发检查点并更新控制文件/数据文件头 | 缩短恢复时间 |
| SMON | 系统监控,做实例恢复等 | 宕机后恢复相关 |
| PMON | 进程监控,清理异常会话资源 | 会话异常退出后释放资源 |
| ARCn | 归档进程,归档 redo log | 归档模式下支持恢复 |
记忆方式:DBWR 写数据块,LGWR 写日志,ARCn 管归档,SMON/PMON 做清理和恢复。
SGA 和 PGA
| 内存 | 作用 |
|---|---|
| SGA | 多个进程共享,包括 Database Buffer Cache、Shared Pool、Redo Log Buffer |
| PGA | 单个服务进程私有,用于排序、Hash、会话状态等 |
flowchart TD
A["SQL 执行"] --> B["Shared Pool 解析和缓存执行计划"]
A --> C["Buffer Cache 读取数据块"]
A --> D["PGA 排序 / Hash"]
A --> E["Redo Log Buffer 记录变更"]SGA 内部关键区域
| 区域 | 做什么 | 相关问题 |
|---|---|---|
| Database Buffer Cache | 缓存数据块和索引块 | 逻辑读、物理读、缓存命中 |
| Shared Pool | 缓存 SQL、执行计划、数据字典信息 | 软解析、硬解析、绑定变量 |
| Redo Log Buffer | 暂存 redo 记录 | commit、LGWR、日志写等待 |
| Large Pool | 大内存操作、备份恢复、并行等 | 大任务资源 |
PGA 做什么
PGA 是单个服务进程私有内存,常用于:
- 排序。
- Hash Join。
- 会话变量和游标状态。
- 连接会话的私有工作区。
如果排序或 Hash 数据太大,PGA 放不下,就可能使用临时表空间,导致 SQL 明显变慢。
Shared Pool、软解析和硬解析
Oracle 执行 SQL 前要解析。相同 SQL 如果能复用已有游标和执行计划,就是软解析;如果不能复用,需要语法检查、语义检查、权限检查、优化器生成计划,就是硬解析。
flowchart TD
A["SQL进入 Oracle"] --> B{"Shared Pool 有可复用游标吗"}
B -- "有" --> C["软解析"]
B -- "没有" --> D["硬解析"]
D --> E["语法、语义、权限检查"]
E --> F["优化器生成执行计划"]
C --> G["执行"]
F --> G为什么推荐绑定变量:
select * from asset where asset_no = :assetNo;如果应用每次拼接字面量:
select * from asset where asset_no = 'A001';
select * from asset where asset_no = 'A002';SQL 文本不同,游标复用率下降,Shared Pool 压力上升,高并发下可能出现大量硬解析。
表空间、段、区、块
flowchart TD
A["Tablespace 表空间"] --> B["Segment 段"]
B --> C["Extent 区"]
C --> D["Block 块"]
D --> E["Row 行"]Oracle 最小 IO 单位是块。理解块之后,才能理解索引扫描、全表扫描、Buffer Cache 和行迁移。
存储层级怎么理解
| 层级 | 类比 | 说明 |
|---|---|---|
| Tablespace | 仓库区域 | 逻辑空间,包含一个或多个数据文件 |
| Segment | 一类对象的存储 | 表段、索引段、Undo 段 |
| Extent | 一批连续块 | 空间分配单位 |
| Block | 货架格子 | Oracle 读写的基本数据块 |
| Row | 具体货物 | 表中的一行数据 |
查询一行数据,底层不是“只读一行”,而是读取它所在的数据块。一个块里可能有多行。
ROWID 是什么
ROWID 可以理解为 Oracle 定位一行物理位置的地址信息。普通 B-tree 索引叶子块保存 key 和 ROWID。查询通过索引找到 ROWID 后,再根据 ROWID 回表读取数据块。
flowchart TD
A["索引 key: asset_no"] --> B["找到 ROWID"]
B --> C["根据 ROWID 定位数据块和行"]
C --> D["读取表数据"]这解释了为什么返回行数少时索引快,返回行数非常多时,索引 + 大量 ROWID 回表可能不如全表扫描。
Redo 和 Undo
| 机制 | 作用 |
|---|---|
| Redo | 记录数据块变化,用于崩溃恢复 |
| Undo | 保存旧值,用于回滚和一致性读 |
flowchart TD
A["update 一行"] --> B["生成 Undo 旧值"]
A --> C["生成 Redo 变更记录"]
B --> D["支持回滚 / 一致性读"]
C --> E["支持崩溃恢复"]Undo 怎么支持一致性读
Oracle 查询开始时会确定一个一致性读 SCN。若查询过程中遇到的数据块版本比这个 SCN 更新,就需要通过 Undo 找旧值,构造出查询开始时能看到的数据版本。
flowchart TD
A["查询开始,获得 SCN=100"] --> B["读取数据块"]
B --> C{"数据块版本是否晚于100"}
C -- "否" --> D["直接读取"]
C -- "是" --> E["沿 Undo 找旧值"]
E --> F["构造 SCN=100 时的版本"]这就是为什么 Oracle 普通查询通常不会被正在更新的事务阻塞,因为查询可以读一致性旧版本。
Redo 怎么支持崩溃恢复
更新时数据块可能只在 Buffer Cache 中修改,还没写回数据文件。只要 commit 时 Redo 已经落盘,宕机后 Oracle 就可以根据 Redo 把已提交修改重做出来。
如果没有 Redo,数据块没来得及写盘就宕机,已提交事务就可能丢失。
SCN
SCN 是 System Change Number,可以理解为 Oracle 内部的一致性时间点。Oracle 用 SCN 判断数据版本、恢复位置和一致性读。
SCN 出现在很多地方:
- 查询一致性读。
- 事务提交。
- 数据文件头。
- 控制文件。
- 归档日志和恢复。
- Data Guard 主备同步。
可以把 SCN 理解成数据库内部的“逻辑时间戳”。它不是用户时间,但用来判断数据版本先后。
Commit 到底做了什么
flowchart TD
A["业务执行 update"] --> B["生成 Undo 旧值"]
B --> C["修改 Buffer Cache 数据块"]
C --> D["生成 Redo 到 Redo Log Buffer"]
D --> E["commit"]
E --> F["LGWR 刷 Redo 到 redo log 文件"]
F --> G["生成提交 SCN"]
G --> H["返回提交成功"]
H --> I["DBWR 后台写数据块"]重点:
- commit 必须保证 Redo 持久化。
- commit 不要求数据块马上写到数据文件。
- 数据块晚点刷盘没关系,因为 Redo 可以恢复。
- 如果 LGWR 慢,commit 会慢。
Checkpoint 是什么
Checkpoint 会推动脏块写盘,并记录数据库恢复到哪里。它的作用不是“提交事务”,而是控制恢复成本。
flowchart TD
A["Buffer Cache 中有脏块"] --> B["Checkpoint 触发"]
B --> C["DBWR 写部分脏块"]
C --> D["更新数据文件头和控制文件信息"]
D --> E["缩短实例恢复需要重做的日志范围"]Checkpoint 越频繁,恢复时可能更快,但运行时写盘压力可能更高;过少则恢复时间可能变长。
商业场景:为什么查询不阻塞更新
场景:资产采集任务正在更新设备状态,报表用户同时查询当天资产列表。
在很多数据库里,读写阻塞是高频问题。Oracle 的一致性读让普通查询可以基于查询开始时的 SCN 读取一致版本;如果某行已被更新但未提交,查询可以通过 Undo 看到旧版本。
这带来两个结果:
- 报表查询通常不会等更新事务提交。
- 长查询需要 Undo 保留足够久,否则可能出现
ORA-01555 snapshot too old。
所以长报表、大事务、Undo 保留时间是 Oracle 生产排查重点。
常见坑
| 坑 | 后果 | 正确理解 |
|---|---|---|
| 分不清实例和数据库 | 无法理解 RAC、恢复、文件结构 | 实例运行,数据库存储 |
| 以为 commit 刷数据块 | 无法解释 Redo 和 DBWR | commit 主要刷 Redo |
| 不用绑定变量 | 硬解析多,Shared Pool 压力大 | 高并发用绑定变量 |
| 忽略 Undo | 长查询报快照过旧 | Undo 支持回滚和一致性读 |
| 只看 SQL 不看等待 | 排查方向错误 | 结合执行计划和等待事件 |
面试标准回答
Oracle 架构要区分实例和数据库。实例由 SGA 共享内存和后台进程组成,数据库由数据文件、控制文件和 redo log 文件等组成。SGA 中有 Database Buffer Cache、Shared Pool、Redo Log Buffer,PGA 是进程私有内存,常用于排序、Hash 和会话工作区。Oracle 存储结构是表空间、段、区、块、行,块是重要的 IO 单位。查询通过 Shared Pool 解析和复用执行计划,通过 Buffer Cache 读取数据块;更新会产生 Undo 和 Redo。Undo 用于回滚和一致性读,Redo 用于崩溃恢复,SCN 用于标识数据库一致性时间点。commit 时 LGWR 刷 Redo 并生成提交 SCN,数据块可以由 DBWR 后台稍后写回数据文件。