从很多年前第一次接触Oracle数据库开始,我就觉得内存结构是一个特别容易被忽视但又特别值得花时间搞懂的东西。面试的时候会考,文档里会写,可真到了生产环境出了性能问题或者半夜收到ORA-04031告警的时候,光知道"SGA是共享池,PGA是私有区"这种程度根本不够用。这篇内容我打算从内存结构的全貌讲起,把SGA和PGA里各个区域到底管什么事、参数怎么配、出了问题怎么排查,一次性说清楚。无论你是刚接触Oracle的开发、需要维护数据库的运维,还是已经在管生产库的DBA,这篇都适合你。
1. 内存结构全景:SGA与PGA的分工,以及为什么必须分家
1.1 一次故障现场:内存问题为什么难排查
先说一个我早些年遇到的案例。当时有一套业务系统每到月底跑批就会变慢,而且毫无规律,有时候上午十点卡住,有时候下午三点卡住。刚开始以为是SQL问题,抓了AWR报告以后发现DB Time全在一个“latch: shared pool”等待事件上,而在Top 5事件里居然还有大量的“free buffer waits”。那个时候我对Oracle内存结构的理解还停留在“SGA是共享内存池”这个层面,完全没意识到跑批SQL的并发解析、频繁的全表扫描、磁盘排序这些操作其实都在悄悄撬动内存的分配逻辑。
后来把sga_target调大了一点,pga_aggregate_target也调大了一点,问题暂时缓解,但过了一个月又复发。这个经历让我意识到一个很扎心的事实:调内存参数本质上是在跟Oracle的内存管理逻辑打交道,如果你不知道它内部怎么分配、怎么回收、怎么在组件之间腾挪空间,那参数调大了也只是一时掩盖问题,甚至可能把别的组件挤爆。
从那时候起,我开始系统性地把SGA和PGA的每一个子组件重新过了一遍。这个文章也算是给自己这些年踩过的坑做一个梳理。
1.2 SGA和PGA各自管什么:用一家餐厅来类比
Oracle实例的内存可以简单分成两大部分:SGA(System Global Area,系统全局区)和PGA(Program Global Area,程序全局区)。
我习惯用一个餐厅来打比方。SGA是这家餐厅的公共大堂和厨房,所有服务员(服务进程)共享这一块区域。点菜单放在大堂的台面上,谁都能看,谁都能取用;炒好的菜也先放在出菜口,等服务员来取。PGA则是每个服务员自己随身带的小本子和围裙口袋里的小工具,只属于自己,别人碰不到,自己负责的客人所涉及的私密信息都记在自己的小本子上。
通俗点讲:
- SGA里放的是所有会话共享的数据:缓存的数据库块(Buffer Cache)、解析过的SQL和执行计划(Shared Pool里)、重做日志缓冲(Redo Log Buffer)、数据字典缓存等。
- PGA里放的是单个会话私有的数据:排序操作(Sort)、哈希连接(Hash Join)、位图合并这些操作的中间结果、游标状态、PL/SQL变量等。
这个"分家"的设计是有讲究的。如果所有会话都共用一块内存且没有任何隔离,那么一个大型排序操作就能把内存占满,其他会话连共享的数据块缓存都没地方放。PGA的隔离机制保证了会话之间的私有操作互不干扰。SGA的共享机制则保证了高频访问的数据库块、SQL游标可以被所有会话复用,不需要每个会话都从磁盘读一遍。
1.3 实例与内存的生命周期:从启动到关闭
Oracle实例启动的时候,会先读取参数文件(spfile或pfile),然后根据参数值去操作系统申请内存映射,构建SGA和后台进程。实例关闭的时候,SGA被释放,所有缓存的数据块都会失效,下次启动需要重新从数据文件加载。
这里有一个概念需要分清:数据库(Database)和实例(Instance)是两回事。数据库指的是物理文件(数据文件、控制文件、重做日志文件),实例指的是内存结构加后台进程。实例可以挂载并打开数据库,实例关闭了,数据库数据文件还在。我们常说的"启动一个实例",实际上就是在操作系统的内存地址空间里创造出一个SGA和一组后台进程。
内存结构的学习,我认为至少要抓住两个关键点:第一,SGA里各个组件是怎么协同工作的;第二,参数是怎么影响内存分配的。这两点搞清楚,后面看AWR、调优、排查问题,思路会清晰很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SGA里谁最吃内存?Buffer Cache与Shared Pool的底层工作逻辑
2.1 Buffer Cache:数据不会直接进内存,而是通过DB Buffer
数据库里最频繁的操作一定是数据的读取和修改。如果每一次读数据都直接去磁盘,那IO会成为绝对瓶颈。Buffer Cache(数据库缓冲区缓存)就是用来缓存的:它把从数据文件读出来的数据块放到内存里,下次再要读同一块数据,直接命中内存,不需要再走磁盘。
每个数据块在Buffer Cache里都会有三种状态之一:
- Pinned(固定):正在被某个会话修改或读取,其他会话暂时不能动它。
- Clean(干净):内容与磁盘一致,可以被换出。
- Dirty(脏):已经被修改但还没有写回磁盘,需要由DBWR(数据库写进程)后台写回。
这里有一个特别常见的误区。很多人以为"Buffer Cache越大,数据读取就越快"。其实不完全对。Buffer Cache大到一定程度后,命中率提升的边际效应很低。更重要的是,Buffer Cache是SGA里的大户,它
