🏺 第1章 Oracle 19c 概述
📄 一句话概括

Oracle 的 startup 不是「打开一个文件」,它分三步:nomount、mount、open。 每一步能访问的东西都不一样,这直接决定了出问题时你能做什么。 关机也有四档,差别只在「等不等」。

📝 本章和前面几章的关系

这是第 1 章的最后一篇,也是第一次碰到「实例」这个概念。 上一篇说了三个入口,这一篇说的是入口打开之后,库处在什么状态。 这个三阶段模型是后面所有运维章节的地基:第 7 章改归档模式必须 mount、 第 16 章恢复要 mount、第 18 章闪回数据库也要 mount。不知道自己在哪一步,就不知道能敲什么。

一、三步开机:每步读一样东西

第一步,nomount:读参数文件,起实例。这一步只用到参数文件(spfile 或 pfile), 按里面的设置分配内存(SGA)、启动后台进程。 此时数据库还没被打开,只能查 v$instance 这类「实例级」视图。 第二步,mount:读控制文件。控制文件里记着数据文件、日志文件都在哪。 读完它,实例就知道「这个库由哪些文件组成」,但还没碰这些文件里的数据。 改归档模式、做恢复,都在这个状态下做。 第三步,open:打开数据文件和日志。这一步会校验文件头、检查一致性, 必要时做实例恢复(把日志重做一遍)。打开之后,数据字典可用,所有表都能查。

阶段 读了什么 能查什么 典型操作
nomount 参数文件 v$instance 建库、改内存参数
mount 控制文件 v$database、v$log 改归档模式、恢复
open 数据文件与日志 所有表 正常的增删改查

二、参数文件:spfile 和 pfile

pfile 是纯文本(initORCL.ora),能拿记事本改,改完要重启才生效。 spfile 是二进制(spfileORCL.ora),只能通过 alter system 改,改完可以只改内存、 只改文件、或者两边都改。 spfile 优先。 两个都存在时,Oracle 用 spfile。 这是很多人改完参数不生效的原因——他改的是 pfile,而系统在用的 spfile。 spfile 丢了怎么办? 能从这里救: create pfile from spfile; 反向也能 create spfile from pfile;。 所以运维的第一条备份不是备份数据,是备份参数文件和控制文件——它们小,但没了就起不来。

三、四档关机:差别只在等不等

normal:不许新连接,等所有人自己断开。最安全,也最可能等一辈子。 transactional:不许新事务,等当前事务提交完就断。折中方案,实际用得不多。 immediate:回滚未提交事务、断开所有会话、写检查点,然后关。这是日常用的那一条。 abort:立刻停,不回滚、不写检查点。 相当于拔电。 下次启动时实例要做恢复(用重做日志把没写完的补上)。 abort 不是「更快」,是「把代价推给下一次启动」。 而且恢复不一定成功——如果日志文件本身坏了,那就真丢了。 所以它只在一种场景下用:库已经卡死,immediate 也关不掉。

四、起不来的时候看什么

第一看告警日志(alert log)。 它记着实例启动的每一步, 路径在 $ORACLE_BASE/diag/rdbms/<库名>/<实例名>/trace/alert_*.log。 起不来时,最后二三十行就是答案。 第二看两个常见错误码:ORA-01113(数据文件需要恢复)、ORA-01033(库正在启动或关闭)。 前者说明文件头不一致,要 recover;后者通常只是还没 open 完。 第三看服务。 Windows 上如果 OracleServiceORCL 没起, sqlplus / as sysdba 会报 ORA-12560——这不是密码问题,是「根本没有实例在跑」。

五、动手 Demo:在 SQLite 上把三阶段跑一遍

SQLite 没有实例,所以严格说它没有 nomount 这回事。 但它有同样的层次:文件在不在 → 能不能读到结构 → 能不能查数据。 这三层正好对应「没连上 → mount → open」,拿来把概念演一遍最合适。 注意这只是类比,不是等价——差别在每一段的注释里。

import os, sqlite3, tempfile

path = os.path.join(tempfile.mkdtemp(), 'demo.db')
os.path.exists(path)              # False:库还不存在

# 第一步(相当于 NOMOUNT):连上去,库还是空的
c = sqlite3.connect(path)
c.execute('select count(*) from sqlite_master').fetchone()   # 0
c.execute('select * from emp').fetchall()                    # 报错

# 第二步(相当于 MOUNT):读到「有哪些对象」
c.execute("CREATE TABLE emp (empno INTEGER PRIMARY KEY, "
          "ename TEXT, sal REAL)")
c.execute("select name from sqlite_master where type='table'")

# 第三步(相当于 OPEN):数据可读
c.execute('select empno, ename from emp').fetchall()

实跑输出:

--------------------------------------------------------------------
第五课:启动与关闭——三步开机,四档关机
--------------------------------------------------------------------

  **Oracle 的 startup 不是「打开一个文件」那么简单,它分三步。**
  **每一步能访问的东西都不一样,这决定了出问题时你能做什么。**


  三步开机,每步能看到什么
  --------------------------------------------------------------------
  阶段      做了什么                                能干什么
  --------------------------------------------------------------------------------------
  NOMOUNT   读参数文件、起实例、分配 SGA            能查 v$instance,查不了任何表
  MOUNT     读控制文件,知道数据文件在哪            能查 v$database、能改归档模式
  OPEN      打开数据文件与重做日志,字典可用        能查所有表
  **这三步对应三条命令:**

  [Oracle 原码 · 本沙箱不执行]
  | STARTUP NOMOUNT;              -- 只到第一步
  | ALTER DATABASE MOUNT;         -- 到第二步
  | ALTER DATABASE OPEN;          -- 到第三步
  | STARTUP;                      -- 一步到 OPEN(最常用)


  在 SQLite 上把这三步演一遍
  --------------------------------------------------------------------
  **SQLite 没有实例,但它也有「打开了才知道」的层次:**
  **文件在不在 → 能不能读结构 → 能不能查数据。**
  临时库路径:`C:/Users/admin/AppData/Local/Temp/ora47_6stm08q6/demo.db`

  **第一步之前:文件还没有。**
  `os.path.exists(path)` → **False**
  **但这儿有个 SQLite 的脾气:连一个不存在的路径,它会顺手建一个空库。**
  硬连上去查结构 → **成功(0 个对象)**,文件当场被建了出来
  `os.path.exists(path)` 再看一次 → **True**

  **第一步(相当于 NOMOUNT):建实例,有内存,但库还是空的。**
  `select count(*) from sqlite_master` → **0**
  直接查业务表 → **报错 `no such table: emp`**
  **这就是「实例起来了、库还没打开」的样子:**
  **内存有了,字典里空空如也,查业务表必然找不到。**

  **第二步(相当于 MOUNT):读到控制文件,知道有哪些文件。**

  SQL> select * from sqlite_master where type = 'table'
  TYPE  NAME TBL_NAME ROOTPAGE SQL
  -----------------------------------------------------------------------------------------------
  table emp  emp      2        CREATE TABLE emp (empno INTEGER PRIMARY KEY, ename TEXT, sal REAL)
  已选择 1 行
  **SQLite 的 `sqlite_master` 就等于 Oracle 的控制文件加数据字典:**
  **它记着「这个库里有什么」。**

  **第三步(相当于 OPEN):数据可读可写。**

  SQL> select empno, ename, sal from emp
  EMPNO ENAME SAL
  ---------------
  7369  SMITH 800
  已选择 1 行

  四档关机,差别在「等不等」
  --------------------------------------------------------------------

  SQL> select mode, wait, scene from shut
  MODE          WAIT                       SCENE
  -----------------------------------------------------------------------
  normal        等所有人自己断开           不赶时间,最安全
  transactional 等当前事务提交完           想快点又不想丢事务
  immediate     回滚未提交事务,断开所有人 最常用
  abort         立刻停,不回滚、不写检查点 只有卡死时才用,下次启动要恢复
  已选择 4 行

  [Oracle 原码 · 本沙箱不执行]
  | SHUTDOWN IMMEDIATE;   -- 日常用这一条
  | SHUTDOWN ABORT;       -- 最后手段,起来后要做实例恢复

  **`abort` 相当于拔电。**
  **Oracle 靠重做日志把它救回来,但救的过程要时间,而且救不回来的部分就没了。**
  **所以它不是「更快」,是「把代价推给下一次启动」。**

  收个尾:这一章真正该记住的三句
  --------------------------------------------------------------------
  - **实例和数据库是两回事**,一个是内存加进程,一个是盘上的文件。
  - **三范式是为了少改几行**,不是越碎越好。
  - **startup 三步、shutdown 四档**,出问题时你在哪一步,决定了你能做什么。
  **后面五章讲的所有东西,都挂在这三句上。**

第一段有个反直觉的地方: 文件还不存在,可 sqlite3.connect(path) 却成功了, 而且顺手把文件建了出来。 SQLite 的设计是「连接即创建」,Oracle 完全不是这样——连不上就是连不上,不会替你建库。 第二段是真正的「nomount 类比」:库连上了、内存有了,但 sqlite_master 是空的, 查业务表直接报 no such table。这就是「实例起来了、库还没打开」的样子。 第三段对应 mount: 建完表再看 sqlite_master,能看到「这个库里有什么」。 SQLite 的 sqlite_master 一个人干了两件事—— 既是控制文件(记文件结构),又是数据字典(记表结构)。 Oracle 里这是两个东西,分别在第 2 章和第 7 章讲。 最后一段对应 open: 数据可读可写。 注意 SQLite 没有「校验文件头、做实例恢复」这一步—— 它出问题时的默认动作是「尽量读出能读的部分」,不是「拒绝打开」。 这个差别很重要:Oracle 宁可打不开,也不给你不一致的数据。

30 秒实验

  1. 把 sqlite3.connect(path) 改成 sqlite3.connect('file:' + path + '?mode=ro', uri=True), 再试一次写入。 只读打开会报 attempt to write a readonly database—— 这就是「库打开了但不让写」,对应 Oracle 的 read only 表空间。
  2. 在 sqlite3.connect 之后立刻 c.close(),再重新连一次,看数据还在不在。 在的——SQLite 每次 commit 就落盘,没有「实例内存里还没写」这回事。
  3. 去翻一翻 PRAGMA journal_mode 的取值(delete/wal/memory)。 wal 就是 SQLite 的重做日志,对应第 7 章要讲的 redo。

常见误区

  1. 以为 startup 就是「打开数据库」。 它其实是三个动作连做,中间每一步都可能失败, 失败在哪一步决定了你怎么救。
  2. 用 shutdown abort 当成「快速关机」。 它不回滚、不写检查点, 下次启动要做恢复,恢复期间库是不可用的。 日常一律用 immediate。
  3. 改完参数不生效,就以为参数写错了。 十有八九是改的是 pfile,系统在用的却是 spfile。 先 show parameter spfile 确认一下。
自测三问(点开对答案) 1. 要改归档模式,库至少要开到哪一步? —— **mount。** 归档模式记在控制文件里,**必须在读得到控制文件、又还没打开数据文件的状态下改**, 也就是 `startup mount` 之后。 2. `shutdown abort` 之后重启,Oracle 会做什么? —— **做实例恢复:** 用重做日志把已提交但没写进数据文件的事务重做一遍,把未提交的回滚掉。 **这一步是自动的,但库在恢复完之前不能用。** 3. `ORA-12560` 一般是哪一层的问题? —— **不是密码、不是权限,是「没有实例在跑」**——多半是 Windows 服务 `OracleServiceORCL` 没启动, 或者 `ORACLE_SID` 指错了。

第 1 章到这里结束。 下一篇进第 2 章:Oracle 的体系结构——把「实例」和「数据库」彻底分开讲, 再往下拆成逻辑存储(块/区/段/表空间)和物理存储(五类文件)。

相关笔记