【问题标题】:JDBC Oracle - Fetch explain plan for queryJDBC Oracle - 获取查询解释计划
【发布时间】:2011-05-21 13:14:46
【问题描述】:

我想知道如何使用 Java 获取解释计划。我需要这个的原因是因为我们有一个框架,特殊用户可以在其中制作报告。这些报告有时会构建大量查询,我们希望在其中即时解释并存储成本。这样我们可以稍后分析高成本查询并进行优化。

给出非法列异常的示例代码:

ResultSet rs = null;
   try {
        oracle = ConnectionManager.getConnection(ConnectionManager.Test);
        pstmt = oracle.prepareStatement("begin execute immediate 
        'explain plan for SELECT   1 from Dual'; end;");
        rs = pstmt.executeQuery();
        while (rs.next()) {
            System.out.println(rs.getString(1));
        }

【问题讨论】:

    标签: java oracle jdbc sql-execution-plan explain


    【解决方案1】:

    使用这个:

    oracle = ConnectionManager.getConnection(ConnectionManager.Test); stmt = oracle.createStatement() stmt.execute("解释从 Dual 中选择 1 的计划"); rs = stmt.executeQuery("从表中选择plan_table_output(dbms_xplan.display())"); 而(rs.next()) { System.out.println(rs.getString(1)); }

    【讨论】:

    • 谢谢!这行得通。你知道如何只获取成本而不是整个计划表吗?
    • 查看 dbms_xplan.display() 的文档。您可以将参数传递给它,以选择您需要的信息
    【解决方案2】:

    还有一种方法可以通过DBMS_XPLAN.DISPLAY_CURSOR 显示用于在此会话中运行最后一个查询的真实计划。感兴趣的查询不需要在前面加上EXPLAIN PLAN FOR

    try (Statement st = connection.createStatement()) {
        try (ResultSet rs = st.executeQuery(
                "select plan_table_output from table(dbms_xplan.display_cursor())")) {
            while (rs.next()) {
                System.out.println(rs.getString(1));
            }
        }
    }
    

    请注意,用户需要被授予以下权限才能使用DBMS_XPLAN.DISPLAY_CURSOR

    GRANT SELECT ON v_$session TO USER;
    GRANT SELECT ON v_$sql_plan TO USER;
    GRANT SELECT ON v_$sql_plan_statistics_all TO USER;
    GRANT SELECT ON v_$sql TO USER;
    

    学分转到https://myoracledbablog.wordpress.com/2016/07/26/dbms_xplan-and-the-user-has-no-select-privilege-on-v-error/

    另见https://blogs.oracle.com/optimizer/how-do-i-display-and-read-the-execution-plans-for-a-sql-statement


    但我体验过,在执行查询后立即调用 dbms_xplan.display_cursor() 可能仍会返回不相关的结果,以防多线程应用使用共享连接池。

    这可以通过在v$sql 系统视图中搜索最新的sql_id 并将其作为参数提供给dbms_xplan.display_cursor 来解决。

    所以这里有一个准备好使用 java 代码通过它的 sql 记录最近执行的查询的实际执行计划(可能是部分的)。

    public void explainActualPlan(String sql, boolean sqlIsPartial, Logger log) {
        if (!log.isTraceEnabled()) return;
        try (Connection connection = dataSource.getConnection()) {
            String sqlId;
            String sqlFilter = sqlIsPartial
                    ? "sql_text like '%' || ? || '%'"
                    //+ " and parsing_schema_id = sys_context('USERENV', 'CURRENT_SCHEMAID')"
                    : (sql.length() <= 1000 ? "sql_text = ?" : "dbms_lob.compare(sql_fulltext, ?) = 0");
            try (PreparedStatement st = connection.prepareStatement(
                    "select sql_id from v$sql where " + sqlFilter +
                            " order by last_active_time desc fetch next 1 row only")) {
                st.setString(1, sql);
                try (ResultSet rs = st.executeQuery()) {
                    if (rs.next()) {
                        sqlId = rs.getString(1);
                    } else {
                        log.warn("Can't find sql_id for sql '{}'. Has it really been just executed?", sql);
                        return;
                    }
                }
            }
            String planFormat = "TYPICAL";
            if (sql.contains("GATHER_PLAN_STATISTICS")) {
                planFormat += " ALLSTATS LAST +cost +bytes OUTLINE";
            }
            try (PreparedStatement st = connection.prepareStatement(
                    "select plan_table_output from table(dbms_xplan.display_cursor(" +
                            "sql_id => ?, format => '" + planFormat + "'))")) {
                st.setString(1, sqlId);
                try (ResultSet rs = st.executeQuery()) {
                    StringBuilder sb = new StringBuilder("Last query plan:\n");
                    while (rs.next()) {
                        sb.append(rs.getString(1)).append('\n');
                    }
                    log.trace(sb.toString());
                }
            }
        } catch (Exception e) {
            log.warn("Failed to explain query plan for '{}'", sql, e);
            log.warn("Check that permissions are granted to the current db user:\n"
                    + "GRANT SELECT ON v_$session TO <USER>;\n"
                    + "GRANT SELECT ON v_$sql_plan TO <USER>;\n"
                    + "GRANT SELECT ON v_$sql_plan_statistics_all TO <USER>;\n"
                    + "GRANT SELECT ON v_$sql TO <USER>;\n"
            );
        }
    }
    

    一些注意事项:

    • Oracle 在将查询文本存储到v$sql 之前,总是将准备好的语句参数从? 转换为:n 语法,因此使用? 的sql 搜索将找不到任何匹配项
    • v$sql.sql_text(截断到前 1000 个字符)和v$sql.sql_fulltext(完整的 CLOB)都存储没有换行符的 sql 文本,因此可能需要在查询中使用 a joinV$SQLTEXT_WITH_NEWLINES文字
    • LIKE匹配用于部分模式,所以可能需要escape'%'和'_'特殊字符
    • 我检查了 Oracle 是否允许在提示注释中包含任何未知字符串,例如 /*+ labuda FIRST_ROWS(200) */。如果附录是有效标识符(字母数字并以字母开头),它仍会应用已知提示。这可能有助于通过将一些哈希码附加到提示子句来跟踪感兴趣的查询。
    • v@sql 可以额外被and parsing_schema_id = sys_context('USERENV', 'CURRENT_SCHEMAID') 过滤,但这会排除一些计划,以防数据库实例被不同架构中的几个类似应用程序使用且具有完全匹配的 sql 请求
    • 如果使用GATHER_PLAN_STATISTICS 提示执行 sql,上面的代码会在计划输出中提供更多详细信息

    这是来自my another answer的查询的上述代码输出示例:

    22:54:53.558 TRACE o.f.adminkit.AdminKitSelectorQuery - Last query plan:
    SQL_ID  c67mmq4wg49sx, child number 0
    -------------------------------------
    select * from (select * from (select /*+ FIRST_ROWS(200) 
    INDEX_RS_DESC("FR_MESSAGE_PART" ("TS")) GATHER_PLAN_STATISTICS */ "ID", 
    "MESSAGE_TYPE_ID", "TS", "REMOTE_ADDRESS", "TRX_ID", 
    "PROTOCOL_MESSAGE_ID", "MESSAGE_DATA_ID", "TEXT_OFFSET", "TEXT_SIZE", 
    "BODY_OFFSET", "BODY_SIZE", "INCOMING" from "FR_MESSAGE_PART" where 
    "TS" + 0 >= :1 and "TS" < :2 and "ID" >= 376894993815568384 and "ID" < 
    411234940974268416 order by "TS" DESC) where ROWNUM <= 200) offset 180 
    rows
    
    Plan hash value: 2499404919
    
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                                 | Name                  | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | Pstart| Pstop | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                          |                       |      1 |        |       |       |   640K(100)|          |       |       |     20 |00:00:00.01 |     322 |       |       |          |
    |*  1 |  VIEW                                     |                       |      1 |    200 |   130K|       |   640K  (1)| 00:00:26 |       |       |     20 |00:00:00.01 |     322 |       |       |          |
    |   2 |   WINDOW NOSORT                           |                       |      1 |    200 |   127K|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |   142K|   142K|          |
    |   3 |    VIEW                                   |                       |      1 |    200 |   127K|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |       |       |          |
    |*  4 |     COUNT STOPKEY                         |                       |      1 |        |       |       |            |          |       |       |    200 |00:00:00.01 |     322 |       |       |          |
    |   5 |      VIEW                                 |                       |      1 |    780K|   487M|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |       |       |          |
    |*  6 |       SORT ORDER BY STOPKEY               |                       |      1 |    780K|    68M|    89M|   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 | 29696 | 29696 |26624  (0)|
    |   7 |        PARTITION RANGE ITERATOR           |                       |      1 |    780K|    68M|       |   624K  (1)| 00:00:25 |     3 |     2 |    400 |00:00:00.01 |     322 |       |       |          |
    |*  8 |         COUNT STOPKEY                     |                       |      2 |        |       |       |            |          |       |       |    400 |00:00:00.01 |     322 |       |       |          |
    |*  9 |          TABLE ACCESS BY LOCAL INDEX ROWID| FR_MESSAGE_PART       |      2 |    780K|    68M|       |   624K  (1)| 00:00:25 |     3 |     2 |    400 |00:00:00.01 |     322 |       |       |          |
    |* 10 |           INDEX RANGE SCAN DESCENDING     | IX_FR_MESSAGE_PART_TS |      2 |    559K|       |       | 44368   (1)| 00:00:02 |     3 |     2 |    400 |00:00:00.01 |       8 |       |       |          |
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    
    Outline Data
    -------------
    
      /*+
          BEGIN_OUTLINE_DATA
          IGNORE_OPTIM_EMBEDDED_HINTS
          OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
          DB_VERSION('12.1.0.2')
          OPT_PARAM('optimizer_dynamic_sampling' 0)
          OPT_PARAM('_optimizer_dsdir_usage_control' 0)
          FIRST_ROWS(200)
          OUTLINE_LEAF(@"SEL$3")
          OUTLINE_LEAF(@"SEL$2")
          OUTLINE_LEAF(@"SEL$1")
          OUTLINE_LEAF(@"SEL$4")
          NO_ACCESS(@"SEL$4" "from$_subquery$_004"@"SEL$4")
          NO_ACCESS(@"SEL$1" "from$_subquery$_001"@"SEL$1")
          NO_ACCESS(@"SEL$2" "from$_subquery$_002"@"SEL$2")
          INDEX_RS_DESC(@"SEL$3" "FR_MESSAGE_PART"@"SEL$3" ("FR_MESSAGE_PART"."TS"))
          END_OUTLINE_DATA
      */
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - filter("from$_subquery$_004"."rowlimit_$$_rownumber">180)
       4 - filter(ROWNUM<=200)
       6 - filter(ROWNUM<=200)
       8 - filter(ROWNUM<=200)
       9 - filter("ID">=376894993815568384)
      10 - access("TS"<:2)
           filter((INTERNAL_FUNCTION("TS")+0>=:1 AND "TS"<:2))
    

    【讨论】:

      猜你喜欢
      • 2011-02-16
      • 1970-01-01
      • 2017-08-03
      • 1970-01-01
      • 2012-11-12
      • 2012-12-16
      • 2018-03-30
      • 2010-09-09
      • 1970-01-01
      相关资源
      最近更新 更多