Oracle数据库SQL的内存结构:父游标、子游标等介绍

在这里插入图片描述

好的,我们将深入探讨 Oracle 共享池中最为核心的内存结构之一:SQL 游标(Cursor) 的存储方式,特别是 父游标(Parent Cursor)子游标(Child Cursor) 的层次结构。理解这个概念是进行 SQL 调优、解决性能问题的基石。


1. 核心概念:为什么需要父游标和子游标?

官方/专业解释

Oracle 引入父游标和子游标的概念,主要是为了高效地管理和共享 SQL 语句的执行计划,同时处理一种特殊情况:即两条文本完全相同的 SQL 语句,可能因执行环境的不同而需要生成不同的执行计划

通俗解释

想象一个学校的教务处(共享池)。

  • 一位老师提交了一份名为《2023级物理期末考试A卷》的试卷(SQL文本)。
  • 教务处看到试卷名,就会去档案柜里找是不是已经有同名的试卷(在父游标中查找)。
  • 他们发现,档案柜里《2023级物理期末考试A卷》的文件夹(父游标)下面,竟然有好几份答案(执行计划):
    • 答案一(子游标1): 适用于普通班(优化器模式=ALL_ROWS)。
    • 答案二(子游标2): 适用于尖子班(优化器模式=FIRST_ROWS)。
    • 答案三(子游标3): 使用了和答案一不同的物理试卷(schema下有一张同名但结构不同的表)。

教务处需要根据班级的不同情况(执行环境),从同一个文件夹里拿出不同的答案来批改试卷。父游标和子游标的机制就是为了高效、准确地管理这种“一题多解”的情况。


2. 父游标 (Parent Cursor)

官方/专业解释

父游标是库缓存(Library Cache)中一个基础结构,它代表了一个被提交的 SQL 语句的原始文本

  • 键(Key): 父游标的唯一标识是 SQL 文本的哈希值。只要 SQL 文本有一个字符不同(包括空格、大小写),其哈希值就不同,从而会产生一个全新的、不同的父游标。
  • 内容: 它主要存储指向 SQL 文本的指针,以及一个指向子游标链表的指针。它本身不包含可执行的计划信息。
  • 目的: 提供对 SQL 文本的快速查找,并作为管理多个可能执行计划的入口点。
通俗解释

父游标就是档案柜里写有《2023级物理期末考试A卷》的那个文件夹的标签页。它的核心作用是:

  1. 快速检索: 让你能通过试卷名称快速找到对应的文件夹。
  2. 管理多版本: 这个文件夹里可以存放多份不同的答案(子游标)。

3. 子游标 (Child Cursor)

官方/专业解释

子游标是附属于某个父游标的结构,它包含了执行该 SQL 语句所需的所有具体信息。一个父游标下可以有一个或多个子游标。

  • 键(Key): 子游标的唯一性由一组 执行环境(Execution Environment) 决定,这包括:
    • 优化器模式(OPTIMIZER_MODE)
    • 涉及的表/视图/存储过程的模式(Schema)对象定义(例如,不同用户下同名的表结构可能不同)
    • 系统环境(如 NLS_SORT 等参数)
    • 绑定变量的类型和长度(在初次解析时)
    • 等等…
  • 内容: 它存储了真正的“干货”:
    • 解析树(Parse Tree)
    • 执行计划(Execution Plan)
    • 执行环境
    • 统计信息(执行次数、逻辑读等)
通俗解释

子游标就是文件夹里那份具体的《A卷答案》。但是,给普通班的答案和给尖子班的答案是不同的。

  • 为什么需要多个子游标?: 因为考试环境变了
    • 场景一: 同一个SQL,一个用户SCOTT执行和一个用户HR执行,他们名下可能都有EMPLOYEES表,但表结构、索引、数据量完全不同,必须生成两份不同的执行计划(两个子游标)。
    • 场景二: 同一个SQL,第一次执行时优化器目标是ALL_ROWS(批量最优),第二次执行时被会话级修改为FIRST_ROWS(快速响应最优),这也会产生两个子游标。

“一父多子”的结构使得完全相同的SQL文本可以在不同的环境下拥有最优的执行计划。


4. 库缓存装载与查找过程

当一条新SQL语句到来时,库缓存如何工作?

  1. 哈希与查找: 对SQL文本计算哈希值,在库缓存中寻找对应的父游标。
  2. 情况A:未找到父游标(硬解析)
    • 创建一个新的父游标。
    • 然后创建一个新的子游标。
    • 进行完整的解析:语法、语义检查、优化、生成执行计划。
    • 将执行计划等信息存入新创建的子游标中。
    • 这是一个成本高昂的过程。
  3. 情况B:找到父游标,然后查找子游标
    • 获取当前SQL的执行环境(用户、优化器模式等),生成一个子游标键。
    • 在父游标下的子游标链表中,寻找匹配该键的子游标。
    • 情况B1:找到匹配的子游标(软解析/软软解析)
      • 直接使用找到的执行计划,这是性能最佳的方式。
    • 情况B2:未找到匹配的子游标(硬解析)
      • 在已有的父游标下,创建一个新的子游标
      • 进行完整的解析,并将结果存入这个新的子游标。
      • 这被称为 “硬解析,但找到父游标”,比情况A的硬解析稍快一点,因为不需要创建父游标结构了。

5. 相关查询与管理 SQL 命令

查询 1:查看父游标信息(V$SQLAREA

V$SQLAREA 视图主要提供父游标级别的聚合信息。每条记录代表一个唯一的 SQL 文本。

SELECT sql_id, -- 父游标的唯一标识
       sql_text,
       version_count, -- 关键!此父游标下有多少个子游标
       executions,
       parse_calls,
       loaded_versions -- 当前加载在内存中的子游标数量
FROM v$sqlarea 
WHERE sql_text LIKE '%SELECT * FROM employees%'
ORDER BY executions DESC;

关键字段 version_count: 如果这个值很高(例如 > 5),说明这条SQL文本在不同环境下产生了多个执行计划,这可能正常,也可能意味着潜在问题(如绑定变量窥探导致的不稳定)。

查询 2:查看子游标详细信息(V$SQL

V$SQL 视图提供了子游标级别的详细信息。同一SQL文本(sql_id相同)可能有多条记录,每条代表一个子游标。

SELECT sql_id,
       child_number, -- 子游标编号
       plan_hash_value, -- 执行计划的哈希值。不同则意味着执行计划不同
       executions,
       optimizer_mode,
       parsing_schema_name, -- 解析该SQL的用户(执行环境的一部分)
       is_bind_sensitive, -- 是否对绑定变量值敏感
       is_bind_aware       -- 是否是感知绑定变量值的游标(自适应游标共享)
FROM v$sql 
WHERE sql_id = 'ayr58h1qvg3up' -- 替换为具体的sql_id
ORDER BY child_number;

这个查询可以清楚地展示一个SQL文本有多少个“孩子”(执行计划),以及每个孩子的特点。

查询 3:诊断子游标过多的原因

如果发现某个 sql_idversion_count 异常高,可以用以下查询深入分析各个子游标的不同之处。

SELECT child_number,
       plan_hash_value,
       optimizer_mode,
       parsing_schema_name,
       executions,
       buffer_gets,
       cpu_time
FROM v$sql
WHERE sql_id = 'ayr58h1qvg3up'
ORDER BY child_number;

如果 plan_hash_value 不同,说明执行计划确实不同。然后需要结合 optimizer_mode, parsing_schema_name 等字段分析导致计划不同的原因。

查询 4:查看子游标无法共享的原因

这是一个更高级的诊断,使用 DBMS_XPLAN 包来直接查看优化器为什么不能共享游标。

SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id => 'ayr58h1qvg3up', cursor_child_no => 0));

SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id => 'ayr58h1qvg3up', cursor_child_no => 1));

通过对比两个不同 child_number 的执行计划输出,可以清楚地看到优化器环境、访问路径等的差异。

管理命令:
  1. 钉住游标: 防止重要的游标被 aged out 出共享池。

    EXEC DBMS_SHARED_POOL.KEEP('ayr58h1qvg3up', 'C'); -- 'C' for Cursor
    
  2. 刷新单个游标(11g+): 比刷新整个共享池更安全。

    EXEC DBMS_SHARED_POOL.PURGE('&address, &hash_value', 'C');
    -- address和hash_value可以从v$sql中查询得到
    
  3. 解决绑定变量相关问题: 如果子游标过多是由于绑定变量窥探导致执行计划不稳定,可以考虑使用 SQL Plan Baseline 来固定一个最优的执行计划。

总结

| 组件 | 官方角色 | 通俗比喻 | 关键点 |
| :— | :— | :— | :— |
| 父游标 | SQL文本的唯一标识 | 试卷名称标签 | 由SQL文本哈希值标识,是子游标的容器 |
| 子游标 | 执行计划与环境的载体 | 具体的试卷答案 | 由执行环境标识,存储真正的执行信息 |
| V$SQLAREA | 父游标聚合视图 | 试卷目录 | 关注 version_count |
| V$SQL | 子游标详细视图 | 答案详情页 | 关注 child_numberplan_hash_value |

理解父游标和子游标的结构,使你能够深入诊断SQL共享性问题、解析开销过高的问题以及执行计划不稳定的问题,是进行数据库性能优化的核心能力之一。

欢迎关注我的公众号《IT小Chen

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值