ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Oracle SESSIONS_PER_USER 详解:会话并发限制配置与踩坑实战

Oracle SESSIONS_PER_USER 详解:会话并发限制配置与踩坑实战 第一次遇到 ORA-02391 这个报错是很多年前在客户现场排查一套财务系统的时候。当时开发那边反馈业务突然大面积报错登录用户集体掉线我拉了一下v$session发现某个业务账号的会话数已经冲到两百多直接把实例的 SESSIONS 参数顶满了整个库几乎处于半瘫痪状态。事后排查发现是应用代码里存在连接泄漏每次请求都新建连接但没走释放逻辑。那次之后我就在 Profile 里给这个账号设置了 SESSIONS_PER_USER 的上限类似问题再没出现过。Oracle 数据库里SESSIONS_PER_USER 是一个非常实用但容易被忽视的并发控制参数。它属于用户配置文件Profile里的资源限制项可以精确限制单个用户最多同时建立多少个数据库会话。这篇文章就从它的原理、配置、验证方法、踩坑经验以及和实例级参数的关系完整梳理一遍。主要内容面向 DBA、数据库运维同学也建议做应用开发的朋友了解下毕竟连接池设计时数据库侧还有这么一道约束。1. 为什么需要限制用户的并发连接数1.1 不设限的典型事故场景数据库实例层面有 PROCESSES 和 SESSIONS 两个参数兜底很多 DBA 觉得有了这两个参数就万事大吉。但实际上它们解决的是数据库整体扛不扛得住的问题解决不了某个用户把资源吃光殃及池鱼的问题。一个很典型的现象实例的 SESSIONS 参数设的是 1000正常情况下整个库 600 个会话左右一切平稳。但如果某个业务账号因为代码 bug 或人为误操作一下子把连接数从 50 打到 500那其他所有业务的会话都会被挤压最终表现为整个库的连接数爆掉谁也登录不上去。类似的事故场景我见过不少包括但不限于以下几种应用代码有连接泄漏。Java 程序里每次请求都getConnection()但忘记close()跑上半天连接数就一路涨。报表任务或批量任务用同一个账号一次性打开几十个并发会话跑数据占用大量临时表空间和 PGA。运维或开发人员排查问题时用同一个业务账号反复登录用完不退出连接越积越多。连接池的maximumPoolSize配置得过大而且应用是多节点部署每个节点都按最大池大小创建连接合起来远超数据库预期。如果账号设置了 SESSIONS_PER_USER上面这些场景最多只会影响这一个账号不会拖垮整个实例。这就是用户级并发控制的价值所在——故障隔离或者说至少能限制故障半径。1.2 哪些场景建议必须配置 Profile 配额通过这些年做数据库运维和架构评审的经验下面这几类场景我基本上都会坚持建议客户配置 Profile 配额而不是只依赖实例级参数场景原因多个应用共享同一个 Oracle 账号单个应用的连接行为不受控一个出问题会拖垮另一个第三方厂商提供的黑盒应用DBA 无法修改应用侧连接配置只能从数据库侧加限制高密度生产环境多个业务系统共用一个实例需要通过配额为不同业务账号分配资源边界等保、企业内控或合规审计要求安全基线里明确要求限制账号并发会话数数据库账号直接暴露给运维脚本和手工登录防止有人开了会话忘记退出长期占用空闲连接在这些场景下SESSIONS_PER_USER 就是个很轻量的流量阀门能从账号维度做资源隔离。2. SESSIONS_PER_USER 的工作机制2.1 参数含义限定用户级并发会话数Profile 在 Oracle 里是一组资源限制的集合默认情况下每个用户都会绑定一个名为 DEFAULT 的 Profile。SESSIONS_PER_USER 是这个集合里的一个资源限制项含义是该用户最多可以同时建立的数据库会话session数。举个例子如果给某个账号设置了SESSIONS_PER_USER 5那么这个账号在同一时间最多只能建立 5 个会话。第 6 个会话发起时数据库会直接拒绝报ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit。通过数据字典可以很方便地查看当前所有 Profile 的资源限制值SELECT profile, resource_name, limit FROM dba_profiles WHERE resource_type KERNEL ORDER BY profile, resource_name;注意RESOURCE_TYPE KERNEL这个条件。Profile 里的参数分两类一类是内核资源限制KERNEL比如 SESSIONS_PER_USER、CPU_PER_SESSION、IDLE_TIME另一类是口令管理PASSWORD比如 PASSWORD_LIFE_TIME、FAILED_LOGIN_ATTEMPTS。SESSIONS_PER_USER 属于前者。2.2 总开关RESOURCE_LIMIT 参数这里必须强调一个硬约束Profile 里的所有内核资源限制参数包括 SESSIONS_PER_USER都受数据库参数RESOURCE_LIMIT控制。如果RESOURCE_LIMIT FALSE哪怕你创建了 Profile、绑定给了用户限制也不会生效。-- 查看当前状态 SHOW PARAMETER resource_limit; -- 动态开启立即生效无需重启 ALTER SYSTEM SET resource_limit TRUE;RESOURCE_LIMIT默认值是 FALSE这是 Oracle 为了兼容旧版本行为保留的默认设置。网上很多教程只教你怎么建 Profile、绑用户不提这个开关结果有人配置完一测试发现压根没限制还以为自己操作错了。还有一个容易忽略的细节RESOURCE_LIMIT只影响 KERNEL 类资源限制不影响 PASSWORD 类的口令管理参数。也就是说就算RESOURCE_LIMIT FALSE你设了FAILED_LOGIN_ATTEMPTS密码尝试次数限制该锁账号还是会锁。这一点在做排查时很有用能帮我们快速区分问题方向。如果确定要在生产环境启用建议同时写入静态参数文件ALTER SYSTEM SET resource_limit TRUE SCOPE BOTH;SCOPEBOTH 表示同时修改当前实例和 spfile避免下次重启后配置丢失。2.3 什么样的连接会计入 SESSIONS_PER_USER搞清统计口径才能准确估算配额。SESSIONS_PER_USER 统计的是指定用户的所有会话统计范围很宽SQL*Plus、SQL Developer 等客户端的命令行连接应用通过 JDBC、ODBC 建立的连接共享服务器Shared Server模式下分配给该用户的所有会话指向本地用户的数据库链路dblink会话通过监听器建立的专用服务器进程对应会话有一点需要特别提一下以 SYSDBA/SYSOPER 等管理员权限登录的会话本质上走的是管理员通道通常不会受普通用户的 Profile 资源限制约束。我见过有人在测试环境用 SYSTEM 账号测试 SESSIONS_PER_USER结果怎么都触发不了报错研究半天才发现方向错了。另外如果应用使用的是代理认证Proxy Authentication用户连接时实际是以代理用户身份建立的会话那会话归属的计数就要看最终的后端用户名而不是连接时的外部用户。这种场景比较少见但如果遇到了记得从v$session里的USERNAME字段去判断当前会话到底记在谁头上。3. 从创建 Profile 到生效的完整配置流程3.1 第一步确认总开关状态在动手配置前先确认RESOURCE_LIMIT已经打开SHOW PARAMETER resource_limit;如果当前是 FALSE执行ALTER SYSTEM SET resource_limit TRUE SCOPE BOTH;这一步建议在变更窗口内操作。虽然它本身是动态参数不会导致实例重启但在高并发的生产库上开启资源限制后某些原本不受约束的用户可能立刻触发限制引发应用连接报错。所以最好先和业务侧沟通尤其在应用连接数峰值已经很高的场景下别贸然开。3.2 第二步创建自定义 Profile假设我们要给财务系统账号 FINAPP 做一个限制并发会话数上限 5空闲会话超过 30 分钟断开单次连接时间不超过 8 小时。可以这样创建CREATE PROFILE fin_profile LIMIT SESSIONS_PER_USER 5 IDLE_TIME 30 CONNECT_TIME 480;我这里只设置了三个参数没写的资源项会继承 DEFAULT Profile 的默认值。比如CPU_PER_SESSION没写的话默认是 UNLIMITEDPASSWORD_LIFE_TIME没写的话默认继承 DEFAULT 里的设置。这个继承机制在运维时很容易踩坑你建了一个新 Profile只设了 SESSIONS_PER_USER以为密码策略也自动继承 DEFAULT 了。但如果你在 DEFAULT Profile 里改了密码有效期而自定义 Profile 里明确设了PASSWORD_LIFE_TIME 180那绑到该 Profile 的用户就会用 180 这个值而不是 DEFAULT 的新值。所以建 Profile 时最好把口令策略也一并明确写出来。3.3 第三步把 Profile 绑定给用户ALTER USER finapp PROFILE fin_profile;绑定之后可以验证一下用户的 Profile 是否切换成功SELECT username, profile FROM dba_users WHERE username FINAPP;正常情况下查询结果里PROFILE字段应该是FIN_PROFILE。注意 Oracle 默认会把 Profile 名称存储为大写除非你用引号创建了小写或混合大小写的名称。这一点在后面查询和修改时会带来麻烦建议创建时统一用大写字母。3.4 第四步触发测试验证限制真的生效开 5 个终端窗口分别用 FINAPP 登录sqlplus finapp/finapp_passwordorcl如果前 5 个会话都登录成功第 6 个窗口继续执行同样的命令就会看到ERROR: ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit这个报错本身就是最直接的验证结果。如果第 6 个连接也成功说明配置没生效优先检查RESOURCE_LIMIT是否为 TRUE以及dba_profiles里这个 Profile 的 SESSIONS_PER_USER 值是否为 5。3.5 修改配额与清理 Profile业务扩容后需要调整并发上限直接改 Profile 就行不需要动用户ALTER PROFILE fin_profile LIMIT SESSIONS_PER_USER 20;修改是立即生效的已经超过旧限制但小于新限制的会话不会被中断新会话按新上限执行。反过来如果调低限制已经存在的会话也不会被踢掉但新会话会按新上限判断。这点很重要我在第 5 节还会详细说。如果某个自定义 Profile 不再需要了删除时要注意它一旦被用户引用必须加 CASCADEDROP PROFILE fin_profile CASCADE;加 CASCADE 之后原来绑定该 Profile 的用户会自动落到 DEFAULT Profile。这个动作是隐式的删之前一定要确认用户本来使用的是否就是 DEFAULT 策略避免密码策略等配置被意外切换。4. 实际压测验证并发超限会发生什么4.1 准备测试账号和带到环境的注意点在测试环境或者专门的验证库上做压测建议准备一个独立的账号别直接用生产账号试。我在测试时通常会这样准备-- 创建测试用户并赋予最小权限 CREATE USER conntest IDENTIFIED BY conntest_pwd; GRANT CREATE SESSION TO conntest; -- 创建测试 Profile限制 3 个并发会话 CREATE PROFILE conntest_profile LIMIT SESSIONS_PER_USER 3; -- 绑定 ALTER USER conntest PROFILE conntest_profile;如果你只想在现有账号上测试记得测试完把 SESSIONS_PER_USER 调回原值或改回 DEFAULT Profile避免影响业务。4.2 压测步骤和结果在服务器上同时打开多个终端或者在脚本里循环执行for i in $(seq 1 5) do sqlplus -S conntest/conntest_pwdorcl EOF SELECT session_$i connected AS info FROM dual; sleep 60; EOF done 这个脚本会尝试同时建立 5 个会话。前 3 个会成功并且执行SELECT后进入 sleep 状态第 4、5 个会失败并返回 ORA-02391。如果不想等 sleep也可以用下面这种方式快速验证开 3 个 SQL*Plus 窗口手动挂着再开第 4 个窗口执行。-- 第 4 个窗口执行 SELECT COUNT(*) FROM v$session WHERE username CONNTEST;查询结果大概率是 3说明当前会话数已经顶满。此时新登录会直接报错。4.3 超限时应用的感知是什么从应用侧看数据库超限报错表现为典型的连接建立失败。Java 应用使用 JDBC 时堆栈里会出现类似的异常java.sql.SQLException: ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit这里有个容易被忽略的问题如果应用本身有连接池而且池子里minimumIdle和maximumPoolSize设置得不够理智那么在连接数被限住的瞬间应用会反复尝试建立新连接数据库反复返回 ORA-02391应用日志里会刷出大量同样的错误堆栈。数据库端看到的则是大量连接尝试记录。这种情况下仅仅调高 SESSIONS_PER_USER 并不能根治问题还得同时排查连接池参数和应用侧是否存在连接泄漏。4.4 关键细节超限只拒绝新连接不断老连接这一点我特意拿出来强调。SESSIONS_PER_USER 超限后动作只有拒绝新的会话建立对已经存在的会话完全不影响。哪怕第 6 个连接一直尝试、一直失败前面 5 个会话依然能正常执行 SQL、正常提交事务。这个特性和实例级参数超限时所有会话都受限的表现不同。比如PROCESSES参数满了整个实例的新连接都会被拒老连接也不能幸免于一些需要新进程的操作。而 SESSIONS_PER_USER 更像一把只卡在门口的门禁房间里的人不受打扰。所以在做容量评估时要给临界情况留出缓冲如果业务峰值并发会到 10你设 10 看起来刚好但一旦业务临时冲一下到 11新请求就会全部失败而且因为老连接不释放失败状态可能持续很久。实际生产环境中设到正常峰值的 1.2 到 1.5 倍会更稳妥给流量毛刺留一点余量。5. 踩过的坑与常见误区5.1 DEFAULT Profile 的默认值其实是 UNLIMITEDDEFAULT Profile 里 SESSIONS_PER_USER 的默认值是 UNLIMITED这也是为什么大多数人从来没遇到过 ORA-02391 的原因——系统默认压根不做用户级限制。Oracle 把所有新用户自动放到 DEFAULT Profile所以如果不主动建 Profile这个参数就是一种存在但从未生效的状态。在做安全基线排查时建议批量查一下当前有哪些用户仍然挂在 DEFAULT Profile 下SELECT username, profile FROM dba_users WHERE profile DEFAULT ORDER BY username;很多次渗透测试和安全审计时这条 SQL 都是第一轮必查项。结果通常能看到不少高权限账号还挂在 DEFAULT 上且 DEFAULT 的 SESSIONS_PER_USER 是 UNLIMITED这就是高风险点。5.2 改了 Profile 却不生效的几种原因这类问题在我帮助用户排查时遇到频率非常高根因通常集中在下面几个地方RESOURCE_LIMIT还是 FALSE。这是第一大坑优先确认。用户绑定的 Profile 不是你以为的那个。比如有人改了fin_profile但用户实际绑的是default。应用连接时用的是服务账号的代理用户计数归属到别的账号。数据库里存在同名大小写不同的 Profile。如果用引号创建过小写名称后面写大写名称查到的是另一个 Profile。修改 Profile 后没有重新建立连接。已经存在的连接在会话建立时就按当时的限制判断改 Profile 不会自动刷新已有连接的配额状态。其中最后一点特别容易误导人你调低了 SESSIONS_PER_USER但之前那些已经超过新限制的老连接全都还挂着这时候想知道新限制是否生效必须新起一个会话去测而不是看老会话有没有被断开。5.3 连接池场景下的配额计算要算总账连接池是 SESSIONS_PER_USER 最容易翻车的场景。很多团队只在一台应用服务器上做了测试配了maximumPoolSize20然后 SESSIONS_PER_USER 设了 25看着没问题。可实际上生产环境是 4 个应用节点每个节点都是这个池大小加起来就是 8025 的配额瞬间被打满应用启动时连接池初始化都会失败。正确的做法是把所有使用该数据库账号的节点池大小加总再乘以一个冗余系数。比如 4 个节点每个池最大 20合计 80那 SESSIONS_PER_USER 至少设置 9680 × 1.2左右再预留 DBA 手工连接和监控账号的空间。如果同一个账号还要跑夜间批处理批处理的并发连接数也要单独计入。5.4 会话数不等于连接数两种特殊连接形态严格来说在专用服务器Dedicated Server模式下一个会话对应一个连接两者基本可以画等号。但在共享服务器Shared Server模式下用户到数据库的物理连接是复用的会话数超过了物理连接数。SESSIONS_PER_USER 限制的是会话数而不是物理连接数量。这表示在共享服务器模式下一个应用可以建立少数几个物理连接却产生很多个并行会话同样会触发限额报警。另外如果应用使用了 DRCPDatabase Resident Connection Pool会话被池化后统计上也会体现为多个会话归属于同一个用户。这种情况下单纯看数据库侧的连接数很容易跟应用侧对不上。排查问题时要先确认数据库运行在专用服务器模式还是共享服务器模式否则方向容易跑偏。5.5 排查脚本快速定位当前账号的会话占用当收到 ORA-02391 告警时用下面这几条 SQL 可以快速定位-- 查看该用户当前所有会话 SELECT sid, serial#, username, machine, program, status, logon_time FROM v$session WHERE username FINAPP ORDER BY logon_time; -- 按机器和程序统计会话分布找到连接大户 SELECT machine, program, COUNT(*) AS session_cnt FROM v$session WHERE username FINAPP GROUP BY machine, program ORDER BY session_cnt DESC;通过第二条 SQL通常能一眼看出是哪台应用服务器、哪个程序占了大量会话。结合logon_time还能判断这些会话是不是从某一个时间点开始集中建立从而反推应用发布或代码变更的时间线。6. 与其他并发控制手段的配合使用6.1 三级限制的层次差异数据库里的并发限制其实是多层次的SESSIONS_PER_USER 不是唯一的工具它和实例级参数各有分工。限制层次参数 / 对象作用范围超限报错操作系统进程数PROCESSES整个实例ORA-00020: maximum number of processes exceeded实例会话数SESSIONS整个实例ORA-00018: maximum number of sessions exceeded用户并发会话数PROFILE.SESSIONS_PER_USER单个用户ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit从运维角度看PROCESSES 和 SESSIONS 是最后一道防线保护的是数据库这个容器本身。SESSIONS_PER_USER 则是更精细的分闸在单个用户的层面做隔离。实际生产环境中两者不能互相替代你把 SESSIONS 设得再大也不妨碍某个业务账号靠连接泄漏把整个库拖垮你把每个账号的 SESSIONS_PER_USER 都设得很小但如果用户数量很多实例级 SESSIONS 仍然可能被整体打满。所以两个维度都要管。6.2 其他相关的 Profile 资源参数SESSIONS_PER_USER 通常不是单独使用的我会习惯把它和下面几个 Profile 参数搭配参数作用搭配理由IDLE_TIME空闲会话最大分钟数自动清理挂机不用的会话释放配额CONNECT_TIME单次连接最大分钟数限制超长连接防止泄漏连接长期占用CPU_PER_SESSION会话累计 CPU 时间上限防止单条失控 SQL 消耗过多 CPULOGICAL_READS_PER_SESSION会话累计逻辑读上限限制大查询读取量保护 I/O 和缓存比如同时设置SESSIONS_PER_USER 20和IDLE_TIME 30即使应用有少量连接忘记释放空闲超过 30 分钟后也会被数据库侧断开避免配额一直被无效连接占着。6.3 生产环境推荐的配套方案在我维护过的生产库里我一般建议按下面的思路做配置建立专门的业务 Profile 模板统一管理 SESSIONS_PER_USER、IDLE_TIME、CONNECT_TIME、口令策略而不是让各个账号各自为政。业务账号的 SESSIONS_PER_USER 按应用节点数 × 单节点池大小 × 1.2~1.5设置同时额外叠加 DBA 日常维护连接数余量。定期巡检每周跑一次 SQL统计每个 Profile 下用户的实时会话数与配额比值提前发现接近配额的账号。把 ORA-02391 纳入数据库告警项一旦出现立即检查是流量暴涨还是连接泄漏而不是等问题发酵。6.4 临时扩容操作业务大促、月底批量跑数这类短期高并发场景临时调高配额是常规操作。这个操作不需要重启不需要断连接只需要一条 SQLALTER PROFILE fin_profile LIMIT SESSIONS_PER_USER 50;活动结束后再改回原值整个过程对业务透明非常方便。我在做双十一大促支持时经常提前几天把核心账号的配额临时调高活动结束后再收紧。这里有一个小提醒改高配额是一瞬间的事但改低配额时如果当时在线会话数大于新配额新会话会受限而老会话不会断开业务侧要留意这种现象别误判是系统故障。关于 SESSIONS_PER_USER我能分享的实操经验基本就是这些了。最后说点个人体会这个参数看起来很小但在生产环境里它是我遇到过的性价比最高的并发保护手段之一。它不像 PROCESSES 那样需要动实例级配置也不像应用侧改代码那样依赖开发排期一条 ALTER PROFILE 就能从数据库侧卡住失控连接的蔓延。如果你手头也管着一批 Oracle 实例建议花半天时间做一轮账号摸底看看哪些账号还挂在 DEFAULT Profile 下然后按业务并发峰值给它们上一个合理的配额。等到真的因为连接泄漏而收到告警时你会庆幸当初多写了这一行配置。
返回列表