Oracle固定执行计划的方法----SQL Profie(二)

Oracle固定执行计划的方法----SQL Profie(二)

继续学习SQL Profile的手工创建方法,SQL Profile的基本知识参考文章《Oracle固定执行计划的方法----SQL Profie(一)》

一、手工创建 SQL Profile

某些场景下我们需要手工创建 SQL Profile,例如:

  • 稳定一个已知最优执行计划的 SQL。
  • 在不修改应用 SQL 的情况下强制改变执行计划。

手工构建sql profile 的hint(提示)的两种方法

1、自己手工编写提示,写入sys.sqlprof_attr数据类型,再通过DBMS_SQLTUNE.IMPORT_SQL_PROFILE,写入profile配置文件中

2、更常用的方法,从 Oracle 10g 开始,V$SQL_PLAN 中包含了 SQL 语句的 OUTLINE Data(大纲数据),这些数据包含了稳定执行计划所需要的所有 Hints,基于此,对于需要通过SQL Profile来稳定执行计划的SQL语句,可以通过添加hint提示后执行语句,不断进行尝试,直到该SQL语句的执行计划达到我们的预期,然后根据预期的执行计划,获取OUTLINE Data(大纲数据),最终通过IMPORT_SQL_PROFILE过程,写入profile配置文件中。

使用 IGNORE_OPTIM_EMBEDDED_HINTS 覆盖原始 Hints

如果原始 SQL 中已经包含了 Hints,而你想让 SQL Profile 覆盖这些 Hints,可以在 SQL Profile 中保留 IGNORE_OPTIM_EMBEDDED_HINTS 提示。这样 SQL Profile 中的 Hints 将优先于 SQL 文本中的 Hints。

DBMS_SQLTUNE.IMPORT_SQL_PROFILE 过程

手工创建 SQL Profile 的核心工具是 DBMS_SQLTUNE.IMPORT_SQL_PROFILE 过程。

sql 复制代码
DESC dbms_sqltune

PROCEDURE IMPORT_SQL_PROFILE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SQL_TEXT                       CLOB                    IN
 PROFILE                        SQLPROF_ATTR            IN
 NAME                           VARCHAR2                IN     DEFAULT
 DESCRIPTION                    VARCHAR2                IN     DEFAULT
 CATEGORY                       VARCHAR2                IN     DEFAULT
 VALIDATE                       BOOLEAN                 IN     DEFAULT
 REPLACE                        BOOLEAN                 IN     DEFAULT
 FORCE_MATCH                    BOOLEAN                 IN     DEFAULT

参数列表:

参数名 类型 输入/输出 默认值? 说明
SQL_TEXT CLOB IN - 需要绑定 SQL Profile 的 SQL 语句文本
PROFILE SQLPROF_ATTR IN - Hints 集合
NAME VARCHAR2 IN DEFAULT SQL Profile 名称
DESCRIPTION VARCHAR2 IN DEFAULT 描述信息
CATEGORY VARCHAR2 IN DEFAULT 分类(用于控制哪些会话可使用)
VALIDATE BOOLEAN IN DEFAULT 是否验证 Hints 有效性
REPLACE BOOLEAN IN DEFAULT 是否替换已存在的同名 SQL Profile
FORCE_MATCH BOOLEAN IN DEFAULT 是否强制匹配(忽略空格、大小写差异)

其中 PROFILE 参数的类型为 SYS.SQLPROF_ATTR,这是一个 VARCHAR2 的集合类型:

sql 复制代码
SELECT text FROM dba_source WHERE name = 'SQLPROF_ATTR' AND owner = 'SYS';

输出:

复制代码
TYPE sqlprof_attr AS VARRAY(2000) OF VARCHAR2(500)

这意味着每个 SQL Profile 最多可以包含 2000 个 Hints ,每个 Hint 的长度不超过 500 个字符。

二、案例(从 Outline Data 创建 SQL Profile)

Oracle数据库版本:11.2.0.4

数据库测试用户: USER1

1、准备工作

sql 复制代码
-- 创建 T1 表:取 dba_objects 的前 50000 行
CREATE TABLE t1 AS
SELECT object_id, object_name
FROM dba_objects
WHERE rownum <= 50000;

-- 创建 T2 表:复制 dba_objects 的全部数据
CREATE TABLE t2 AS
SELECT * FROM dba_objects;

select count(*) from t1;
  COUNT(*)
----------
     50000
     
select count(*) from t2;
  COUNT(*)
----------
     86315
     
-- 在 T2 表的 object_id 上创建索引
CREATE INDEX ind_t2 ON t2(object_id);

-- 收集 T1和T2 表统计信息
BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'user1',
                                TABNAME=>'T1', 
                                ESTIMATE_PERCENT=>1,
                                METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',
                                NO_INVALIDATE=>FALSE,
                                CASCADE=>TRUE,
                                DEGREE => 4); 
END; 
/

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'user1',
                                TABNAME=>'T2', 
                                ESTIMATE_PERCENT=>1,
                                METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',
                                NO_INVALIDATE=>FALSE,
                                CASCADE=>TRUE,
                                DEGREE => 4); 
END; 
/

2、执行目标 SQL 并观察执行计划

sql 复制代码
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
  AND t1.object_id = t2.object_id;  
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1838229974
-- 执行计划,使用哈希连接
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |  2512 |   100K|   414   (1)| 00:00:05 |
|*  1 |  HASH JOIN         |      |  2512 |   100K|   414   (1)| 00:00:05 |
|*  2 |   TABLE ACCESS FULL| T1   |  2512 | 75360 |    70   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   | 87356 |   938K|   344   (1)| 00:00:05 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
   2 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOT NULL)

Statistics
----------------------------------------------------------
          5  recursive calls
          0  db block gets
       1485  consistent gets      
          0  physical reads
          0  redo size
       2923  bytes sent via SQL*Net to client
        552  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         58  rows processed

**执行计划结果:**两张表都是全表扫描(FULL TABLE SCAN)+ 哈希连接(HASH JOIN)

3、给SQL添加hint提示,使得语句的执行计划达到预期

语句添加提示后(/*+ sql_profile_test2 USE_NL(t1 t2) INDEX(t2) */ ),再次执行

复制代码
set autotrace on
SELECT /*+ sql_profile_test2 USE_NL(t1 t2) INDEX(t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
  AND t1.object_id = t2.object_id;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1022743391
---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|   1 |  NESTED LOOPS                |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|   2 |   NESTED LOOPS               |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2512 | 75360 |    70   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | T2_IDX |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL)
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        308  consistent gets
          0  physical reads
          0  redo size
       2165  bytes sent via SQL*Net to client
        541  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         38  rows processed

结果:添加提示后,确实获得了我们想要的执行计划(嵌套循环连接和T2表索引)

4、获取 Outline Data提示集合

SQL语句在我们添加提示后,达到我们想要的执行计划语句的。

通过DBMS_XPLAN.DISPLAY_CURSOR('','','outline')方法,获取outline提示集合,如下

复制代码
1)获取SQL语句在内存中的信息
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number 
  FROM v$sql 
 WHERE sql_text LIKE '%sql_profile_test2%' 
   AND sql_text NOT LIKE '%v$sql%';
   
SQL_TEXT                                 SQL_ID        HASH_VALUE CHILD_NUMBER
---------------------------------------- ------------- ---------- ------------
SELECT /*+ sql_profile_test2 USE_NL(t1 t 2qputnnd0wc1m  437137459            0
2) INDEX(t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%'   AN
D t1.object_id = t2.object_id

2)获取SQL语句在内存中的实际执行计划,带outline提示信息的输出!
set linesize 200 pagesize 999
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('2qputnnd0wc1m','','outline'));


Plan hash value: 1022743391
---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |       |       |  5095 (100)|          |
|   1 |  NESTED LOOPS                |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|   2 |   NESTED LOOPS               |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2512 | 75360 |    70   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | T2_IDX |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Outline Data   --下面这一部分就是outline提示集合!!
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      FULL(@"SEL$1" "T1"@"SEL$1")
      INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))
      LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")
      USE_NL(@"SEL$1" "T2"@"SEL$1")
      NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

5、编写 PL/SQL 脚本导入SQL Profile

通过上面获取的outline提示集合来编写和构建

复制代码
DECLARE
  sql_txt CLOB;
  v_hint       SYS.SQLPROF_ATTR;
BEGIN
  -- 1. 定义 SQL 文本 (尽量完全匹配,force_match=>true匹配模式变量值可以不一样)
  sql_txt := q'[
    select t1.*, t2.owner 
    from t1, t2 
    where t1.object_name like '%T1%' 
    and t1.object_id = t2.object_id
  ]';

  -- 2. 定义 Hints (从 Outline Data 中提取)
  v_hint := SYS.SQLPROF_ATTR(
     'BEGIN_OUTLINE_DATA',
     'IGNORE_OPTIM_EMBEDDED_HINTS',
     'OPTIMIZER_FEATURES_ENABLE(''11.2.0.4'')',
     'DB_VERSION(''11.2.0.4'')',
     'ALL_ROWS',
     'OUTLINE_LEAF(@"SEL$1")',
     'FULL(@"SEL$1" "T1"@"SEL$1")',
     'INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))',
     'LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")',
     'USE_NL(@"SEL$1" "T2"@"SEL$1")',
     'NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")',
     'END_OUTLINE_DATA'   
  );

  -- 3. 导入 Profile
  DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
    sql_text    => sql_txt,
    profile     => v_hint,
    name        => 'MANUAL_PROFILE_T1_T2', -- 自定义名称
    category    => 'DEFAULT',
    description => '手工锁定 NL 连接',
    force_match => TRUE  -- 关键:TRUE 表示忽略字面量差异
  );
  
  DBMS_OUTPUT.PUT_LINE('Profile 创建成功');
END;
/

6、验证SQL Profile是否创建

复制代码
SELECT name, category, signature, type, status, force_matching 
FROM dba_sql_profiles;

NAME                           CATEGORY                        SIGNATURE TYPE    STATUS   FOR
------------------------------ ------------------------------ ---------- ------- -------- ---
MANUAL_PROFILE_T1_T2           DEFAULT                        3.9607E+18 MANUAL  ENABLED  YES

7、验证 SQL Profile 中的 Hints 是否被正确存储

sql 复制代码
SELECT COMP_DATA
FROM dba_sql_profiles a, sys.sqlobj$data b
WHERE a.signature = b.signature
  AND a.name = 'MANUAL_PROFILE_T1_T2';

输出:

复制代码
  COMP_DATA
--------------------------------------------------------------------------------
<outline_data><hint><![CDATA[BEGIN_OUTLINE_DATA]]></hint><hint><![CDATA[IGNORE_O
PTIM_EMBEDDED_HINTS]]></hint><hint><![CDATA[OPTIMIZER_FEATURES_ENABLE('11.2.0.4'
)]]></hint><hint><![CDATA[DB_VERSION('11.2.0.4')]]></hint><hint><![CDATA[ALL_ROW
S]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1")]]></hint><hint><![CDATA[FULL(@"
SEL$1" "T1"@"SEL$1")]]></hint><hint><![CDATA[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."
OBJECT_ID"))]]></hint><hint><![CDATA[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")
]]></hint><hint><![CDATA[USE_NL(@"SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[N
LJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[END_OUTLINE_DATA]]></
hint></outline_data>

8、验证SQL Profile是否生效

执行对应的 SQL:

sql 复制代码
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
  AND t1.object_id = t2.object_id;
set autotrace off

查看执行计划:

复制代码
Execution Plan
----------------------------------------------------------
Plan hash value: 1386590592

---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|   1 |  NESTED LOOPS                |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|   2 |   NESTED LOOPS               |        |  2512 |   100K|  5095   (1)| 00:01:02 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2512 | 75360 |    70   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | IND_T2 |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOTNULL)
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
   - SQL profile "MANUAL_PROFILE_T1_T2" used for this statement


Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
        318  consistent gets
          1  physical reads
          0  redo size
       2923  bytes sent via SQL*Net to client
        552  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         58  rows processed

可以看到 Note 显示 SQL Profile 被使用了,执行计划变为是嵌套循环连接 + 索引范围扫描------达到预期效果。

9、验证 SQL Profile 的稳定性

现在,我们大幅度修改 T1 表的统计信息行数,模拟统计信息严重失真的场景:

sql 复制代码
EXEC dbms_stats.set_table_stats('USER1', 'T1', numrows => 50000000);

再次执行 SQL:

sql 复制代码
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
  AND t1.object_id = t2.object_id;
set autotrace off

执行计划:

复制代码
Execution Plan
----------------------------------------------------------
set autotrace off
Plan hash value: 1386590592

---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|   1 |  NESTED LOOPS                |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|   2 |   NESTED LOOPS               |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2500K|    71M|   437  (84)| 00:00:06 |
|*  4 |    INDEX RANGE SCAN          | IND_T2 |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOT  NULL)
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
   - SQL profile "MANUAL_PROFILE_T1_T2" used for this statement
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        313  consistent gets
          0  physical reads
          0  redo size
       2923  bytes sent via SQL*Net to client
        552  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         58  rows processed

观察结果:

  • 优化器虽然评估 T1 表返回 2500K 行(因为统计信息显示 T1 有 5000 万行),但执行计划仍然保持不变------仍然是嵌套循环连接 + 索引范围扫描。

这说明:SQL Profile 成功稳定了执行计划 ,不受统计信息剧烈变化的影响。但需要注意的是,这里的稳定是通过强制指定 Hints(USE_NL 和 INDEX)实现的,而非通过 OPT_ESTIMATE 缩放系数。

三、使用 COE_XFR_SQL_PROFILE 脚本构建SQL Profile------自动化利器

从 MOS(My Oracle Support)可以下载到 SQLT (SQLTXPLAIN) 工具包。其中包含的 coe_xfr_sql_profile.sql 脚本可以自动从 shared pool 或 AWR 中提取指定 SQL ID 的 Outline Data 并生成创建 SQL Profile 的脚本。

主要作用:如果一个SQL语句包含多条执行计划,且经常出现执行速度变化的情况,可以通过该脚本,选择一个预期的执行计划(当前内存中、AWR历史资料库中保存的执行计划),生成SQL Profile,来稳定该语句的执行计划!

6.1 下载与准备

  1. 从 MOS 下载 sqlt.zip 文件(文档 ID: 215187.1)。
  2. 解压后,在 目录中找到 coe_xfr_sql_profile.sql 脚本。

6.2 执行脚本

首先在 shared pool 中执行目标 SQL:

sql 复制代码
SELECT /*+ sql_profile_test3 */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
  AND t1.object_id = t2.object_id;
  
--注意需要查询 plan_hash_value 这一列
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
  FROM v$sql 
 WHERE sql_text LIKE '%sql_profile_test3%' 
   AND sql_text NOT LIKE '%v$sql%';

SQL_TEXT                                 SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ sql_profile_test3 */ t1.*, t2 cdy6d6t5quzfd 1265466829            0      2959412835
.owner FROM t1, t2 WHERE t1.object_name
LIKE '%T1%'   AND t1.object_id = t2.obje
ct_id

执行 coe_xfr_sql_profile.sql 脚本:

sql 复制代码
@coe_xfr_sql_profile.sql

SQL> @coe_xfr_sql_profile.sql

Parameter 1:
SQL_ID (required)

Enter value for 1: cdy6d6t5quzfd   ---输入sql_id


PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
     2959412835        .033

Parameter 2:
PLAN_HASH_VALUE (required)

Enter value for 2: 2959412835     --输入语句想要生成sql profile的plan_hash_value

Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID         : "cdy6d6t5quzfd"
PLAN_HASH_VALUE: "2959412835"

SQL>BEGIN
  2    IF :sql_text IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20100, 'SQL_TEXT for SQL_ID &&sql_id. was not found in memory (gv$sqltext_with_newlines) or AWR (dba_hist_sqltext).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
SQL>BEGIN
  2    IF :other_xml IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20101, 'PLAN for SQL_ID &&sql_id. and PHV &&plan_hash_value. was not found in memory (gv$sql_plan) or AWR (dba_hist_sql_plan).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;

Execute coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql    --生成一个脚本文件,运行该脚本,就可以创建sql profile
on TARGET system in order to create a custom SQL Profile
with plan 2959412835 linked to adjusted sql_text.


COE_XFR_SQL_PROFILE completed.

脚本会生成另一个脚本 coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql。

6.3 执行生成的脚本

运行生成的脚本即可创建 SQL Profile:

sql 复制代码
@coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql

SQL>@coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql
SQL>REM
SQL>REM $Header: 215187.1 coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql 11.4.4.4 2026/08/15 carlos.sierra $
SQL>REM
SQL>REM Copyright (c) 2000-2012, Oracle Corporation. All rights reserved.
SQL>REM
SQL>REM AUTHOR
SQL>REM   carlos.sierra@oracle.com
SQL>REM
SQL>REM SCRIPT
SQL>REM   coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql
SQL>REM
SQL>REM DESCRIPTION
SQL>REM   This script is generated by coe_xfr_sql_profile.sql
SQL>REM   It contains the SQL*Plus commands to create a custom
SQL>REM   SQL Profile for SQL_ID cdy6d6t5quzfd based on plan hash
SQL>REM   value 2959412835.
SQL>REM   The custom SQL Profile to be created by this script
SQL>REM   will affect plans for SQL commands with signature
SQL>REM   matching the one for SQL Text below.
SQL>REM   Review SQL Text and adjust accordingly.
SQL>REM
SQL>REM PARAMETERS
SQL>REM   None.
SQL>REM
SQL>REM EXAMPLE
SQL>REM   SQL> START coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql;
SQL>REM
SQL>REM NOTES
SQL>REM   1. Should be run as SYSTEM or SYSDBA.
SQL>REM   2. User must have CREATE ANY SQL PROFILE privilege.
SQL>REM   3. SOURCE and TARGET systems can be the same or similar.
SQL>REM   4. To drop this custom SQL Profile after it has been created:
SQL>REM  EXEC DBMS_SQLTUNE.DROP_SQL_PROFILE('coe_cdy6d6t5quzfd_2959412835');
SQL>REM   5. Be aware that using DBMS_SQLTUNE requires a license
SQL>REM  for the Oracle Tuning Pack.
SQL>REM   6. If you modified a SQL putting Hints in order to produce a desired
SQL>REM  Plan, you can remove the artifical Hints from SQL Text pieces below.
SQL>REM  By doing so you can create a custom SQL Profile for the original
SQL>REM  SQL but with the Plan captured from the modified SQL (with Hints).
SQL>REM
SQL>WHENEVER SQLERROR EXIT SQL.SQLCODE;
SQL>REM
SQL>VAR signature NUMBER;
SQL>VAR signaturef NUMBER;
SQL>REM
SQL>DECLARE
  2  sql_txt CLOB;
  3  h       SYS.SQLPROF_ATTR;
  4  PROCEDURE wa (p_line IN VARCHAR2) IS
  5  BEGIN
  6  DBMS_LOB.WRITEAPPEND(sql_txt, LENGTH(p_line), p_line);
  7  END wa;
  8  BEGIN
  9  DBMS_LOB.CREATETEMPORARY(sql_txt, TRUE);
 10  DBMS_LOB.OPEN(sql_txt, DBMS_LOB.LOB_READWRITE);
 11  -- SQL Text pieces below do not have to be of same length.
 12  -- So if you edit SQL Text (i.e. removing temporary Hints),
 13  -- there is no need to edit or re-align unmodified pieces.
 14  wa(q'[SELECT /*+ sql_profile_test3 */ t1.*, t2.owner
 15  FROM t1, t2
 16  WHERE]');
 17  wa(q'[ t1.object_name LIKE '%T1%'
 18    AND t1.object_id = t2.object_id]');
 19  DBMS_LOB.CLOSE(sql_txt);
 20  h := SYS.SQLPROF_ATTR(
 21  q'[BEGIN_OUTLINE_DATA]',
 22  q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
 23  q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
 24  q'[DB_VERSION('11.2.0.4')]',
 25  q'[ALL_ROWS]',
 26  q'[OUTLINE_LEAF(@"SEL$1")]',
 27  q'[FULL(@"SEL$1" "T2"@"SEL$1")]',
 28  q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
 29  q'[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]',
 30  q'[USE_HASH(@"SEL$1" "T1"@"SEL$1")]',
 31  q'[END_OUTLINE_DATA]');
 32  :signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt);
 33  :signaturef := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt, TRUE);
 34  DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
 35  sql_text    => sql_txt,
 36  profile     => h,
 37  name        => 'coe_cdy6d6t5quzfd_2959412835',
 38  description => 'coe cdy6d6t5quzfd 2959412835 '||:signature||' '||:signaturef||'',
 39  category    => 'DEFAULT',
 40  validate    => TRUE,
 41  replace     => TRUE,
 42  force_match => FALSE /* TRUE:FORCE (match even when different literals in SQL). FALSE:EXACT (similar to CURSOR_SHARING) */ );
 43  DBMS_LOB.FREETEMPORARY(sql_txt);
 44  END;
 45  /

PL/SQL procedure successfully completed.

SQL>WHENEVER SQLERROR CONTINUE
SQL>SET ECHO OFF;

            SIGNATURE
---------------------
 10081132228680031145


           SIGNATUREF
---------------------
  7070267937381023371


... manual custom SQL Profile has been created


COE_XFR_SQL_PROFILE_cdy6d6t5quzfd_2959412835 completed

验证 SQL Profile 中的 Hints:

sql 复制代码
SELECT name, category, signature, type, status, force_matching 
FROM dba_sql_profiles;
NAME                           CATEGORY        SIGNATURE TYPE    STATUS   FOR
------------------------------ -------------- ---------- ------- -------- ---
MANUAL_PROFILE_T1_T2           DEFAULT        3.9607E+18 MANUAL  ENABLED  YES
coe_cdy6d6t5quzfd_2959412835   DEFAULT        1.0081E+19 MANUAL  ENABLED  NO

set linesize 200 pagesize 9999
set long 100000
SELECT COMP_DATA
FROM dba_sql_profiles a, sys.sqlobj$data b
WHERE a.signature = b.signature
  AND a.name = 'coe_cdy6d6t5quzfd_2959412835';

COMP_DATA
--------------------------------------------------------------------------------
<outline_data><hint><![CDATA[BEGIN_OUTLINE_DATA]]></hint><hint><![CDATA[IGNORE_O
PTIM_EMBEDDED_HINTS]]></hint><hint><![CDATA[OPTIMIZER_FEATURES_ENABLE('11.2.0.4'
)]]></hint><hint><![CDATA[DB_VERSION('11.2.0.4')]]></hint><hint><![CDATA[ALL_ROW
S]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1")]]></hint><hint><![CDATA[FULL(@"
SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[FULL(@"SEL$1" "T1"@"SEL$1")]]></hin
t><hint><![CDATA[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]]></hint><hint><![C
DATA[USE_HASH(@"SEL$1" "T1"@"SEL$1")]]></hint><hint><![CDATA[END_OUTLINE_DATA]]>
</hint></outline_data>

6.4 脚本的注意事项

  • 脚本会从 shared pool 和 AWR 中获取 SQL 执行的各个执行计划的统计信息。
  • 需要输入你认为正确的、需要稳定的执行计划的 hash value。
  • 生成的 SQL Profile 名称格式为:coe + sql_id + plan_hash_value。
  • force_match 默认为 FALSE,可以手工修改生成的脚本将其改为 TRUE。
  • 也可以按需要修改生成脚本的其他内容。

七、高级技巧:在不修改 SQL 的情况下改变执行计划

在实际生产环境中,常常遇到这样的情况:SQL 语句的执行计划有问题,或者 SQL 使用了错误的 Hints(例如 /*+ RULE */),但应用代码不能修改或短期内无法修改。

有三种解决办法:

  1. 调整统计信息------不推荐,因为复杂、不稳定(统计信息可能被重新收集),且影响面广。
  2. 使用 Outline------方法复杂。
  3. 使用 SQL Profile------推荐方案。

7.1 核心思路:"乾坤大挪移"

使用 SQL Profile 改变 SQL 执行计划的核心思路是:

  1. 取得原始 SQL 的文本(如有可能还包括 SQL_ID)。
  2. 构造一个与原始 SQL 在逻辑上、结构上完全相同的 SQL。这里强制逻辑上和结构上相同,SQL 解析的用户名、SQL 中引用对象的用户名甚至是一些谓词条件都可以不同。
  3. 执行构造的 SQL,并取得构造的 SQL 的 Outline Data。
  4. 使用原始 SQL 的文本 和构造 SQL 的 Outline Data 创建 SQL Profile。

7.2 完整演示

第一步:原始 SQL(执行计划不优)

sql 复制代码
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
  AND t1.object_id = t2.object_id;
  
  
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
  FROM v$sql 
 WHERE sql_text LIKE '%old_sql%' 
   AND sql_text NOT LIKE '%v$sql%';
   

SQL_TEXT                                 SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ old_sql full(t1) full(t2) use g7ndz4jp2j3uh 1781043024            0      2959412835
_hash(t1 t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%'   AN
D t1.object_id = t2.object_id

原始 SQL 中使用了 FULL(t1)、FULL(t2) 和 USE_HASH(t1 t2) 这些 Hints,强制使用全表扫描 + 哈希连接。执行后 SQL_ID 为 g7ndz4jp2j3uh。

执行计划:

复制代码
select * from table(dbms_xplan.display_cursor('g7ndz4jp2j3uh','',''));

Plan hash value: 2959412835
-----------------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |       |  5854 (100)|          |
|*  1 |  HASH JOIN         |      |  2500K|    97M|  1968K|  5854   (7)| 00:01:11 |
|   2 |   TABLE ACCESS FULL| T2   | 87356 |   938K|       |   344   (1)| 00:00:05 |
|*  3 |   TABLE ACCESS FULL| T1   |  2500K|    71M|       |   437  (84)| 00:00:06 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
   3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))   

第二步:构造目标 SQL(我们希望的执行计划)

构造一条在逻辑上和结构上与原始 SQL 完全相同的 SQL,但使用我们希望的执行计划:

sql 复制代码
SELECT /*+ new_sql index(t2) use_nl(t1 t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
  AND t1.object_id = t2.object_id;

set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
  FROM v$sql 
 WHERE sql_text LIKE '%new_sql%' 
   AND sql_text NOT LIKE '%v$sql%';


SQL_TEXT                                 SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ new_sql index(t2) use_nl(t1 t 0sz1znbny6v0m 3923995667            0      1386590592
2) */ t1.*, t2.owner FROM t1, t2 WHERE t
1.object_name LIKE '%T1%'   AND t1.objec
t_id = t2.object_id

执行计划:

复制代码
select * from table(dbms_xplan.display_cursor('0sz1znbny6v0m','',''));
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |       |       |  5001K(100)|          |
|   1 |  NESTED LOOPS                |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|   2 |   NESTED LOOPS               |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2500K|    71M|   437  (84)| 00:00:06 |
|*  4 |    INDEX RANGE SCAN          | IND_T2 |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

第三步:提取构造 SQL 的 Outline Data

使用 coe_xfr_sql_profile.sql 脚本提取构造 SQL 的 Outline Data:

sql 复制代码
@coe_xfr_sql_profile.sql
-- 输入构造 SQL 的 SQL_ID(0sz1znbny6v0m)和 PLAN_HASH_VALUE(1386590592)
-- /*+ new_sql index(t2) use_nl(t1 t2) */SQL 的 Outline Data

SQL>@coe_xfr_sql_profile.sql
Parameter 1:
SQL_ID (required)
Enter value for 1: 0sz1znbny6v0m
PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
     1386590592        .033
Parameter 2:
PLAN_HASH_VALUE (required)

Enter value for 2: 1386590592

Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID         : "0sz1znbny6v0m"
PLAN_HASH_VALUE: "1386590592"

SQL>BEGIN
  2    IF :sql_text IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20100, 'SQL_TEXT for SQL_ID &&sql_id. was not found in memory (gv$sqltext_with_newlines) or AWR (dba_hist_sqltext).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
SQL>BEGIN
  2    IF :other_xml IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20101, 'PLAN for SQL_ID &&sql_id. and PHV &&plan_hash_value. was not found in memory (gv$sql_plan) or AWR (dba_hist_sql_plan).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
Execute coe_xfr_sql_profile_0sz1znbny6v0m_1386590592.sql
on TARGET system in order to create a custom SQL Profile
with plan 1386590592 linked to adjusted sql_text.


COE_XFR_SQL_PROFILE completed.

生成的脚本 coe_xfr_sql_profile_0sz1znbny6v0m_1386590592.sql 中包含 Outline Data:

sql 复制代码
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
q'[DB_VERSION('11.2.0.4')]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
q'[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))]',
q'[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")]',
q'[USE_NL(@"SEL$1" "T2"@"SEL$1")]',
q'[NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]',
q'[END_OUTLINE_DATA]');

第四步:为原始 SQL 创建 SQL Profile

针对原始 SQL(SQL_ID = g7ndz4jp2j3uh,PLAN_HASH_VALUE = 2959412835)使用 coe_xfr_sql_profile.sql 生成脚本。

然后手工修改 生成的脚本 coe_xfr_sql_profile_g7ndz4jp2j3uh_2959412835.sql:

  1. 将 h := SYS.SQLPROF_ATTR(...) 那段替换成上一步得到的 Outline Data。

    复制代码
    q'[BEGIN_OUTLINE_DATA]',
    q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
    q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
    q'[DB_VERSION('11.2.0.4')]',
    q'[ALL_ROWS]',
    q'[OUTLINE_LEAF(@"SEL$1")]',
    q'[FULL(@"SEL$1" "T2"@"SEL$1")]',
    q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
    q'[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]',
    q'[USE_HASH(@"SEL$1" "T1"@"SEL$1")]',
    q'[END_OUTLINE_DATA]');
    改为
    q'[BEGIN_OUTLINE_DATA]',
    q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
    q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
    q'[DB_VERSION('11.2.0.4')]',
    q'[ALL_ROWS]',
    q'[OUTLINE_LEAF(@"SEL$1")]',
    q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
    q'[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))]',
    q'[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")]',
    q'[USE_NL(@"SEL$1" "T2"@"SEL$1")]',
    q'[NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]',
    q'[END_OUTLINE_DATA]');
  2. 将 force_match 从 FALSE 改为 TRUE。

第五步:执行修改后的脚本

sql 复制代码
@@coe_xfr_sql_profile_g7ndz4jp2j3uh_2959412835.sql
PL/SQL 过程已成功完成。
            SIGNATURE
---------------------
 15211772502623681692
           SIGNATUREF
---------------------
  1099879712749910124
... manual custom SQL Profile has been created
COE_XFR_SQL_PROFILE_g7ndz4jp2j3uh_2959412835 completed

第六步:验证效果

执行原始 SQL(注意这里将 LIKE 条件从 %T1% 改为 %T2%,以验证 force_match 是否起作用):

sql 复制代码
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
 FROM t1, t2
 WHERE t1.object_name LIKE '%T1%'
 AND t1.object_id = t2.object_id;

set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
  2    FROM v$sql 
  3   WHERE sql_text LIKE '%old_sql%' 
  4     AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT                                 SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ old_sql full(t1) full(t2) use g7ndz4jp2j3uh 1781043024            0      1386590592
_hash(t1 t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%'   AN
D t1.object_id = t2.object_id

执行计划:

复制代码
SQL>select * from table(dbms_xplan.display_cursor('g7ndz4jp2j3uh','',''));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------
SQL_ID  g7ndz4jp2j3uh, child number 0
-------------------------------------
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
FROM t1, t2 WHERE t1.object_name LIKE '%T1%'   AND t1.object_id =
t2.object_id
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |       |       |  5001K(100)|          |
|   1 |  NESTED LOOPS                |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|   2 |   NESTED LOOPS               |        |  2500K|    97M|  5001K  (1)| 16:40:18 |
|*  3 |    TABLE ACCESS FULL         | T1     |  2500K|    71M|   437  (84)| 00:00:06 |
|*  4 |    INDEX RANGE SCAN          | IND_T2 |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T2     |     1 |    11 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT   NULL))
   4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
   - SQL profile coe_g7ndz4jp2j3uh_2959412835 used for this statement

成功! SQL Profile 起作用了,执行计划从全表扫描 + 哈希连接变成了嵌套循环连接 + 索引范围扫描。

7.3 自动化改进

如果觉得手动修改脚本比较繁琐,可以修改 create_sql_profile.sql 脚本。将其中提取 Outline Data 的部分:

sql 复制代码
SELECT extractvalue(value(d), '/hint') AS outline_hints BULK COLLECT
INTO ar_profile_hints
FROM xmltable('/*/outline_data/hint' PASSING (
    SELECT xmltype(other_xml) AS xmlval
    FROM v$sql_plan
    WHERE sql_id = '&&sql_id'
      AND child_number = &&child_no
      AND other_xml IS NOT NULL
)) d;

改为:

sql 复制代码
SELECT extractvalue(value(d), '/hint') AS outline_hints BULK COLLECT
INTO ar_profile_hints
FROM xmltable('/*/outline_data/hint' PASSING (
    SELECT xmltype(other_xml) AS xmlval
    FROM v$sql_plan
    WHERE sql_id = '&&modi_sql_id'      -- 改为构造 SQL 的 SQL_ID
      AND child_number = &&modi_child_no -- 改为构造 SQL 的 child_number
      AND other_xml IS NOT NULL
)) d;

这样脚本就会自动从构造 SQL 的执行计划中提取 Outline Data,用于创建原始 SQL 的 Profile。

相关推荐
Mortalbreeze1 小时前
MySQL 基础篇(二):数据库和数据表的基本操作
linux·服务器·数据库·mysql
Zenova EdgeOS1 小时前
工业网关心跳机制实战:Keepalive、应用层探活与健康判定
服务器·网络·数据库
龙腾AI白云10 小时前
AI检索增强生成(RAG):解决大模型幻觉的核心落地技术
数据库·人工智能·机器学习·知识图谱
一 乐10 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
zzj_26261010 小时前
MySQL常用操作
数据库·mysql
其实防守也摸鱼10 小时前
Fastjson 反序列化漏洞(JNDI注入)
android·数据库·安全·oracle·自动化·fastjson·反序列化
haerapi10 小时前
把 PostgreSQL 复制巡检做成可审计闭环:确定性采集、阈值判定与受控模型归纳
数据库·postgresql
微学AI11 小时前
不让每一步都调用最贵模型:用蓝耘智能路由改造自主式研究 Agent
数据库·人工智能·蓝耘
yjb.gz12 小时前
Oracle19 RAC查看集群状态及磁盘空间情况(巡检)
数据库