紧急救场!解密ORA-01000:最大游标超限故障的全攻略(CDB/PDB与NON-CDB环境实战指南)
这是一个星期六的早晨,却突然收到数据库系统的紧急告警!日志里跳出一串让人头大的错误——ORA-01000: maximum open cursors exceeded,而截图中显示:
现象是应用程序响应变慢,部分用户报告无法登录系统,尤其是你使用的是多租户架构(如Oracle的CDB和多个PDB),问题变得更加复杂!别慌,这篇文章将带你从零基础入门,深入剖析技术细节,快速定位问题并解决故障。无论是数据库小白还是资深DBA,这里都有干货!
🧠 诊断思路:从现象到根源的逐步探索
面对 ORA-01000 错误,我们需要有条不紊地排查。以下是诊断的三大核心步骤:
1. 确认问题范围:确定错误发生在哪个会话、哪个数据库实例(CDB 或 NON-CDB)或具体 PDB。
2. 定位资源瓶颈:检查当前打开的游标数量是否超过 OPEN_CURSORS
限制,以及哪些 SQL 或进程导致堆积。
3. 分析根本原因:判断是代码问题(未关闭游标)、配置不足(OPEN_CURSORS
过低)还是系统负载异常。
本文将沿着这个思路,逐步抽丝剥茧,解释现象,并提供针对 CDB 和 NON-CDB 环境的诊断与处理方案。
🔥 什么是ORA-01000?现象背后的故事
游标(Cursor)是什么?
在 Oracle 数据库中,游标就像一个“指针”,指向内存中的私有SQL区域,用于处理 SELECT 或 DML(插入、更新、删除)语句。它是数据库和应用程序沟通的桥梁。
- 隐式游标:由 PL/SQL 自动创建和管理,运行简单语句时自动打开并关闭。
- 显式游标:需手动声明、打开、获取数据并关闭,常见于复杂查询。
现象解析
从日志来看,多个 PDB(如 PUB2
和 SZKA
)的进程 33749 报告游标超限,应用程序响应变慢且用户无法登录。这提示:
- 多会话问题:可能多个会话同时打开过多游标。
- 多租户影响:CDB 架构下,PDB 资源共享,可能某个 PDB 的负载影响全局。
- 潜在瓶颈:未关闭游标或 OPEN_CURSORS
设置过低。
🕵️♂️ 故障诊断:沿着思路抽丝剥茧
步骤1:确认问题范围
首先,我们需要知道错误发生在哪个会话和环境中。以下是针对 CDB 和 NON-CDB 的诊断。
CDB 架构下的诊断
在多租户 CDB 环境中,需检查哪个 PDB 或会话引发问题。运行以下格式化 SQL:
-- 格式化设置
SET LINESIZE 120
SET PAGESIZE 50
COLUMN curr_open_cursors HEADING 'Current|Open Cursors' FORMAT 999999
COLUMN pdb_name HEADING 'PDB Name' FORMAT A15
COLUMN username HEADING 'Username' FORMAT A15
COLUMN sid HEADING 'Session ID' FORMAT 9999
COLUMN serial# HEADING 'Serial#' FORMAT 99999
-- 查询语句
SELECT
a.value AS curr_open_cursors,
c.name AS pdb_name,
s.username,
s.sid,
s.serial#
FROM v$sesstat a
JOIN v$statname b ON a.statistic# = b.statistic#
JOIN v$session s ON s.sid = a.sid
JOIN v$containers c ON c.con_id = s.con_id
WHERE b.name = 'opened cursors current'
AND s.username IS NOT NULL
ORDER BY a.value DESC;
- 输出示例
Current
Open Cursors PDB Name Username Session ID Serial#
------------ --------------- --------------- ---------- -------
555 BIUAT SINOET 1947 19019
1 CDB$ROOT SYS 2920 60039
- 现象解释:BIUAT 的会话 1947 打开 555个游标,超出典型限制(默认 300),可能是问题源头。
NON-CDB 架构下的诊断
在单租户 NON-CDB 环境中,无需区分 PDB,直接检查全局会话。运行:
-- 格式化设置
SET LINESIZE 120
SET PAGESIZE 50
COLUMN curr_open_cursors HEADING 'Current|Open Cursors' FORMAT 999999
COLUMN username HEADING 'Username' FORMAT A15
COLUMN sid HEADING 'Session ID' FORMAT 9999
COLUMN serial# HEADING 'Serial#' FORMAT 99999
-- 查询语句
SELECT
a.value AS curr_open_cursors,
s.username,
s.sid,
s.serial#
FROM v$sesstat a
JOIN v$statname b ON a.statistic# = b.statistic#
JOIN v$session s ON s.sid = a.sid
WHERE b.name = 'opened cursors current'
AND s.username IS NOT NULL
ORDER BY a.value DESC;
- 输出示例
Current
Open Cursors Username Session ID Serial#
------------ --------------- ---------- -------
7 SYSRAC 583 35188
6 SYS 1705 43304
5 SYSRAC 2838 8445
2 SYSRAC 3974 51805
2 SYSTEM 2850 26183
1 SYSRAC 1150 24937
1 SYSRAC 10 9406
0 SYS 2832 18059
8 rows selected.
步骤2:定位资源瓶颈
找到问题会话后,检查具体 SQL。
CDB 架构下的定位
运行以下 SQL,分析 PDB 内的 SQL 行为:
-- 格式化设置
SET LINESIZE 200
SET PAGESIZE 300
COLUMN sid HEADING 'Session ID' FORMAT 9999
COLUMN pdb_name HEADING 'PDB Name' FORMAT A15
COLUMN sql_text HEADING 'SQL Text' FORMAT A50 WORD_WRAPPED
COLUMN "OPEN CURSORS" HEADING 'Open Cursors' FORMAT 999999
COLUMN username HEADING 'Username' FORMAT A15
-- 查询语句
SELECT
s.sid,
c.name AS pdb_name,
o.sql_text,
COUNT(*) AS "OPEN CURSORS",
o.user_name as username
FROM v$open_cursor o
JOIN v$session s ON s.sid = o.sid
JOIN v$containers c ON c.con_id = s.con_id
WHERE s.sid IN ('1947') -- 替换为步骤1中的SID
GROUP BY s.sid, c.name, o.sql_text, o.user_name
ORDER BY COUNT(*) DESC;
- 输出示例
Session ID PDB Name SQL Text Open Cursors Username
---------- --------------- -------------------------------------------------- ------------ ---------------
1947 BIUAT SELECT ID_DATABASE_TYPE FROM R_DATABASE_TYPE WHERE 300 SINOET
CODE = :1
1947 BIUAT DELETE FROM R_JOB_HOP WHERE ID_JOB = :1 1 SINOET
1947 BIUAT SELECT ID_STEP FROM R_STEP WHERE ID_TRANSFORMATION 1 SINOET
= :1
1947 BIUAT SELECT * FROM R_STEP WHERE ID_STEP = :1 1 SINOET
1947 BIUAT update seq$ set 1
increment$=:2,minvalue=:3,maxvalue=:4,cycle#
1947 BIUAT INSERT INTO R_DATABASE_ATTRIBUTE 1 SINOET
(ID_DATABASE_ATTRIBUTE, ID_
1947 BIUAT DELETE FROM R_TRANS_HOP WHERE ID_TRANSFORMATION = 1 SINOET
:1
1947 BIUAT SELECT ID_SLAVE FROM R_SLAVE 1 SINOET
1947 BIUAT SELECT ID_SLAVE FROM R_CLUSTER_SLAVE WHERE 1 SINOET
ID_CLUSTER = :1
1947 BIUAT SELECT COUNT(*) FROM R_JOB_ATTRIBUTE WHERE ID_JOB 1 SINOET
= :1 AND
1947 BIUAT insert /* QOSD */ into exp_head$ (exp_id, objn, 1
sub_id, fixe
1947 BIUAT merge into sys.col_group_usage$ d using (select :1 1
obj#, :2
- 现象解释:BIUAT 的会话 1947的 SELECT ID\_DATABASE\_TYPE FROM R\_DATABASE\_TYPE WHERE CODE = :1未关闭游标,导致堆积。
NON-CDB 架构下的定位
运行:
-- 格式化设置
SET LINESIZE 120
SET PAGESIZE 50
COLUMN sid HEADING 'Session ID' FORMAT 9999
COLUMN sql_text HEADING 'SQL Text' FORMAT A50 WORD_WRAPPED
COLUMN "OPEN CURSORS" HEADING 'Open Cursors' FORMAT 999999
COLUMN username HEADING 'Username' FORMAT A15
-- 查询语句
SELECT
sid,
sql_text,
COUNT(*) AS "OPEN CURSORS",
user_name as username
FROM v$open_cursor
WHERE sid IN (125) -- 替换为步骤1中的SID
GROUP BY sid, sql_text, user_name
ORDER BY COUNT(*) DESC;
- 输出示例
Session SQL Text Open Cursors Username
ID
-------- ------------------------------------ ------------ ---------------
125 SELECT * FROM employees 300 APP_USER
125 UPDATE employees SET salary = 5000 150 APP_USER
- 现象解释:APP\_USER的会话 125 的 SELECT 和 UPDATE 未正确关闭游标,引发游标超限。
步骤3:分析历史上最高的游标数量
原因
检查 OPEN\_CURSORS 设置是否不足:
-- 格式化设置
SET LINESIZE 120
SET PAGESIZE 50
COLUMN highest_open_cur HEADING 'Highest Open Cursors' FORMAT 999999
COLUMN max_open_cur HEADING 'Max Open Cursors' FORMAT 999999
-- 查询语句
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 Cursors Max Open Cursors
--------------------- -----------------
500 300
- 现象解释:历史上最高游标数 500 超过上限 300,确认配置不足。
🛠️ 故障处理:针对 CDB 和 NON-CDB 的方案
方案1:优化代码,关闭游标
- 显式游标:确保调用 CLOSE。
收起自动换行复制
DECLARE CURSOR my_cursor IS SELECT * FROM employees; rec employees%ROWTYPE; BEGIN OPEN my_cursor; LOOP FETCH my_cursor INTO rec; EXIT WHEN my_cursor%NOTFOUND; -- 处理数据 END LOOP; CLOSE my_cursor; -- 必须关闭! EXCEPTION WHEN OTHERS THEN CLOSE my_cursor; -- 异常时也关闭 RAISE; END;
- CDB/NON-CDB 通用:检查应用程序代码,修复未关闭资源。
方案2:调整 OPEN\_CURSORS 参数
- CDB 架构:在根容器调整,影响所有 PDB:
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH;
- NON-CDB 架构:直接调整实例:
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH;
- 注意:调整后监控内存使用,避免资源耗尽。
方案3:多PDB 环境下的特别注意(仅 CDB)
- 检查每个 PDB 负载:
-- 格式化设置
SET LINESIZE 120
SET PAGESIZE 50
COLUMN con_id HEADING 'Container ID' FORMAT 999
COLUMN pdb_name HEADING 'PDB Name' FORMAT A15
COLUMN username HEADING 'Username' FORMAT A15
COLUMN sid HEADING 'Session ID' FORMAT 9999
COLUMN value HEADING 'Open Cursors' FORMAT 999999
-- 查询语句
SELECT c.con_id, c.name AS pdb_name, s.username, s.sid, a.value
FROM v$sesstat a, v$statname b, v$session s, v$containers c
WHERE a.statistic# = b.statistic#
AND s.sid = a.sid
AND c.con_id = s.con_id
AND b.name = 'opened cursors current'
ORDER BY a.value DESC;
- 输出示例:
Container ID PDB Name Username Session ID Open Cursors
------------ --------------- --------------- ---------- ------------
4 BIUAT SINOET 1947 55
7 ZTUAT APP 1464 2
7 ZTUAT ZT_CORE_READ 26 1
7 ZTUAT ZT_CORE_READ 978 1
7 ZTUAT ZT_CORE_READ 3403 1
7 ZTUAT ZT_CORE_READ 1951 1
7 ZTUAT ZT_CORE_READ 2431 1
7 ZTUAT ZT_CORE_READ 2439 1
7 ZTUAT ZT_CORE_READ 2914 1
7 ZTUAT ZT_CORE_READ 3395 1
7 ZTUAT ZT_CORE_READ 12 1
1 CDB$ROOT SYS 2920 1
7 ZTUAT ZT_CORE_READ 1942 1
13 rows selected.
- 处理:针对消耗 PDB 优化代码或分配更多资源。
- 参考:Monitoring Open Cursors & Troubleshooting ORA-1000 Errors (文档 ID 76684.1)
本文由mdnice多平台发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。