紧急救场!解密ORA-01000:最大游标超限故障的全攻略(CDB/PDB与NON-CDB环境实战指南)

这是一个星期六的早晨,却突然收到数据库系统的紧急告警!日志里跳出一串让人头大的错误——ORA-01000: maximum open cursors exceeded,而截图中显示:

ORA-01000报错截图

现象是应用程序响应变慢,部分用户报告无法登录系统,尤其是你使用的是多租户架构(如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(如 PUB2SZKA)的进程 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多平台发布


老林数智运维拾遗
3 声望0 粉丝