ARTICLE DETAIL

资讯详情

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

Oracle 报 ORA-01000 超出打开游标最大数:从 open_cursors 配置到代码排查的完整解决方案

Oracle 报 ORA-01000 超出打开游标最大数:从 open_cursors 配置到代码排查的完整解决方案 1. 从一次线上告警说起ORA-01000 到底在报什么应用跑得好好的突然日志里刷出一片红ORA-01000: maximum open cursors exceeded。紧接着接口开始大面积超时用户侧看到的是 500运维侧看到的是数据库连接池被打满。这个报错翻译成人话就是当前这个会话打开的游标数量已经超过数据库允许的上限了。游标cursor你可以理解成数据库为一条 SQL 语句准备的“执行上下文”它记录了这条语句解析后的执行计划、绑定变量、结果集指针等信息。每次你执行一条 SQLOracle 就会在会话里占用一个游标位。用完不还游标就会越积越多直到撞上open_cursors这道墙。这个错误最容易出现在两类场景一是 Java 应用在循环里反复prepareStatement却从不close二是用了连接池以为conn.close()就万事大吉实际上连接只是归还池子PreparedStatement和ResultSet还挂在那个物理连接上占着游标。这篇就按“先看参数、再查占用、最后改代码”的顺序把 ORA-01000 从表象挖到根上。适合谁看正在被这个报错折磨的后端开发、DBA以及写 PL/SQL 存储过程时踩过游标坑的同学。下面所有 SQL 和 Java 代码都可以直接复制去用。2. 动手前先把 TaoToken 配好方便边查边验证排查这类问题我习惯一边在数据库里跑诊断 SQL一边用模型帮我快速解读执行计划、生成修复代码。这时候一个稳定的模型调用入口就很省事。TaoToken 提供统一的 API 接入兼容常见的 OpenAI 风格调用方式你可以在官网了解整体能力也可以直接进控制台创建密钥。具体操作路径是这样先打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号然后进控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 生成 API Key。拿到 Key 之后接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言的调用示例。如果你只是想快速问几个 Oracle 游标的问题直接用模型对话页面就行https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。要是你打算长期用 AI 辅助写代码、做 Agent 自动化排查那 Coding Plan 更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。密钥管理统一在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite API 基础地址是 https://taotoken.net/api 。配好之后你就可以把下面查出来的游标占用 SQL 直接丢给模型让它帮你分析哪条语句最可疑。3. 第一步确认 open_cursors 当前值并合理调大先别急着改代码第一步永远是看参数。Oracle 用初始化参数OPEN_CURSORS限定单个会话最多能同时持有多少游标默认值通常是 50 或 300对现代应用来说偏小。查看当前值show parameter open_cursors;输出大概长这样NAME TYPE VALUE ------------------------------------ ----------- ----- open_cursors integer 300如果只有 300而你的应用并发高、SQL 种类多很容易撞线。调整语句如下注意这是系统级动态参数改完立即生效但重启后会失效要持久化得写进 spfilealter system set open_cursors1000 scopeboth;scopeboth表示同时改内存和 spfile重启也保留。改完再确认一次show parameter open_cursors;这里有个关键认知把 open_cursors 调大只是止血不是治病。官方文档也说过即便设置得比实际需要大也不会额外增加系统开销所以适当调大是安全的。但如果你的代码在循环里泄漏游标调到 10000 也只是把爆炸时间往后推。真正要做的是下一步——找出是谁在疯狂占游标。4. 第二步用 v$open_cursor 定位游标占用大户Oracle 提供了v$open_cursor视图它能跟踪会话中已解析但未关闭的游标。配合v$session就能按会话统计占用数量。下面这条 SQL 按游标数降序排列一眼看出谁是大户select o.sid, s.osuser, s.machine, count(*) num_curs from v$open_cursor o, v$session s where o.sid s.sid group by o.sid, s.osuser, s.machine order by num_curs desc;典型输出SID OSUSER MACHINE NUM_CURS ---- ------- -------- -------- 217 m1 1000 96 411 m2 10 10 50 test 9 9看到 SID 217 占了 1000 个游标基本可以锁定问题会话。接着拿这个 SID 去查它到底在执行哪些 SQLselect q.sql_text from v$open_cursor o, v$sql q where q.hash_value o.hash_value and o.sid 217;结果会列出这个会话打开的所有 SQL 文本。如果看到同一条select * from empdemo where empid?重复出现几百次那答案就很明显了——某段代码在循环里反复打开同一条语句却没关闭。注意v$open_cursor跟踪的是 PARSED 和 NOT CLOSED 的游标包括用dbms_sql.open_cursor()打开的动态游标。它不会跟踪那些已打开但未解析的动态游标不过日常应用里这种情况不常见。拿到可疑 SQL 后你可以反向去代码里搜这条语句定位到具体的 DAO 方法或存储过程。5. 第三步代码层修复把游标关在循环外面现在进入根治环节。ORA-01000 在 Java 里高发的根本原因是conn.createStatement()和conn.prepareStatement()每次调用都相当于在数据库开了一个游标。如果这两个调用写在循环里游标就会不停开、不停漏。先看一段典型的错误代码for (int i 0; i balancelist.size(); i) { prepstmt conn.prepareStatement(sql[i]); prepstmt.setBigDecimal(1, nb.getRealCost()); prepstmt.setString(2, adclient_id); prepstmt.setString(3, daystr); prepstmt.setInt(4, ComStatic.portalId); prepstmt.executeUpdate(); }问题有两处prepareStatement在循环内反复创建且执行完从不close。修复方式有两种优先推荐第一种——把语句准备提到循环外用批处理prepstmt conn.prepareStatement(sql); for (int i 0; i balancelist.size(); i) { prepstmt.setBigDecimal(1, nb.getRealCost()); prepstmt.setString(2, adclient_id); prepstmt.setString(3, daystr); prepstmt.setInt(4, ComStatic.portalId); prepstmt.addBatch(); } prepstmt.executeBatch(); prepstmt.close();如果业务上确实每条 SQL 都不同没法合并那至少要在每次执行后立即关闭for (int i 0; i balancelist.size(); i) { prepstmt conn.prepareStatement(sql[i]); prepstmt.setBigDecimal(1, nb.getRealCost()); prepstmt.setString(2, adclient_id); prepstmt.setString(3, daystr); prepstmt.setInt(4, ComStatic.portalId); prepstmt.executeUpdate(); prepstmt.close(); }更稳妥的写法是用 try-with-resources让 JVM 保证关闭for (int i 0; i balancelist.size(); i) { try (PreparedStatement prepstmt conn.prepareStatement(sql[i])) { prepstmt.setBigDecimal(1, nb.getRealCost()); prepstmt.setString(2, adclient_id); prepstmt.setString(3, daystr); prepstmt.setInt(4, ComStatic.portalId); prepstmt.executeUpdate(); } }这里必须强调连接池的坑如果你用了连接池conn.close()并不是物理关闭连接只是把连接还给池子。此时PreparedStatement和ResultSet如果没关它们仍然挂在那条物理连接上占着游标。所以用了连接池更要显式关闭 Statement 和 ResultSet不能指望conn.close()帮你兜底。PL/SQL 存储过程里同理显式游标用完要CLOSEDECLARE CURSOR c_emp IS SELECT empid FROM empdemo; v_id empdemo.empid%TYPE; BEGIN OPEN c_emp; LOOP FETCH c_emp INTO v_id; EXIT WHEN c_emp%NOTFOUND; -- 处理逻辑 END LOOP; CLOSE c_emp; END; /6. 第四步验证修复效果确认游标不再泄漏改完代码别急着上线先在测试环境验证。重新部署后让应用跑一段有代表性的业务流量然后回到数据库执行第 4 步的统计 SQLselect o.sid, s.osuser, s.machine, count(*) num_curs from v$open_cursor o, v$session s where o.sid s.sid group by o.sid, s.osuser, s.machine order by num_curs desc;修复前某个会话游标数一路涨到接近 open_cursors修复后应该稳定在一个较低的水平不再持续攀升。你可以隔几分钟查一次观察数值是否收敛。再配合查一下当前会话的游标使用峰值select sid, value from v$sesstat ss, v$statname sn where ss.statistic# sn.statistic# and sn.name opened cursors current order by value desc;如果最高值远低于 open_cursors说明泄漏已经堵住。这时候你可以考虑把之前临时调大的 open_cursors 适当回调保持一个合理值即可。7. 本篇常见报错与排查清单排查过程中还会遇到一些衍生问题这里集中列一下。报错一ORA-01000反复出现调大参数后过一阵又爆。说明代码泄漏没解决回到第 4 步查v$open_cursor重点看循环内prepareStatement的代码。报错二v$open_cursor查不到明显大户但游标总数就是高。可能是动态游标未解析的情况检查是否有dbms_sql相关调用或者用v$sesstat里的opened cursors cumulative看累计打开次数。报错三改了 open_cursors 重启后失效。说明只改了内存没写 spfile用scopeboth重改一次或者直接alter system set open_cursors1000 scopespfile;然后重启。报错四连接池配置了maxPoolSize但游标还是涨。连接池大小和游标数是两个维度池子小不代表游标不泄漏每个归还的连接上挂着的未关闭 Statement 依然占游标。报错五ResultSet没关导致游标泄漏。执行executeQuery后如果不再需要结果集数据立即关闭ResultSet和Statement顺序是先关 ResultSet 再关 Statement。提示日常开发养成习惯createStatement、prepareStatement一律放在循环外或者用 try-with-resources 包裹。这一条能挡掉八成以上的 ORA-01000。如果你在排查时对某段执行计划或报错拿不准可以把 SQL 和报错贴到模型对话里让它帮你分析https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。需要批量生成修复代码或做自动化巡检脚本走 Coding Plan 更顺手https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。密钥在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 管理接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API 地址 https://taotoken.net/api 。
返回列表