You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Oracle执行后的DDL语句(如CTAS)未显示在V$SQL视图中?如何获取其SQL_ID用于SQL计划基线?

为什么Oracle的DDL(如CTAS)不在V$SQL中,以及如何获取其SQL_ID

一、为什么V$SQL看不到CTAS这类DDL?

V$SQL视图主要存储的是可以被共享复用的SQL语句——也就是那些执行后会被缓存到共享池、后续相同语句可以直接复用执行计划的SQL(比如SELECT、INSERT、UPDATE这类DML)。

但DDL语句(包括CTAS)有个关键特性:它们被Oracle标记为不可共享(non-sharable)。原因很简单:

  • DDL是一次性的元数据变更操作,执行后会直接修改数据字典,几乎没有被重复执行的需求;
  • Oracle不会将这类语句缓存到共享池,执行完成后就会从V$SQL中被清理掉(甚至很多时候根本不会被写入V$SQL)。

不过要注意:CTAS里的SELECT子查询部分,其实是会被记录到V$SQL中的,但外层的CREATE TABLE ... AS这个DDL本身不会出现在V$SQL里。

二、如何获取DDL的SQL_ID?

既然V$SQL靠不住,我们可以通过以下几种方式来捕获CTAS这类DDL的SQL_ID:

1. 执行DDL时强制生成监控记录(推荐即时捕获)

给DDL语句加上/*+ MONITOR */提示,强制Oracle生成SQL监控记录,之后就能在V$SQL_MONITOR视图中查到SQL_ID:

/*+ MONITOR */
CREATE TABLE my_new_table AS SELECT * FROM my_source_table WHERE id < 1000;

执行后立即查询:

SELECT sql_id, sql_text
FROM v$sql_monitor
WHERE sql_text LIKE '%CREATE TABLE my_new_table%'
AND status = 'DONE';

如果DDL执行时间很短(比如几秒内),可能需要先设置ALTER SESSION SET SQL_MONITOR_FORCE_TRACING = TRUE;来确保监控记录被生成。

2. 使用DBMS_SQL包执行DDL,直接获取SQL_ID

如果是通过PL/SQL执行DDL,可以用DBMS_SQL包来捕获SQL_ID,执行完就能直接拿到:

SET SERVEROUTPUT ON;
DECLARE
  l_cursor NUMBER;
  l_sql_id VARCHAR2(13);
  l_sql_stmt VARCHAR2(1000) := 'CREATE TABLE my_plsql_table AS SELECT * FROM my_source_table';
BEGIN
  l_cursor := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(l_cursor, l_sql_stmt, DBMS_SQL.NATIVE);
  l_sql_id := DBMS_SQL.LAST_SQL_ID; -- 获取刚解析的SQL_ID
  DBMS_SQL.EXECUTE(l_cursor);
  DBMS_SQL.CLOSE_CURSOR(l_cursor);
  DBMS_OUTPUT.PUT_LINE('DDL的SQL_ID是: ' || l_sql_id);
END;
/

3. 从AWR历史记录中查询(针对已执行的DDL)

如果DDL已经执行过一段时间,且AWR快照保留了相关记录,可以查询DBA_HIST_SQLTEXT视图:

SELECT sql_id, sql_text
FROM dba_hist_sqltext
WHERE sql_text LIKE '%CREATE TABLE%AS SELECT%'
AND sql_text NOT LIKE '%SELECT%CREATE TABLE%'; -- 过滤掉子查询里的内容

注意:这个方法依赖AWR的快照频率和保留时间,如果DDL执行时间超过AWR保留期,就查不到了。

4. 从V$SESSION中临时捕获(执行过程中)

在DDL执行的过程中(比如大表的CTAS需要跑几分钟),可以查询当前会话的SQL_ID:

SELECT sql_id
FROM v$session
WHERE sid = <你的会话SID>
AND serial# = <你的会话SERIAL#>;

但这个方法只能在DDL执行时拿到,执行完成后会话的sql_id会被清空,所以适合长时间运行的DDL。

三、获取SQL_ID后用于SQL计划基线

拿到SQL_ID后,你可以用DBMS_SPM包来创建或加载SQL计划基线。比如:

-- 从SQL监控记录中加载计划基线
DECLARE
  l_plans_loaded NUMBER;
BEGIN
  l_plans_loaded := DBMS_SPM.LOAD_PLANS_FROM_SQLSET(
    sqlset_name => 'MY_SQLSET',
    basic_filter => 'sql_id = ''你的SQL_ID'''
  );
  DBMS_OUTPUT.PUT_LINE('加载了' || l_plans_loaded || '个计划基线');
END;
/

或者如果已经有执行计划的话,也可以直接创建基线:

DECLARE
  l_baseline_name VARCHAR2(100);
BEGIN
  l_baseline_name := DBMS_SPM.CREATE_SQL_PLAN_BASELINE(
    sql_id => '你的SQL_ID',
    plan_hash_value => <计划HASH值>
  );
  DBMS_OUTPUT.PUT_LINE('创建的基线名称: ' || l_baseline_name);
END;
/

内容的提问来源于stack exchange,提问作者Yaser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:29:06