值班手机响起来那一刻我其实已经猜到了七八分。电话那头的声音很急“数据库起不来了报了一个ORA-27104shared memory相关的。”我打开笔记本连上跳板机看了一眼告警群里贴出来的alert日志果然——ORA-27104: system-defined limits for shared memory was misconfigured。这个报错对DBA来说不算陌生但它坑过的人绝对不少尤其是刚接触Oracle的运维同事第一次撞上时很容易一头雾水明明安装的时候好好的怎么重启一下就起不来了或者明明只是把SGA调大了一点实例就直接罢工了这个报错的核心是操作系统共享内存参数和Oracle实例内存需求不匹配本质是shmget()系统调用被内核拒绝。它无关Oracle License无关字符集无关数据文件损坏纯粹是Linux/Unix系统层面的限制在捣乱。写这篇东西是想把ORA-27104从现象到原理、从排查到修复完整讲清楚包括HugePages、12c自动内存管理、容器环境这些进阶场景下的变体问题。新手照着做能恢复数据库老手也能从排查思路上对照一下自己有没有漏掉什么细节。1. ORA-27104不是偶然故障先搞懂它在说什么1.1 报错长什么样最容易撞上的是哪些人先看一个典型的报错现场。用sqlplus启动实例时命令敲下去之后没有出现熟悉的“Database opened”而是直接弹出一段很长的错误SQL startup ORA-27104: system-defined limits for shared memory was misconfigured如果去翻alert日志通常会看到更完整的信息类似ORA-27104: system-defined limits for shared memory was misconfigured Additional information: 1236363264这个“Additional information”后面的数字很关键它通常是Oracle尝试分配的共享内存段大小单位是字节也可能是触发失败时的相关值。先记住这个数字后面排查时用得上。我总结下来ORA-27104最容易出现在三类场景里第一类新装数据库后的首次启动。系统镜像默认的内核参数往往只满足最低要求安装完Oracle软件后一启动实例就挂了。第二类调大SGA或memory_target之后重启实例。这种最常见很多运维为了性能把SGA从4G调到8G结果重启直接起不来。第三类操作系统重启后数据库起不来。这种情况通常是之前有人改了/etc/sysctl.conf但没执行sysctl -p让它生效或者重启后参数被云平台初始化脚本重置了。在这三类场景里DBA和系统运维的相互扯皮也很常见——DBA说系统参数不够运维说之前一直好好的为什么你的数据库要这么多内存。其实两边都没说错问题就是Oracle要的和系统给的出现了断层。1.2 底层原理Oracle为什么非要碰“共享内存”要理解这个报错得先知道Oracle在Linux上是怎么使用内存的。Oracle的SGASystem Global Area系统全局区是整个实例的核心内存区域里面的buffer cache、shared pool、redo log buffer等组件都需要所有后台进程和服务器进程共同访问。SGA在物理上就是操作系统的一块共享内存通过shmget()系统调用创建然后用shmat()挂载到进程地址空间。这个设计有明确的历史原因在Oracle的进程架构下所有进程必须能同时访问同一份数据缓冲区。如果每个进程都复制一份buffer cache那内存消耗是不可想象的而且完全无法保证数据一致性。用共享内存本质上就是让一个进程写、所有进程都能读。Linux内核为了控制共享内存的使用设了几个关键参数。最直接的约束就是kernel.shmmax单个共享内存段的最大字节数kernel.shmall系统范围内共享内存页的总数上限Oracle启动时会根据sga_max_size或memory_target的值尝试用shmget()申请一块共享内存。如果申请的大小超过了shmmax或者累计页数超过了shmallshmget()就会返回失败errno通常是EINVALOracle再把这个系统错误包装成ORA-27104抛出来。打一个生活化的比方共享内存就像一栋大楼里的会议室shmmax规定的是“单个会议室最多能坐多少人”shmall规定的是“整栋楼所有会议室加起来最多能坐多少人”。Oracle是那种动不动就要包一个大会场的主儿它说要坐500人结果你们楼里最大的会议室只能坐200人那它当然进不来只能报错。有时候单个会议室够大但整栋楼已经被其他会议占满了它同样进不来。1.3 先分清责任边界是数据库配置问题还是系统限制问题排查ORA-27104第一步不是改参数而是判断责任边界。因为同样是这种报错可能是两个完全相反的根源一个是Oracle需要的太多一个是系统给的太少。这两个方向的解决方案完全不同。我见过一个案例某系统把sga_max_size设成了物理内存的80%然后再加PGA总需求直接把物理内存干爆了。这种时候哪怕把shmmax调到无穷大也没用因为物理内存根本不够Oracle就算拿到了共享内存段后续也会因为内存压力导致性能灾难。反过来另一些案例里物理内存明明有64GSGA只设了8G但系统的shmmax默认只有32MB这明显就是系统参数太低需要把shmmax提上去。所以接到这个报错后心里先过一遍这台机器物理内存多大Oracle的SGA或者MEMORY_TARGET设了多少系统的shmmax和shmall当前是多少这三个数字一对问题基本就浮出水面了。2. 一套可复制的排查流程从确认现象到定位瓶颈2.1 第一步收集完整的现场信息遇到ORA-27104别急着改参数。先把现场信息收集齐我一般按以下顺序操作# 查看当前共享内存内核参数 sysctl -a | grep -E shmmax|shmall # 查看系统中已有的共享内存段 ipcs -lm # 查看物理内存和交换分区 free -g # 查看系统页面大小这个决定了shmall的计算方式 getconf PAGE_SIZE这几个命令执行完后你已经掌握了系统层面的核心信息。与此同时另开一个窗口去查看Oracle的参数文件确认实例到底打算申请多大的内存。# 如果实例还没起来可以直接读spfile里的字符串 strings $ORACLE_HOME/dbs/spfile$ORACLE_SID.ora | grep -E sga|memory # 或者用更多细节的方式 cd $ORACLE_HOME/dbs ls -l spfile*.ora如果是Oracle 11g且使用了AMMAutomatic Memory Management也就是设置了MEMORY_TARGET那Oracle会优先在/dev/shm下创建内存文件。这种情况下还要多检查一个地方# 查看/dev/shm的大小 df -h /dev/shm这里有个很容易忽略的点/dev/shm默认是物理内存的一半但如果你的MEMORY_TARGET设得比/dev/shm还大启动时可能报的是ORA-00845而不是ORA-27104。我后面会讲到这两个报错之间的联系和区别。2.2 第二步算出你实际需要的共享内存大小收集完信息后把Oracle需要的内存算清楚。这一步看起来简单但很多人会算错。对于11g如果使用AMM重点看MEMORY_TARGET和MEMORY_MAX_TARGET。如果使用的是传统的内存管理重点看SGA_MAX_SIZE、SGA_TARGET和PGA_AGGREGATE_TARGET。无论哪种方式Oracle在创建共享内存段时真正关联的是SGA这块PGA不走共享内存。但在AMM模式下MEMORY_TARGET包含了SGA和PGA的总和Oracle在启动时会尝试把整个MEMORY_TARGET都映射到共享内存区域其中PGA部分实际是匿名内存映射但AMM的内存分配会影响共享内存的初始创建策略所以计算时不能只看SGA。举个例子一套11g库spfile里的配置是*.memory_max_target8589934592 *.memory_target85899345928GB页大小默认4096字节那么映射到共享内存的量至少是8GB。如果系统shmmax只有2GBshmget()申请8GB的共享内存段几乎必然失败。更严谨一点还要考虑Oracle实际创建的共享内存段数量和大小。传统上Oracle在Linux上会创建一个主共享内存段大小接近SGA_MAX_SIZE或MEMORY_TARGET。如果系统shmmax小于这个值而Oracle没有启用“多个共享内存段”的兼容模式那就会直接报ORA-27104。新版本Oracle支持多个共享段但对SGA的核心段仍然有连续性要求所以不能赌“系统会把多个段拼起来”。为了方便直接对比把需要的内存换算成统一的单位。shmmax的单位是字节shmall的单位是页页面大小通常是4096字节。换算关系是shmall应有的下限 共享内存字节数 / 4096比如需要8GB共享内存那shmall至少应该是8 * 1024 * 1024 * 1024 / 4096 2097152这个2097152就是shmall需要达到的最小页数。2.3 第三步对比系统参数找到真正的瓶颈系统和数据库两边数字都有了接下来做一个简单对比。这个对比过程我建议按以下顺序来逻辑是从宏观到微观第一看物理内存总量。Oracle要的内存MEMORY_TARGET或SGAPGA不能超过物理内存的70%到80%留出给操作系统和文件缓存的空间。如果Oracle要的内存本身就超了物理内存那不用调内核参数先改数据库参数。第二看kernel.shmmax。这个值必须大于SGA_MAX_SIZE或MEMORY_MAX_TARGET。经验值是设置为物理内存的一半或者SGA目标大小取两者较大值同时不能超过物理内存。很多服务器默认的shmmax只有3355443232MB这在现代服务器上几乎必炸。第三看kernel.shmall。这个值必须大于SGA字节数除以页面大小。很多人只改shmmax不改shmall结果还是报错或者系统运行一段时间后出现其他共享内存分配失败。第四看/dev/shm仅11g AMM场景。df -h /dev/shm显示的大小必须大于MEMORY_TARGET。这个顺序很重要因为它是从“Oracle需求是否合理”到“系统是否能满足”的一层层校验。不要跳过第一步直接改系统参数——我在生产环境见过因为盲目调大shmmax导致数据库把内存吃光操作系统OOM Killer把Oracle进程杀掉的情况那比报ORA-27104惨多了。2.4 第四步修改内核参数并验证修复确定是系统参数不足后修改/etc/sysctl.conf。注意不是用sysctl -w临时改完就完事因为重启后会被重置必须写入配置文件持久化。vi /etc/sysctl.conf追加或修改以下内容# 单个共享内存段最大字节数这里假设16G物理内存SGA目标8G kernel.shmmax 8589934592 # 共享内存总页数shmmax / 4096上例为8G/4K kernel.shmall 2097152保存后执行sysctl -p执行没有任何输出说明语法正确。然后验证一下新的参数是否生效sysctl -a | grep -E shmmax|shmall输出里应该能看到新值。最后用ipcs -lm确认系统识别的参数也是新的ipcs -lm这里要提醒一个细节修改shmmax和shmall之后不需要重启操作系统sysctl -p会直接更新内核的当前值。但如果修改的是HugePages相关的vm.nr_hugepages或者需要重新规划大页内存那可能需要重启或者释放内存才能生效后面我单独讲。参数确认生效后回到sqlplus里重新启动数据库SQL startup正常情况下应该能看到实例成功启动伴随着“Database mounted”和“Database opened”。至此ORA-27104的核心修复流程就走完了。3. 一次真实修复过程复盘11g单机环境从报错到恢复3.1 现场情况和初步判断讲一个我自己处理过的案例方便你把前面的原理串起来。那是一套Oracle 11.2.0.4的单机库运行在RHEL 7.x上物理内存16GB。故障背景是开发反馈数据库响应变慢DBA分析后认为是SGA偏小于是把SGA_TARGET从4GB调到了8GB。调整后执行了重启操作结果实例起不来了报的就是ORA-27104。我接到电话时先远程看了三样东西alert日志、内核参数、物理内存。alert日志里的报错信息是ORA-27104: system-defined limits for shared memory was misconfigured Additional information: 8589934592这个数字8,589,934,592换算一下正好是8GB。看到这个数字我心里已经有数了——Oracle想申请8GB的共享内存段系统显然没给。接着看系统参数kernel.shmmax 2147483648 # 2GB kernel.shmall 524288 # 对应约2GB物理内存16GBOracle想申请8GB的SGA但系统的shmmax只有2GB单个共享内存段最大只能到2GBOracle提交的8GB请求自然被内核直接打回。根因非常清晰没有任何悬念。3.2 参数计算与配置调整接下来是参数计算。当时Oracle的SGA目标是8GB按照稳健的原则shmmax要大于SGA大小我设置成10GB留了一点余量。换算成字节10 * 1024 * 1024 * 1024 10737418240shmall按同样的10GB除以页面大小4096计算10737418240 / 4096 2621440把这两个值写入/etc/sysctl.confkernel.shmmax 10737418240 kernel.shmall 2621440然后执行sysctl -p让它生效。这里顺便说一下为什么我不直接把shmmax设成跟物理内存一样大16GB而是给了10GB。因为shmmax限制的是单个共享内存段的最大值Oracle需要8GB就给10GB足够。设成物理内存大小也不是不行但没必要。而且如果未来SGA要继续扩展10GB这个值旁边还有空间运维心理上也会更谨慎。真正合理的做法是让shmmax略大于当前Oracle实例的最大SGA需求既满足要求又不给系统内存管理带来过多约束。3.3 启动验证与事后总结参数生效后用sqlplus启动实例SQL startup ORACLE instance started. Total System Global Area 8.5808E09 bytes Fixed Size 2263064 bytes Variable Size 7511998568 bytes Database Buffers 1073741824 bytes Redo Buffers 6197248 bytes Database mounted. Database opened.看到这串输出心里的石头才落地。整个过程从接到电话到实例恢复不到十五分钟大部分时间花在收集信息和确认根因上真正动手改参数就几分钟。事后我让系统运维同事检查了/etc/sysctl.conf的备份和修改记录发现之前的值是安装数据库时按最小配置写的后续扩容SGA时没人同步更新内核参数。这也是一个很典型的运维协作问题数据库参数和操作系统参数由不同的人管各改各的最终在重启那一刻集中爆发。为了以后不再踩坑我建议把内核参数的当前值和Oracle内存配置做成一张基线表在每次调整SGA或MEMORY_TARGET时同步review一遍shmmax和shmall。4. 那些容易翻车的进阶场景HugePages、12c与容器环境4.1 HugePages配置不当引起的连带故障如果说shmmax和shmall是ORA-27104的明面元凶那HugePages就是藏在暗处的第二层坑。Linux默认使用4KB的普通内存页管理大块内存时页表会非常庞大。Oracle在数据仓库或高并发OLTP场景下SGA动辄几十GB甚至上百GB默认页模式下页表开销巨大还会出现TLB miss带来的性能抖动。解决办法是启用HugePages大页比如2MB或1GB的页显著减少页表项数量。但HugePages的正确配置比shmmax要麻烦得多。vm.nr_hugepages设少了Oracle的SGA无法完全使用HugePages会退回普通内存映射性能预期达不到了。设多了内存被大页预留占用其他进程可用内存骤减严重时系统直接hang住。我见过一个真实翻车案例有人在16GB内存的机器上配了1024个2MB大页总共2GB预留本意是给Oracle SGA用。配置过程大致是修改/etc/sysctl.conf里的vm.nr_hugepages1024然后重启系统。结果数据库实例启动时依然报ORA-27104而且系统整体也变得卡顿。后来一查free -g显示可用内存只有13GB多2GB被大页吃掉了但Oracle的SGA是8GB2GB大页根本不够实例起不来大页内存还白白占用着。这种问题的排查命令是# 查看大页配置和使用情况 grep -i huge /proc/meminfo输出里重点关注这几项HugePages_Total: 1024 HugePages_Free: 1024 Hugepagesize: 2048 kB如果HugePages_Total不等于你要的数值说明系统在启动时因为内存整体紧张没有完全分配预留的大页。如果HugePages_Free和Total一样说明Oracle根本没使用大页。配合检查有没有memlock限制# 查看进程可以锁定的最大内存 ulimit -l在启用HugePages的环境里Oracle用户需要足够的memlock通常设为unlimited或物理内存大小否则Oracle在启动时会因为无法锁定大页内存而报错。这里Oracle文档包括MOS 1376995.1也专门提到Oracle会在alert日志里提示建议启用HugePages但配置不正确时它自己也可能成为ORA-27104的来源。所以处理ORA-27104时如果系统启用了HugePages且报了共享内存错误除了检查shmmax和shmall还要检查vm.nr_hugepages是否足够覆盖SGA大小HugePages_Total是否与配置一致Oracle用户的ulimit -l是否足够数据库参数里是否设置了use_large_pages我习惯设成only或true这项检查在12c、19c甚至21c上同样重要。Oracle 12c默认的use_large_pages是true如果系统没有配置HugePages它会自动使用标准页但如果配置了却不够情况就会变得微妙。4.2 12c/19c自动内存管理引入的新变化Oracle 12c之后数据库默认启用了新的内存管理模式很多人认为“自动内存管理就是Oracle自己搞定一切内存分配”这个理解在ORA-27104场景下会害死人。实际上12c的内存自动管理不止一种。CDB架构下MEMORY_TARGET参数可以自动在SGA和PGA之间动态调整SGA内部各组件也可以自动调整。但底层机制没有本质改变SGA依然要使用操作系统共享内存。MEMORY_TARGET设置越大Oracle启动时尝试创建的共享内存段就越大对shmmax的要求就越高。更麻烦的是12c在多租户架构下PDB的数量和内存需求会动态变化内存的使用曲线不像11g那样平稳。如果一套CDB里有十几个PDB某些PDB的SGA_TARGET设置不合理占用的内存总量可能远超DBA的预期最终让共享内存需求突破系统限制。在12c/19c排查ORA-27104时我的建议是把MEMORY_TARGET、SGA_MAX_SIZE、各PDB的SGA_TARGET加在一起估算总共享内存需求用show parameter sga和show parameter memory分别在CDB和PDB层面查询如果使用了Resource Manager的内存限制还要检查相关指令配置重点看是否设置了memory_max_target但实际memory_target比较大导致重启时申请更大内存从运维习惯上说我会在12c上优先检查是否真的需要MEMORY_TARGET。如果业务场景稳定、SGA和PGA比例基本固定建议设置固定的SGA_TARGET和PGA_AGGREGATE_TARGET而不是交给MEMORY_TARGET自动摆动。这样内存使用更可预期也更容易和系统内核参数对齐。4.3 容器化数据库的特殊处理Oracle数据库跑在Docker容器里ORA-27104的排查逻辑和物理机完全不同坑也更隐蔽。Docker容器默认的共享内存大小是64MB这个值对Oracle来说简直是杯水车薪。如果你用docker run直接启动一个Oracle容器没有显式指定共享内存大小SGA稍微大一点就会触发共享内存不足的报错。此时容器内部的sysctl参数往往没法随意修改因为默认的Docker容器是共享宿主机内核参数的。解决方法是启动容器时显式指定共享内存大小docker run -d \ --shm-size8g \ --name oracle19c \ registry.example.com/oracle/database:19c--shm-size8g就是在容器创建时把/dev/shm这个tmpfs大小指定为8GB同时影响共享内存段的可分配量。Kubernetes环境同理需要在Pod的yaml里配置spec: containers: - name: oracle image: registry.example.com/oracle/database:19c ... securityContext: sysctls: - name: kernel.shmmax value: 8589934592 - name: kernel.shmall value: 2097152不过Kubernetes对sysctl的管理有严格限制kernel.shmmax属于“非安全型sysctl”默认情况下集群管理员不一定允许设置非安全型sysctl是否放行取决于kubelet的配置。要么找平台团队开通白名单要么用hostIPC等替代方案具体要看集群环境。这里最稳妥的思路其实是在容器环境下优先把SGA/MEMORY_TARGET控制在容器允许的共享内存范围之内再谈性能。容器环境还有一个平时注意不到的问题容器使用的页大小和宿主机一致但如果宿主机的hugepages配置对容器可见容器里的Oracle也会尝试使用大页。这需要宿主机层面的协调不是容器内能解决的。4.4 从报错到预防监控和基线每次处理完ORA-27104我都会强制自己做一遍事后复盘把当前环境的内核参数、数据库内存参数、HugePages配置、物理内存大小放进一张基线表。这么做的原因是这个报错本质上不是突发的随机故障而是配置漂移的累积结果——有人在某次变更中调大了SGA或者某次系统补丁重置了sysctl直到下次重启才真正爆雷。监控方面我建议关注以下几个指标的变化趋势共享内存段的使用率ipcs -u输出中的segments和bytes共享内存分配失败的系统日志dmesg里偶尔会有shmget失败的记录/proc/meminfo里的HugePages_Total和HugePages_Free差异数据库告警日志里与内存相关的ORA-错误配置基线表的好处是任何一次内存相关的变更都能快速做到“变更前对比基线、变更后更新基线”。如果SGA从8G调整到12G而shmmax还是10G变更前的对比就能提前发现风险而不是等到重启报错再来救火。5. 常见问题速查与多年DBA实战心得5.1 ORA-27104排查速查表为了方便以后遇到问题快速定位我把需要检查的项整理成一个速查表。这张表是我自己平时处理问题时也会对照的覆盖了大多数ORA-27104场景。检查项命令或文件判断标准修复方式物理内存总量free -gOracle内存需求不超过物理内存70%-80%调整数据库参数或扩容硬件kernel.shmmaxsysctl -a大于SGA_MAX_SIZE或MEMORY_MAX_TARGET修改/etc/sysctl.conf后sysctl -pkernel.shmallsysctl -a大于SGA字节数/4096修改/etc/sysctl.conf后sysctl -p/dev/shm大小df -h /dev/shm11g AMM场景需大于MEMORY_TARGET修改/etc/fstab或容器--shm-sizeHugePages总页数grep Huge /proc/meminfo预留大页能覆盖SGA调整vm.nr_hugepagesHugePages使用率grep Huge /proc/meminfoHugePages_Free应远小于HugePages_Total检查数据库use_large_pages设置Oracle用户memlockulimit -l大于物理内存或unlimited修改/etc/security/limits.confspfile里的内存参数strings spfile*.ora确认SGA或MEMORY_TARGET当前值用alter system调整alert日志alert_ .log看Additional info确认失败时的字节数用于判断申请的内存大小这张表里的任何一项异常都可能导致ORA-27104或类似的内存类错误。建议打印出来贴工位或者存成文档放知识库里。5.2 文档里不会写的几个实战小经验第一Additional information里的数字比报错文本本身更有价值。ORA-27104后面的数字就是Oracle申请共享内存失败时的大小直接拿它和shmmax、shmall对比能省去很多猜测。但如果这个数字是十六进制格式需要用计算器或者bc命令转成十进制再看。第二修改sysctl.conf有风险执行sysctl -p前先备份。虽然改错了可以改回去但生产环境每一次无谓的变更都是有风险的。我习惯先执行cp /etc/sysctl.conf /etc/sysctl.conf.bak_$(date %Y%m%d)再动文件内容。第三11g AMM的坑查报错时别漏了/dev/shm。MEMORY_TARGET模式依赖于/dev/shm如果你看到ORA-27104顺手df -h /dev/shm不会有坏处。如果是ORA-00845那几乎肯定就是/dev/shm小于MEMORY_TARGET。第四有些云厂商的镜像会定期重置内核参数。我在云上遇到过几次客户按文档改好sysctl后一切正常但某次系统镜像重置或自动化巡检脚本把sysctl.conf顺手改回“默认优化值”数据库瞬间就挂了。这种环境最好通过cloud-init或配置管理工具统一管理sysctl参数避免人工漂移。第五hugepages或shmmax的修改并不是每次都能在运行中生效。修改vm.nr_hugepages经常需要重启或者满足连续内存条件才会真正分配到位。如果你改完发现HugePages_Total没变化不要慌先尝试重启或者用sysctl -w vm.nr_hugepages值来强制设置但仍需预留足够空闲内存。5.3 什么时候可以考虑重启解决什么时候必须改参数有一个问题经常被问到ORA-27104能不能重启就好了我的答案是分情况。如果数据库实例之前能正常启动后来由于某些操作导致共享内存段没释放干净比如进程被kill -9后共享内存段还挂在系统里那么重启操作系统清理掉这些残留段确实有可能恢复。但这种情况在真实生产里很少因为Oracle本身有IPCS清理机制而且正常重启实例会自动释放共享内存段。更大概率的情况是你之前调整了SGA或MEMORY_TARGET而系统参数没有同步更新。这种情况下重启一百遍也一样报错必须改内核参数或者调小数据库内存参数。所以我处理时会先看数据库参数是不是最近变更过如果是先评估能不能临时把SGA降回原值让实例先起来然后再规划参数调整窗口。这样对业务的影响最小。反过来如果数据库参数没变而系统参数被人动过或者系统更新后参数被重置那就直接改sysctl恢复即可数据库参数不需要动。说到底ORA-27104不是那种需要神秘技巧才能解决的报错它考察的是DBA对Oracle内存架构和Linux共享内存机制的扎实理解。把shmmax、shmall、HugePages、/dev/shm这几个关键点理清楚把现场信息收集完整这个报错很快就能从“看着吓人”变成“翻翻表就能处理”的常规问题。至少我自己后来再遇到它心态已经完全放平了备份sysctl对比参数计算需求改完验证十五分钟收工。这套流程在我手上跑过几十次基本没失手过希望看完这篇的你也一样。