刚开始接触Oracle的时候绝大多数人都会被同一个问题卡住实例和数据库到底是不是一回事。我见过不少从MySQL转过来的开发张口就是“把数据库重启一下”结果敲完shutdown immediate之后整个人都懵了——因为Oracle会先关掉实例而你真正想重启的可能是那个物理数据库文件集合。这个概念的混淆几乎贯穿了日后的所有操作为什么startup报ORA-01034为什么监听器明明起来了还连不上为什么数据文件删了实例照样能启动要回答这些问题绕不开的就是Oracle体系结构里那个最经典的二分法实例Instance 数据库Database。这篇文章不是讲怎么装Oracle也不是罗列各种命令的文档而是想站在一个过来人的角度把Oracle体系结构这两块核心内容彻底拆开。你会搞明白SGA和后台进程到底在内存里干了些什么数据文件和控制文件在磁盘上各扮演什么角色以及一条SQL从客户端发出来之后是怎么在两个世界里穿梭的。适合刚入门Oracle的学生、从MySQL或其他数据库转过来的开发以及要考OCP或者做实施运维的朋友。1. 先搞清楚为什么Oracle初学者十个有九个卡在这里Oracle体系结构之所以绕是因为它把“数据”这件事拆成了两个完全独立的运行单元。数据库Database是落在磁盘上的那一堆文件包含数据文件、控制文件、联机重做日志文件它们负责把数据永久保存下来。实例Instance则是跑在内存里的一套东西由系统全局区SGA和一组后台进程组成它负责把磁盘上的数据变成可以被SQL操作的状态。用大白话说数据库是存东西的仓库实例是仓库里干活的员工和管理系统。仓库在不在员工都得上班员工上班了仓库里的货才能被搬来搬去。问题在于在MySQL里这两者是绑死的你启动一个服务就是在操作一个实例加一组文件平时根本不用区分。Oracle偏要把它们设计成可分离的原因在于它有“一个数据库被多个实例同时装载”的需求这就是后面提到的Oracle真正应用集群RAC多台服务器的实例同时访问同一套数据库文件。反过来一个实例同一时刻也只能装载一个数据库。所以当你听到“启动数据库”这个说法时严格来说是不完整的startup命令执行后Oracle先是根据参数文件启动实例然后让实例去装配数据库文件。如果实例没起来后面的步骤全是空谈。这也是为什么排障的时候要养成分层判断的习惯先看实例状态再看数据库状态。初学者最典型的懵圈场景是在SQL*Plus里敲startup系统弹出ORA-01034: ORACLE not available然后围着参数文件、权限、磁盘空间排查半天实际上问题往往很简单——要么实例已经处于启动的某个中间状态要么环境变量根本没指到正确的实例。我在带项目的时候习惯举一个类比把数据库当成一个巨大的图书仓库实例就是仓库门口的登记台和一批搬运工。图书数据存在仓库的货架数据文件上搬运工后台进程从货架上取书放到阅览室SGA里的缓冲区供读者查阅同时把新批注记录到登记本重做日志上。读者走了之后登记员要在合适的时候把改动回写到货架。仓库一直摆在那里但如果没有登记台和搬运工读者就没法借阅。反过来登记台和搬运工开着但仓库门锁了数据库没open读者同样进不去。这个类比虽然简单但基本上把实例和数据库的关系讲透了。2. 实例SGA和一堆后台进程共同搭起来的“运转中枢”实例这个概念看起来就是两个词内存和进程但展开之后内容非常多。整个实例的核心是SGA它在实例启动时被分配实例关闭时释放。SGA是Oracle手里最大的一块内存里面存着数据块的副本、SQL的执行计划、数据字典的缓存、重做日志的内容等等。在讨论实例时一定要分清楚SGA和后面要提到的PGA的区别前者是进程共享的公共区域后者是单个进程私有的工作区。下面先说SGA的组成。2.1 SGA里的核心区共享池、缓冲区缓存和日志缓冲区共享池Shared Pool可以理解为Oracle的“中央食堂”。执行过的SQL文本、解析出来的执行计划、数据字典信息都放在这里。Oracle在收到一条SQL时先在共享池里找有没有相同的SQL文本如果找到就直接用现成的执行计划省掉硬解析的昂贵代价这是后面理解“绑定变量为什么能提升性能”的基础。共享池里最常被吐槽的就是SQL写得五花八门一条SELECT * FROM t WHERE id1和WHERE id2会被当成两条语句来解析原因就在于共享池按文本哈希判断是否命中。实际项目里让开发统一SQL书写风格不是为了强迫症而是为了共享池的命中率。数据库缓冲区缓存Buffer Cache是SGA里可能最大的一块。数据在磁盘上最小单位是数据块Block缓冲区缓存放的就是数据块的副本。执行select时不需要直接去磁盘读而是先到缓冲区里找“有没有人把这块数据块搬过来了”没有就去磁盘读进来。执行修改时也是在缓冲区里改改完的数据块被称为“脏块”Dirty Buffer脏块要等合适的时机由数据库写进程DBWn写回磁盘。这块区域的大小直接决定了物理读和逻辑读的比例所以监控数据库时缓冲区命中率是一个逃不开的指标。重做日志缓冲区Redo Log Buffer是个容易被忽略的小角色但它身负重任。Oracle在做任何修改之前先把“修改了什么”写进日志缓冲区再由日志写进程LGWR快速写到联机重做日志文件。这个“先日志、后数据”的顺序叫Write-Ahead Logging是整个数据库恢复机制的根基。你可以把日志缓冲区看成仓库门口的来客登记本进出先登记之后货架怎么搬都行。除了这三个核心区SGA里还有大型池Large Pool供备份恢复和并行执行用Java池和流池供特定功能使用。初学阶段不必研究得很细但心里要清楚SGA不是一块单一的内存它里面的每个区域承担的工作完全不同配内存参数时也要区别对待。2.2 后台进程真正干活的那些“打工人”实例的另一半是后台进程。Oracle的进程模型在不同操作系统上略有差别Windows上表现为线程但概念上统一叫进程。日常DBA抓包时经常看到一堆进程名核心的几个必须认全。DBWn数据库写进程把缓冲区里的脏块写回数据文件它并不等你提交而是按检查点、缓冲区空间压力等因素批量写。LGWR日志写进程把重做日志缓冲区里的记录写到联机重做日志文件事务提交时它会同步触发写盘。CKPT检查点进程负责更新控制文件和数据文件头记录检查点SCN信息配合DBWn把实例恢复的起点往前推。SMON系统监控进程负责实例恢复、临时段清理和表空间合并等“大扫除”类的活。PMON进程监控进程监视其他后台进程处理异常断开的会话释放资源把实例服务动态注册到监听器。ARCn归档写进程在归档模式下把写满的联机重做日志文件复制到归档目录里形成完整的历史日志链条。这些进程的分工可以在一次实例意外断电后的恢复过程中看得清楚。重读日志就是靠LGWR留下的重做记录把缓冲区里没来得及写回数据文件的修改重放一遍SMON负责组织这件事。如果没有“先日志后数据”的机制断电后根本不知道哪些脏块没落盘。2.3 PGA会话私有的“工位”与SGA相对的是进程全局区PGA。每次有会话连接Oracle都会为该会话分配私有的PGA排序、哈希连接、位图合并这些操作是在PGA里做。比如执行一个order byOracle先把结果集读进PGA里排序排好之后才返回给客户端。PGA里的内存也受参数控制如PGA_AGGREGATE_TARGET只是在会话级不共享。有人容易把共享池和PGA搞混记住一句SGA里大家共用PGA里各干各的。实操里DBA在观察实例内存时一般先看SGA的各区使用率再看PGA有没有发生磁盘排序。比如v$sort_usage里有大量数据说明PGA不足排序落到了临时表空间上SQL自然就慢了。3. 数据库磁盘上一组文件组成的“物理仓库”聊完实例再来看数据库这套文件集合。Oracle数据库的文件分三类核心文件外加几个重要附属文件。新手最容易把控制文件和数据文件混为一谈实际上它们的职责完全不同。3.1 三类核心文件控制文件、数据文件、联机重做日志文件数据文件Data File是真正存表数据、索引、回滚段的地方一个数据库逻辑上可以有若干表空间每个表空间对应一个或多个数据文件。你可以把数据文件想象成仓库里的货架所有业务数据最终都放在这些货架上。注意SQL里操作的表在逻辑上位于表空间物理上落在数据文件这种“逻辑与物理分离”的设计是Oracle和很多其他数据库的重要区别。控制文件Control File可以说是数据库的“总目录”。它虽然小却记录了数据库的名字、创建时间、所有数据文件和联机重做日志文件的位置、当前检查点SCN、归档日志序列号等关键信息。控制文件极其重要所以Oracle强制要求至少维护两个副本实际上默认会创建三份分布在不同的磁盘位置任何一个丢失都可能让整个数据库无法正常open。联机重做日志文件Online Redo Log File记录了所有对数据库块的修改“流水”。它一般至少分成两组每组一个或多个成员LGWR按顺序往当前组写写满后发生日志切换下一组成为了当前组。日志切换在归档模式下会触发ARCn把历史日志备份成归档日志。如果联机日志损坏且没有镜像恢复会非常棘手所以生产环境里联机日志组至少双成员这个参数在建库的时候就要规划好。3.2 参数文件、密码文件和归档日志数据库的“周边配套”参数文件spfile/pfile决定了实例启动时的行为它记录各种初始化参数如SGA大小、数据库名字、DB_BLOCK_SIZE等。启动实例的第一步就是读参数文件这也是为什么要分清楚二进制参数文件SPFILE和文本参数文件PFILE两者优先级和修改方式不同。修改spfile里的参数时用alter system set xxxyyy scopeboth这个后面实操会提到。密码文件orapwd用于远程管理员登录认证存放在数据库的$ORACLE_HOME/dbs目录下。遇到远程DBA无法用SYS身份登录时十有八九是密码文件和操作系统权限的问题。归档日志Archive Log不是常驻的运行文件但它是备份恢复的命根子。在线日志一旦切换并被覆盖未归档的历史日志就无法复原数据库就只能恢复到归档日志覆盖的时间点。生产环境安全起见必须开归档模式并且把归档目录放到和联机日志不同的磁盘上。3.3 逻辑结构与物理结构的对应关系表空间Tablespace是逻辑存储单位段Segment对应表或索引区Extent是一组连续的数据块Block数据块是最小的I/O单位。这些逻辑概念最终都映射到数据文件的具体偏移上。不说透这块好多人在做一些表空间扩容时不知道为什么要加数据文件其实是因为表空间空间用完而一个表空间可以由多个物理数据文件组成。我推荐新手把“表空间—段—区—块”这条链记成表空间是一套房子逻辑上数据文件是房子的砖块物理上段是房间每个表一个房间区是房间里摆的柜子块是柜子里的抽屉。查询表数据时Oracle根据段头信息找到区再定位到数据块并做块级别的缓冲。很多时候性能问题出在数据块争用上比如缓冲区里一个块同时被多个会话修改业务表批量更新时块级锁竞争严重这些都要回到物理结构层面去理解。4. 实例与数据库的绑定启动的三个阶段和一条SQL的旅程前面把两个概念拆开了现在要把它们拼回去。搞懂启动的三个阶段就能真正理解startup命令在做些什么也能理解为什么某些报错只发生在特定阶段。4.1 启动三阶段Nomount、Mount、Open第一阶段是NomountOracle读取参数文件按参数分配SGA启动后台进程。这一阶段实例已经存在了但跟数据库文件还没有发生任何关系。如果报错在这一步往往和参数文件、内存分配、操作系统环境有关。第二阶段是Mount实例根据控制文件里的信息载入数据库的元数据但数据文件和联机日志文件还没有打开。这个阶段是DBA的维护窗口很多恢复操作选择在此阶段执行比如重命名数据文件、重做日志切换后的介质恢复等。做这些操作前其实并不需要数据库处于打开状态。第三阶段是Open实例打开所有数据文件和联机重做日志文件数据库进入可用状态用户可以正常访问。如果这一步报错通常是数据文件、日志文件缺失或损坏或者是SCN不一致需要恢复。把三阶段理清楚后平时听到的一些说法就明白了“重新启动数据库”是关闭shutdown后重新走一遍三个阶段的完整过程而“把库改到mount状态”只是停到第二阶段并不会动数据文件。4.2 一条UPDATE语句从客户端到落盘的全过程一开始学体系结构时最高效的办法就是跟着一条UPDATE语句走一遍因为这条路径几乎把每个组件都用上了。假设有一个会话执行了UPDATE t_acc SET balance balance - 100 WHERE id 1;这条语句到达实例后大概经历以下步骤服务器进程收到SQL在共享池中进行语法和权限校验生成执行计划并把SQL和执行计划缓存到共享池。进程在数据库缓冲区缓存中查找 id1 所在的数据块。如果不在缓冲区里就从数据文件读入缓冲区。在修改之前Oracle先在回滚段Undo Segment中记录旧值balance的原始值这部分操作也通过缓冲区完成回滚段相关块同样在缓冲区中。数值被修改当前数据块变成脏块存放在缓冲区同时重做日志缓冲区中记录了这次修改的重做记录包括旧值、新值和块号等信息。客户端发出COMMIT时LGWR把重做日志缓冲区里的记录写入联机重做日志文件写成功后才向客户端返回提交成功信号。之后DBWn在某个合适的时机检查点、缓冲区压力、表空间离线等把脏块写回数据文件。从这个过程中能看到几个关键点提交动作本身不保证立刻写数据文件但日志必须先落盘脏块不是马上刷回磁盘的这样设计是为了减少随机小I/O让日志顺序写更快数据批量写更高效回滚段保证了事务的原子性和undo信息是Oracle实现读一致性的重要机制。我给新人的建议是把这条UPDATE路径画一遍在图上标注每个步骤涉及的SGA区域、后台进程和磁盘文件这张图就是Oracle体系结构的“目录页”。我当年就是这么学下来的后来排查问题时第一反应永远是定位“这条SQL现在走到哪一步了”。5. 用几个SQL把体系结构“摸”一遍理论知识说再多都不如动手查一遍来得扎实。Oracle提供了一系列数据字典视图和动态性能视图专门用来观察实例和数据库的运行状态。以下是我常用来“摸底”的几个命令。5.1 查看实例状态和内存分配-- 查看当前实例的基本信息 SELECT instance_name, host_name, version, status, startup_time FROM v$instance; -- 查看SGA总大小和各个组件尺寸 SHOW SGA; -- 查看SGA详细分配 SELECT component, current_size, min_size FROM v$sga_dynamic_components WHERE current_size 0;这里面instance_name对应实例的名字status是实例启动状态通常是STARTED只能做Nomount/Mount阶段、MOUNTED、OPEN这三种。如果实际数据库处于Open但v$instance.status显示OPEN那就正常。SHOW SGA在SQL*Plus里执行很方便会直接列出Fixed Size、Variable Size、Database Buffers、Redo Buffers等几块。很多人第一次看到Variable Size会误以为它只是个软性大小其实它主要由共享池、大池、Java池等动态组件组成。5.2 查看数据库文件和控制文件信息-- 查看数据库当前状态 SELECT name, dbid, created, log_mode, open_mode FROM v$database; -- 查看数据文件和联机重做日志文件 SELECT file#, name, status, bytes/1024/1024 AS size_mb FROM v$datafile; SELECT group#, member FROM v$logfile; -- 查看控制文件路径 SELECT name FROM v$controlfile;v$database里的log_mode是ARCHIVELOG还是NOARCHIVELOG直接关系到备份策略能不能做时间点恢复open_mode是READ WRITE还是READ ONLY含义好理解。v$datafile能看到每个数据文件的路径、状态和大小平时遇到表空间满了第一件事就是看数据文件头目录在哪好决定加数据文件还是调整自动扩展。v$logfile的结果里如果某个成员状态是INVALID或STALE说明那份日志可能有问题需要立即关注。5.3 查看后台进程和当前会话-- 查看后台进程 SELECT pid, spid, program, pname FROM v$process WHERE pname IS NOT NULL ORDER BY pname; -- 查看当前会话连接情况 SELECT sid, serial#, username, program, status FROM v$session WHERE username IS NOT NULL;v$process中pname是Oracle内部的后台进程名比如PMON、SMON、DBW0、LGWR、CKPT数字结尾表示启用了多个DBW进程。用这个视图能看到每个进程对应的操作系统进程号spid配合ps -ef可以在操作系统层面追踪进程开销。v$session能看到当前有哪些用户在连接、会话状态是ACTIVE还是INACTIVE。排查“数据库hang住了”的时候大多数人第一步就是查v$session和v$process看是哪个会话在哪类等待事件上卡住了。5.4 熟悉几个动态性能视图的组合用法动态性能视图以v$开头是Oracle自启动后从内存里实时读出来的不需要额外授权普通用户只有访问某些视图的权限。我自己习惯用这样一组组合来快速定位一个会话的“工作现场”SELECT s.sid, s.username, s.program, sw.event, sw.wait_time, sw.state FROM v$session s LEFT JOIN v$session_wait sw ON s.sid sw.sid WHERE s.username IS NOT NULL;等待事件是Oracle体系结构中非常重要的一部分。会话在做SQL操作时如果长时间停留在同一种等待事件上就说明它在某个组件上受阻。比如大量会话卡在db file sequential read通常是指针往单个数据块读取性能瓶颈多指向索引扫描或数据文件I/O卡在buffer busy waits说明缓冲区里有热块多个会话争抢同一个块卡在enq: TX - row lock contention八成是行锁等待。这些名词如果放在体系结构框架里看会非常清晰因为它们本质上就是在SGA或I/O的某个环节上排队。6. 常见问题与排查技巧实录我见过不少朋友学完体系结构之后还是不会用它来排查问题。这里挑几个典型的坑每一个都能在体系结构里找到对应解释。6.1 ORA-01034实例没起来还是数据库没open这个报错几乎是入门第一道槛。报错信息是ORA-01034: ORACLE not available侧面常跟着ORA-27101: shared memory realm does not exist。从体系结构的角度看就是连接时没有可用的实例。排查思路很简单先用ps -ef | grep smon看看有没有SMON进程看看监听器状态然后以sqlplus / as sysdba登录执行startup。如果启动时报内存分配错误就是SGA参数设置超过了系统可用内存如果startup成功但报控制文件或数据文件错误就进入下一类问题。这里有个细节sqlplus / as sysdba登录后即使此时没有实例在运行也可以执行startup。因为以sysdba身份登录时Oracle会启动一个“预实例”环境然后startup基于它完成Nomount阶段。很多新手以为必须有一个现成的实例才能用SQL*Plus登录其实不是这也算体系结构带来的一个隐晦知识点。6.2 ORA-01157控制文件里的信息与物理文件对不上典型报错是ORA-01157: cannot identify/lock data file - see DBWR trace file ORA-01110: data file 1: /oradata/...报错背后是数据文件缺失、文件权限不对或者是文件头损坏。根据三阶段理论这个报错基本发生在Open阶段因为Open阶段要打开所有数据文件。处理时先alter database datafile ... offline;或恢复文件然后重新open。如果目标文件已经不存在且没有备份就只能做不完全恢复这是DBA最不愿面对的情况。理解了这个机制就不会在实例没起来的时候瞎找数据文件。6.3 监听器起来了但连不上实例没有注册到监听这台电脑的数据库明明开着客户端报ORA-12514: TNS listener does not currently know of service requested in connect descriptor。去找监听器发现监听器状态正常但listener.ora里没有静态注册条目。问题的本质是实例启动时PMON没有把服务注册到监听器或者注册信息还没生效。解决办法是在SQL*Plus里执行alter system register;或重启监听器优先级顺序和体系结构也有关实例注册依赖PMONPMON是实例的一部分所以数据库必须处于open状态才能动态注册。如果你只是改了listener.ora记得检查服务名是否匹配v$services里的服务名用lsnrctl status查看监听器实际认识的service列表能快速定位是注册问题还是连接描述符问题。6.4 Oracle慢在哪里从体系结构看等待每次有同事拿着一个“数据库慢”的问题过来我都引导他先判断慢在哪个环节。如果用体系结构来分类大致可以分成四类慢的现象可能的体系结构环节排查视图/手段CPU使用率极高共享池SQL解析频繁、执行计划差v$sql、v$sqlarea、AWR报告I/O压力大数据文件读取慢、物理读过多v$filestat、v$session_wait内存不足缓冲区命中率低、PGA排序落盘v$buffer_pool_statistics、v$sgastat锁等待会话阻塞、回滚段争用v$lock、v$session里blocking_session判断方式也很朴素先看v$session里活动会话的当前等待事件再看是CPU还是I/O类等待然后顺着体系结构去找对应的组件。我从没见过一个查不出原因的慢数据库只看你愿不愿先画一遍“SQL走到哪一步了”的图。7. 体系结构学完之后下一步该学什么如果这篇文章的内容你能吃透接下来再学Oracle的其他方向就会顺手很多。存储过程本质上是在SGA里反复解析对象分页查询绕不开排序和PGA变长数组属于PL/SQL集合类型实际操作时也离不开内存分配甚至做Oracle EBS WIP相关接口开发时理解表结构和事务的边界也依赖体系结构里事务与回滚段的知识。可以说体系结构是整个Oracle技能的底盘。我个人的体会是不要一上来就背命令先把“实例是内存进程数据库是文件集合”这句话用自己的话复述出来然后能对着v$instance、v$database、v$process解释为什么看到的东西是这样就已经赢了大半。剩下的无非是在日复一日的排障和优化中把这张体系结构的图越画越细而已。