ARTICLE DETAIL

资讯详情

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

Oracle ORA-01000 游标超限排查:从 open_cursors 到会话级游标泄漏定位

Oracle ORA-01000 游标超限排查:从 open_cursors 到会话级游标泄漏定位 1. 从一次线上告警说起ORA-01000 到底在报什么应用日志里突然刷出ORA-01000: maximum open cursors exceeded接口大面积超时重启能好一阵子过几个小时又复发——这是我处理过最典型的游标泄漏现场。这个报错的意思很直白当前会话打开的游标数量超过了数据库参数open_cursors允许的上限。游标可以理解成数据库为一条 SQL 准备的“执行句柄”每次解析、执行、取数都要占用一个。正常写法用完就关句柄会被回收但如果代码路径里只开不关句柄就会像水池里的水龙头一样越拧越多直到撞上上限。它适合谁看DBA 需要快速定位是哪个会话、哪条 SQL 在堆积游标Java/后端开发需要顺着会话找到自己代码里没关的Statement、PreparedStatement、ResultSet。这篇就按“先看参数上限再看会话级实时游标最后定位到具体 SQL 和代码路径”的顺序走一遍所有 SQL 都能直接复制执行。核心检索词先记住三个open_cursors参数、v$open_cursor视图、opened cursors current统计项排查全程围绕它们展开。需要说明的是调大open_cursors只是给泄漏争取时间不是修复。真正的根因几乎都在应用侧循环里创建 Statement、连接池归还连接但没关 Statement、异常分支漏掉 close。下面从环境准备开始。2. 前置准备连接方式与 TaoToken 辅助排查排查 ORA-01000 本身只需要一个能连上目标库的客户端SQL*Plus、PL/SQL Developer、DBeaver、Navicat 都行用有SELECT ANY DICTIONARY或 DBA 权限的账号登录才能查v$系列动态性能视图。如果你习惯在命令行里跑sqlplus user/passhost:1521/service直接进。我平时还会用 TaoToken 这类聚合入口来辅助处理排查过程中的杂活比如把一段报错日志丢给模型让它帮我梳理可能漏关的资源顺序或者生成一段批量采集会话游标的脚本草稿。它的模型对话入口在 https://taotoken.net/api 控制台和密钥管理在 https://taotoken.net/api-keys 接入文档在 https://taotoken.net/doc 。需要说明的是TaoToken 只是帮你更快写脚本、读日志的辅助工具真正的游标数据必须从目标 Oracle 库的v$视图里取两者不要混为一谈。如果你要长期做数据库巡检、写自动化采集脚本可以考虑 Coding Plan把常用的排查 SQL 沉淀成脚本库入口在 https://taotoken.net/coding-plan 。下面进入正题先确认参数上限。3. 可复制配置从 open_cursors 到会话级游标监控3.1 确认当前 open_cursors 上限第一步永远是看上限是多少别急着改。在 SQL*Plus 命令窗口执行show parameter open_cursors;或者用 SQL 查结果更规整方便脚本化SELECT name, value, description FROM v$parameter WHERE name open_cursors;value就是当前每个会话允许打开的最大游标数。很多老库默认是 300业务一复杂就不够用。这里先记下这个数字后面判断“是不是真的泄漏”要拿它做参照。3.2 查看各会话当前打开的游标数这是定位问题的核心查询按打开数量倒序谁在堆积一眼就能看出来SELECT a.value AS opened_cursors, s.username, s.sid, s.serial#, s.machine, s.program FROM v$sesstat a, v$statname b, v$session s WHERE a.statistic# b.statistic# AND s.sid a.sid AND b.name opened cursors current AND s.username IS NOT NULL ORDER BY a.value DESC;opened_cursors就是该会话此刻打开的游标数。如果某个会话长期停在几百甚至逼近open_cursors基本可以锁定它。sid和serial#记下来后面杀会话或进一步追踪都要用。3.3 按用户聚合看哪个账号在堆积SELECT o.sid, s.osuser, s.machine, s.program, COUNT(*) AS num_curs FROM v$open_cursor o, v$session s WHERE o.sid s.sid AND o.user_name YOUR_USER GROUP BY o.sid, s.osuser, s.machine, s.program ORDER BY num_curs DESC;把YOUR_USER换成上一步查出来的用户名注意 Oracle 里用户名默认大写。这条能帮你区分是某个应用节点、某台机器在泄漏还是全库普遍现象。3.4 定位到具体 SQL 文本光知道哪个会话不够得知道是哪条 SQL 在反复打开游标SELECT s.machine, oc.user_name, oc.sql_text, COUNT(1) AS cnt FROM v$open_cursor oc, v$session s WHERE oc.sid s.sid AND oc.user_name ! SYS GROUP BY s.machine, oc.user_name, oc.sql_text HAVING COUNT(1) 5 ORDER BY cnt DESC;HAVING COUNT(1) 5是过滤噪音正常 SQL 不会同一文本堆这么多。如果某条sql_text出现几十上百次而且它长得像循环里拼出来的语句那嫌疑就非常大了。3.5 看历史峰值判断是否已经触顶SELECT MAX(a.value) AS highest_open_cur, p.value AS max_open_cur FROM v$sesstat a, v$statname b, v$parameter p WHERE a.statistic# b.statistic# AND b.name opened cursors current AND p.name open_cursors GROUP BY p.value;highest_open_cur是自实例启动以来的会话游标峰值。如果它已经等于或非常接近max_open_cur说明确实撞过顶报错不是偶发。3.6 临时缓解调大 open_cursors只有在确认是业务量正常增长、而非泄漏时才考虑调大。命令如下ALTER SYSTEM SET open_cursors 1000 SCOPE BOTH;SCOPE BOTH表示同时改内存和 spfile重启后仍生效。改完用show parameter open_cursors确认。注意这只是把天花板抬高泄漏的会话迟早还会撞上来所以调大之后必须继续做第 4 步的验证和代码排查。4. 验证请求复现游标泄漏并确认定位准确排查不能只靠看要能复现才算坐实。下面用一个最小 Java 片段复现“循环里创建 PreparedStatement 不关闭”的经典泄漏。// 反面示例循环内创建 PreparedStatement 且不关闭 public void leakCursors(Connection conn) throws SQLException { for (int i 0; i 500; i) { PreparedStatement ps conn.prepareStatement( SELECT i FROM dual); ps.executeQuery(); // 只执行不 close } }跑这段之前先在另一个会话里持续监控游标数SELECT a.value AS opened_cursors, s.sid, s.serial# FROM v$sesstat a, v$statname b, v$session s WHERE a.statistic# b.statistic# AND s.sid a.sid AND b.name opened cursors current AND s.sid target_sid;把target_sid换成执行 Java 的那个会话 SID。你会看到opened_cursors随着循环一路往上爬循环结束也不回落——这就是泄漏的铁证。正确写法是把PreparedStatement提到循环外复用并在 finally 里关闭public void noLeak(Connection conn) throws SQLException { String sql SELECT ? FROM dual; try (PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i 500; i) { ps.setInt(1, i); try (ResultSet rs ps.executeQuery()) { // 处理结果 } } } }用 try-with-resources 后再跑一遍监控opened_cursors会在循环结束后回落到基线。这一步验证通过就说明你定位到的代码路径是对的。5. 本篇常见错排查改了 open_cursors 还是报错。先确认SCOPE是否生效show parameter看的是内存值如果只改了 spfile 没改内存当前实例不生效。另外连接池可能缓存了旧会话需要重启应用让新参数落到新会话上。v$open_cursor 查不到数据。多半是权限不够普通用户看不到v$视图换 DBA 账号或授予SELECT ANY DICTIONARY。也可能是user_name大小写写错Oracle 默认大写。会话游标数不高但依然报 ORA-01000。检查是不是open_cursors被设得过低比如 50正常业务都能撞上。也可能是某个会话瞬间爆发监控查询没抓到峰值用 3.5 的历史峰值查询确认。连接池归还连接后游标没释放。这是最隐蔽的坑。Connection.close()在连接池里只是归还不是物理关闭如果 Statement 没关游标资源仍被持有。务必在归还前显式关闭 Statement 和 ResultSet或依赖 try-with-resources。杀会话后问题复发。ALTER SYSTEM KILL SESSION sid,serial#只能清掉当前堆积代码不改过一阵子照样堆。杀会话是止血改代码才是治本。6. 把排查沉淀成日常巡检游标泄漏这类问题靠出事再查永远被动。我的做法是把第 3 节的几条 SQL 做成定时采集脚本每小时把各会话opened cursors current快照写进一张监控表连续几个周期都在涨的会话自动告警。这样在撞上open_cursors之前就能发现苗头。如果你想把采集脚本、告警逻辑、日志分析这些环节串起来可以用 TaoToken 的模型对话帮你快速生成脚本骨架和解析逻辑入口在 https://taotoken.net/api 密钥在 https://taotoken.net/api-keys 管理接入方式看 https://taotoken.net/doc 。长期做数据库自动化的Coding Plan 在 https://taotoken.net/coding-plan 可以了解下。记住核心顺序先看open_cursors上限再用v$open_cursor和opened cursors current定位会话与 SQL最后回到代码里把没关的 Statement 补上——调参数只是买时间关游标才是修根因。
返回列表