
拿到这个标题我第一反应是想到那些深更半夜被电话叫起来处理数据库性能问题的场景。系统突然变慢登录服务器一看free -g里 swap 的使用率涨得离谱而 Oracle 的 SGA 占用却高得吓人。很多人第一反应是“内存不够”然后下意识去加内存、扩 swap结果问题依然反复出现。这个标题说小了是内存管理说大了其实是理解 Oracle 与操作系统之间如何协作的根本问题。如果你负责过任何一套生产环境迟早会撞上 SGA 和 swap 之间的这场“暗战”。这篇文章不聊虚的我会直接从内存分配机制讲起说明为什么 SGA 配置不当会触发 swap 的异常使用然后给出一套完整的诊断命令和优化方案。内容适合数据库管理员、系统运维以及刚接触 Oracle 调优的开发者数据量大、内存吃紧的环境尤其值得花十分钟看完。1. 先搞清楚 SGA 与 swap 在系统里的真实角色1.1 SGA 不只是“一块大内存”很多人一说 SGA 就想到SGA_MAX_SIZE好像把参数调大就万事大吉。实际上SGA 是一个组合体由多个独立的内存组件构成常见的包括DB_CACHE_SIZE缓存数据块的默认缓冲池也是 SGA 里最吃内存的部分SHARED_POOL_SIZE存放 SQL 游标、执行计划、数据字典缓存REDO_LOG_BUFFER重做日志的缓冲写事务日志时先经过这里JAVA_POOL_SIZE、STREAMS_POOL_SIZE、LARGE_POOL_SIZE等用于特定功能模块。从 Oracle 10g 开始引入了自动内存管理AMM即MEMORY_TARGET和自动共享内存管理ASMM即SGA_TARGET但这不代表我们可以完全放手不管。内核在分配 SGA 时通常会以 mmap 的方式映射到物理内存并且为了效率会尽量常驻。问题恰恰出在这里SGA 的设计初衷是“常驻物理内存”但操作系统并不一定买账。1.2 swap 的真正含义内存的“溢出区”swap 是操作系统把暂时不用的内存页挪到磁盘的一块交换空间。它不是 Oracle 专用的东西而是内核在物理内存压力下的一种“安全阀”。很多 DBA 对 swap 有误解觉得 swap 越大越好或者认为只要 oracle 进程用了 swap 就一定要消灭它。实际上 swap 的使用分两种情况一种是系统内存真正不足大量进程的页面被换出这是性能灾难另一种是系统在空闲时主动把部分很少访问的匿名页面换出去这是内核的正常行为特别是在vm.swappiness设置较高的时候。Oracle 的 SGA 被换出属于第二种情况的变种——本应常驻的页面被内核判定为“低热度”继而写入了 swap但 SGA 中的缓存一旦被换出再访问时就触发换入swap in。这个换入换出的过程就是性能“杀手”它比磁盘多块读的代价还要高得多。1.3 SGA 与 swap 产生联系的本质SGA 里的 buffer cache 被访问的频率极高如果这部分内存页被换到 swap意味着每个数据块访问都伴随一次磁盘 I/O数据库能不慢吗而之所以会发生换入换出通常是因为两个原因叠加SGA 设置过大导致操作系统物理内存余量不足内核被迫把 SGA 的页面当作回收对象或者是服务器上还有其他进程比如 PGA、应用服务、监听进程抢内存系统整体处于“紧平衡”一旦有突发内存申请就开始动 swap。搞清楚这个本质之后我们才能谈优化。否则就是无头苍蝇。2. 为什么 SGA 设置会牵动 swap 的敏感神经2.1 物理内存分配与页面回收机制先看一个基础模型。Linux 内核在分配内存时会经历几个层次优先从空闲页列表分配空闲不足时开始回收可回收页包括文件页缓冲page cache和可换出的匿名页如果回收仍然不够会触发 OOM 或者进行交换。Oracle SGA 通过shmget或mmap申请内存这些页面属于匿名页不是文件页。文件页回收代价低脏页写回磁盘即可而匿名页如果被回收必须先写入 swap所以内核一旦回收了 SGA 所在的匿名页swap 使用率就会上升。有一个细节很多人忽略Linux 内核的回收代码是很“机械”的它不会知道某个页面属于 Oracle SGA也不会知道这个页面可能即将被高频访问。它只看一个指标这个页面最近是否被访问过。如果 SGA 启动后长时间没有特定 region 的访问内核可能误判为“冷页面”而换出。这就是为什么有些系统明明内存没耗尽swap 里却出现了 oracle 进程的页面。2.2vm.swappiness参数的双刃剑vm.swappiness的默认值是 60含义是内核倾向于回收匿名页的程度。值越大内核越积极地把匿名页换出值越小内核越倾向于回收文件页缓存而不是动 swap。对于 Oracle 环境很多最佳实践建议把 swappiness 设置为 1 或 0但这有个陷阱如果完整设置为 0在内存极端紧张时内核可能优先回收 page cache导致磁盘读性能波动甚至影响文件系统元数据操作。新版本内核如 5.x建议最小值设为 1而不建议设 0。设置方法是sysctl -w vm.swappiness1 echo vm.swappiness 1 /etc/sysctl.conf需要注意的是此参数控制的是“倾向性”不是绝对开关。就算设为 1如果物理内存真不够该用 swap 还是得用。2.3 SGA 设置过大导致的内存黑洞很多系统是从物理机迁移到虚拟机上的内存参数还是沿用旧配置。比如原来物理机 256GSGA 设了 192G后来迁到 128G 的虚机上DBA 忘了调参。结果是系统启动后 free 立刻见底内核只能疯狂使用 swap最后整库卡死。这种情况我见过太多次不是说 SGA 不能大但必须建立在“物理内存足够”的前提下。2.4 SGA 过小的隐患也指向 swapSGA 设得小buffer cache 装不下工作集业务会频繁进行物理读。物理读超过一定阈值磁盘 I/O 队列拉高用户事务被阻塞最终也会导致会话堆积内存消耗飙升系统压力间接反映到 swap 使用率上。所以这个关系不是线性的“SGA 大 - swap 高”而是要找到一个平衡点。3. 实战诊断如何判断 swap 升高是否由 SGA 引发3.1 三步定位法面对 swap 升高我一般按照三个步骤定位第一确认操作系统整体内存水位。用free -g看 total、used、free、available重点看 available 而不只是 free因为 Linux 的 free 列并不包含可回收的缓存available 才是应用可用内存的估算值。第二确认 swap 使用趋势。用vmstat 2 10观察 siswap in和 soswap out列。如果 so 列持续大于 0说明系统正在主动换出页面这非常危险如果只是 si 偶尔出现可能只是瞬时抖动。第三步细化到进程级别。用top -Hp oracle_pid或者pidstat -r -p pid 2去查看 oracle 进程的 RSS对比其虚拟内存 VSZ。如果某个 oracle 进程的 RSS 远小于 VSZ极有可能有大量页面被换出。实际上我们可以写一个简单命令把占用 swap 最多的进程找出来for f in /proc/*/status; do awk /VmSwap/{sw[$1]$2} END{for (k in sw) print k, sw[k]} $f 2/dev/null; done | sort -k2 -rn | head -20这个命令遍历每个进程的 VmSwap 字段把 swap 占用最高的进程列出来。Linux 的/proc/pid/status中 VmSwap 表示该进程使用了多少 swap这个信息对定位“是不是 oracle 吃了 swap”非常直接。3.2 Oracle 侧的视图v$sgastat 和 v$pgastat在 Oracle 内部我们可以借助视图感知内存组成select name, bytes/1024/1024 as size_mb from v$sgastat where pool is not null order by bytes desc;再看 PGA 的情况select name, value/1024/1024 as value_mb from v$pgastat where name in (total PGA allocated,maximum PGA allocated,total PGA inuse);如果 PGA 持续增长且占用极高要小心排序、哈希连接等操作耗尽 PGA导致物理内存不够进而诱发 swap。3.3 用 sar 保留历史证据线上问题排查最怕没有历史数据。如果你提前开启了sysstat那么可以直接用sar -r回顾内存水位sar -r -f /var/log/sa/sa$(date %d -d yesterday) | grep -i swapkbswpused和kbswpfree两列会告诉你前一天 swap 占用情况。配合sar -B查看 pgpgin/pgpgout可以判断是否存在大量的页面换入换出。这套东西在没有监控平台的场景下就是救命稻草。4. 从根上优化让 SGA 不再“赖”在 swap 身上4.1 确定 SGA 的合理上限SGA 大小的设置没有万能公式但有个经过验证的静态参考单独一台数据库服务器上SGA PGA 的总和通常建议控制在物理内存的 50% 到 75% 之间如果操作系统还需要运行大量其他服务这个比例还要更低。为什么不能直接给到 90%因为 Linux 的文件系统缓存对 Oracle 也有正向作用。redo 文件的写入、数据文件的读入都需要经过 OS 缓存层完全挤压掉 page cache 会让数据库的物理 I/O 更慢。我一般建议数据库服务器上 SGA 最大值不超过物理内存的 70%且要为 PGA 预留同等大小的空间。看过太多 SGA 占 80% 然后 PGA 爆掉导致内存互换的案例了。实操中可以设置一个初始值然后用自动内存管理来自动调整比如alter system set sga_max_size96G scopespfile; alter system set sga_target96G scopespfile;但要注意sga_max_size是硬上限sga_target是动态调整目标。如果物理内存只有 128G目标也没必要设 96G。4.2 启用 HugePages规避 swap 的关键一招Linux 默认的内存页大小为 4KB管理一个 96G 的 SGA 需要约 2400 万个页表项TLB 根本覆盖不过来同时那些被换出的页也格外分散内核回收时开销巨大。启用大页通常 2MB后页表项数量骤减TLB 命中率大幅提高SGA 区域几乎不会成为操作系统的回收候选。配置大页的步骤不复杂但每步都容易出错查看当前大页配置grep HugePages_Total /proc/meminfo grep Hugepagesize /proc/meminfo修改/etc/sysctl.conf预留大页数量。假设想让 64G 的 SGA 全部落在大页上计算方式是 64GB / 2MB 32768 个页面再加上额外余量 100 个页面左右vm.nr_hugepages 32868 vm.hugetlb_shm_group 54321 # 替换为 dba 组的 gid应用或重启生效sysctl -p把数据库锁在大页上需要将memory_target和memory_max_target设为 0避免 Oracle 使用共享内存文件系统而绕过 HugePages。同时在 Oracle 11.2.0.3 及以上版本可以启用参数alter system set use_large_pagesonly scopespfile;use_large_pagesonly的意思很明确只允许使用大页如果没有足够大页实例直接启动失败而不是偷偷退回普通内存这能帮我们尽早发现问题。4.3 调整lock_sga把 SGA 钉死在物理内存另一个能彻底解决 SGA 换出问题的参数是LOCK_SGA它在 Unix/Linux 平台上会将整个 SGA 锁定在物理内存中。但要注意这个参数通常与 HugePages 搭配使用且操作系统必须允许相关用户锁定足够的内存。修改limits.conf里的memlock然后重启实例。oracle soft memlock unlimited oracle hard memlock unlimited如果启用了 HugePages 并把lock_sgatrue那么 Oracle 的共享内存基本不可能再被 swap这是最“稳”的组合。4.4 通过调整数据库缓存块大小优化局部性除了直接在 OS 层下手数据库层也有辅助手段。如果业务数据有明显的热点集可以用db_keep_cache_size设置一个独立的 keep 池把高频访问的表放到里面并用alter table ... storage(buffer_pool keep)指定。这样 BUFFER_CACHE 主池的压力会减小SGA 总体占用更可控内存“热区”更集中被换出的概率自然降低。4.5 别忘记 PGA 的调节PGA 是另一个内存大户。排序区、哈希区都从 PGA 分配如果很多会话同时做大的排序操作PGA 会瞬间暴涨把物理内存吃掉一大块。可以用PGA_AGGREGATE_TARGET限制总量并监控v$pgastat中over allocation count是否反复出现。如果经常出现 over allocation说明 PGA 限制过小或工作负载过大导致额外分配临时段间接引发内存和 I/O 压力最终也会反映到 swap。5. 常见问题与排错实录5.1 现象swap 使用率稳定增长但 si/so 很低这种情况最迷惑人。系统并不在进行大量换页但 swap 的 used 一直居高不下比如占了 20G。这通常意味着之前某个时段发生过内存压力部分页面被换出后一直没有被再次访问所以 si/so 降下来了但 swap 占用不会自动归还。排查方向看/proc/meminfo中SwapCached是否偏高。如果SwapCached高说明 swap 中有页面被读回内存但尚未释放可以用swapoff -a swapon -a来清空 swap但生产环境使用此命令要极其小心会导致所有进程再次换入内存水位瞬时飙升。稳妥做法是先调低 swap 使用重启数据库实例或逐步回收。5.2 现象启动数据库后操作系统内存迅速耗尽经常遇到有朋友说“数据库一启动free 就剩几个 G”。如果 SGA 已经占了 70% 物理内存而数据库服务器上还跑着 agent、监控、备份脚本等free 低是必然的。这里要提醒一句Linux 的 free 参数中available列比free列更有参考意义。只要 available 大于 20%系统大概率还能兜住不必过度紧张。但是如果 available 长期低于 10%就需要考虑收缩 SGA 或迁走非核心进程。5.3 现象设置了sga_max_size但物理内存没被完全利用多数情况下 SGA 是按需触碰的即只有访问过的内存页才会实际驻留在物理内存中。因此sga_max_size设了 96G并不代表开机就吃掉 96G 物理内存。有人看到这个现象误以为“参数没生效”其实是正常行为。要验证 SGA 是否在物理内存里常驻可以大量扫描数据后再次观察内存占用。5.4 场景速查表为了方便直接抄作业我整理一张排查表供参考现象可能原因优先检查处理思路swap 使用率突增数据库变慢SGA 过大或工作负载瞬间抬升vmstat 的 so 列top 的 oracle 进程 VmSwap调整 SGA启用 HugePages必要时重启实例swap 使用率不高但 si/so 频繁内存紧张或回收策略激进swappiness、pgpgin/pgpgout调低 swappiness检查 page cache 是否被过度回收数据库进程 RSS 持续增长PGA 被大量消耗v$pgastat、物理内存 available限制 PGA优化 SQL减少大规模排序启动后 free 极少但系统不卡大页未启用或 SGA 大面积触碰内存/proc/meminfo的 HugePages_Total启用 HugePages 减少页表开销多个实例共存时某实例特别卡共享内存分配不均各实例 sga_target、物理内存拓扑按实例业务量分配避免挤占5.5 独家避坑经验别把 swap 直接关闭很多人觉得 Oracle 用了 swap 就是不行于是干脆swapoff -a把 swap 彻底关掉。这条操作在低负载环境可能没事但在内存尖峰来临时内核没有缓冲直接触发 OOM Killer可能杀掉 oracle 进程。相比进程被杀swap 抖动反而是可以接受的。正确的做法是保留一个几 G 的 swap 分区作为最后的兜底同时用 HugePages 合理 SGA 让日常几乎碰不到 swap。5.6 一个实际调优案例的复盘这里分享一个简化版的实际案例方便理解整体流程。一台 64G 物理内存的数据库服务器SGA 设为 40GPGA 默认 4Gvm.swappiness保持默认 60。业务高峰期时 swap 使用率升至 8G数据库大量 buffer busy wait 和 log file sync。我当时的排查顺序是先free -g发现 available 只剩下 3G再用pidstat -r看到 oracle 进程 VmSwap 约有 5G确认 SGA 页面确实被换出。然后检查v$sgastat发现 buffer cache 的尺寸并没有问题真正原因是 swappiness 太高导致内核把共享内存页当普通匿名页换出了。处理方式分两步第一步临时调低vm.swappiness1并用echo 3 /proc/sys/vm/drop_caches清理文件缓存缓解内存压力第二步启用 HugePages给数据库的 SGA 分配大页内存然后修改use_large_pagesonly重启实例。调整后 swap 使用率回落到接近 0高峰期的 log file sync 不再出现。这个案例想说明的是SGA 与 swap 的问题往往不是单点参数错误而是 OS 层和数据库层多个配置互相叠加的结果。遇到问题别急着加内存先逐层排查理清因果再动手。6. 日常运维中值得坚持的几个习惯6.1 建立内存和 swap 的基线监控我不建议只用一套固定阈值去报警更好的做法是连续收集两周的内存和 swap 数据形成基线。比如某套系统平时 swap 使用率一直在 2G 以内某天突然到 6G即便绝对值不高也是预警信号。用 Prometheus、Zabbix 或者简单的 cron 脚本都行关键是数据要沉淀下来。6.2 升级或变更前先做内存评审在数据库实例参数变更、SGA 调整、服务器迁移之前先做一次简单的计算确认物理内存总量检查是否符合 SGA PGA OS 其他进程的需求验证 HugePages 数量和 SGA 的匹配度测试 swap 空间是否足够支撑极端场景观察变更后的vmstat和/proc/meminfo是否发生异常。这套“变更前评审”成本不高却能省下一堆半夜故障。6.3 定期检查大页利用率大页配了不代表一直在用。Oracle 12c 之后可以用lsnrctl status和grep HugePages等方式确认实例是否真正使用了大页。有些版本或迁移场景下sga 使用了大页但参数配置不全导致大页利用率只有一半。配合监控HugePages_Rsvd和HugePages_Free的变化就能及时发现配置漂移。7. 我个人在实际操作中的一些体会调优做多了你会发现 SGA 与 swap 的问题本质上是“内存资源归一化分配”的问题。数据库认为 SGA 是它的私有领地操作系统却把它看作可以回收的匿名页两者之间的错位就是故障的来源。每次处理类似问题我都提醒团队先把系统层面的数据看全再动数据库参数千万不能拿着“经验值”硬套。最后分享一个小技巧如果你不确定当前服务器的内存分配是否合理可以在业务低峰期手动触发一次内存回收测试。先记录 swap 使用再使用echo 1 /proc/sys/vm/drop_caches随后观察数据库性能尖刺和 swap 变化。这比在高峰期排查问题要安全得多。也提醒一句这个操作不要在业务高峰期做否则缓存的强制回收反而会有一点性能回调。这个“小动作”能帮你快速判断系统对内存回收的敏感度长期来看非常有用。