(Oracle)SQL优化基础(一):获取执行计划

目录

一、AUTOTRACE方法

二、EXPLAIN PLAN

三、DBMS_XPLAN包

1、DISPLAY

2、DISPLAY_CURSOR

3、DISPLAY_AWR  

四、Sql Developer 工具

五、总结与分析 

1、虚假和真实

2、使用场景建议

3、个人习惯

4、最后、最后


对于SQL优化来讲,除了大神级别的人物一眼洞穿原理;我等凡人除了对那些明显逻辑错误的SQL可以撸起袖子直接干;否则起手式就是先获取目标SQL的执行计划,再根据执行计划分析目标SQL慢的原因。获取执行计划的方法多种多样,本篇文章会介绍4种用过的方法。下面先甩张图,说明本篇文章将要介绍的获取执行计划方法。

一、AUTOTRACE方法

首先说明AUTOTRACE方法是SQL plus自带的工具。 朋友们不要拿该方法去PLSQL或者其他数据库客户端连接工具中去用,不好使。(我有一个朋友,曾经干过)

AUTOTRACE用法                                                                                  

SQL> set autot
用法: SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]]

下面对用法做进一步说明

命令含义

set autot on

该命令会运行SQL且显示运行结果、执行计划、统计信息。

set autot trace

该命令会运行SQL,但不显示运行结果,会显示执行计划和统计信息。

set autot trace exp

该命令不执行查询语句,但会执行DML语句,只显示执行计划。

set autot trace stat

该命令会运行SQL,但只显示统计信息。

set autot off

该命令会关闭AUTOTRACE

使用 set autot on查看执行计划例子

SQL> set autot on
SQL> select count(*) from scott.emp;

  COUNT(*)
----------
        14


执行计划
----------------------------------------------------------
Plan hash value: 2937609675

-------------------------------------------------------------------
| Id  | Operation        | Name   | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT |        |     1 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |        |     1 |            |          |
|   2 |   INDEX FULL SCAN| PK_EMP |    14 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------


统计信息
----------------------------------------------------------
        305  recursive calls
          0  db block gets
         58  consistent gets
          1  physical reads
          0  redo size
        534  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

使用set autot trace查看执行计划例子

SQL> set autot trace
SQL> select count(*) from scott.emp;


执行计划
----------------------------------------------------------
Plan hash value: 2937609675

-------------------------------------------------------------------
| Id  | Operation        | Name   | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT |        |     1 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |        |     1 |            |          |
|   2 |   INDEX FULL SCAN| PK_EMP |    14 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------


统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          1  consistent gets
          0  physical reads
          0  redo size
        534  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

关闭AUTOTRACE

SQL> set autot off
SQL> select count(*) from scott.emp;

  COUNT(*)
----------
        14

SQL>

其他几种AUTOTRACE用法不太常用,就不占用篇幅写例子了,朋友们如果有兴趣可以自己去试试;AUTOTRACE用法还是比较简单的。

二、EXPLAIN PLAN

Oracle优化器基于当前数据库对象统计信息生成执行计划和相关信息,但不会实际执行EXPLAIN PLAN后面的语句;最终将执行计划默认输出到PLAN_TABLE表中。

EXPLAIN PLAN语法

EXPLAIN PLAN
SET statement_id = 'xxx'
[INTO table]
FOR 需要获取执行计划的SQL语句

示例

EXPLAIN PLAN
SET statement_id = 'zqd20240226'
FOR SELECT COUNT(*) FROM scott.js js WHERE js.object_id >100

然后查看PLAN_TABLE,可以看到输出结果中已经有目标SQL的执行信息了(但此时的执行信息不可以直接使用)

SELECT * FROM plan_table
输出结果

部分参数说明

参数说明
statement_id用于区分PLAN TABLE中不同SQL语句的标识符,自定义写;默认值为NULL。
INTO table用于指定输出表的名称,还可以指定用户和数据库。当省略不写时,默认输出到当前用户的PLAN _TABLE。
FOR 需要获取执行计划的SQL语句要为哪个SQL生成执行计划,FOR后面就写什么SQL;支持DML语句和部分DDL语句(MERGE、CREATE TABLE、CREATE INDEX等)

注意:

PLAN_TABLE是一个会话级的临时表,生成的数据只能用于当前会话,其他会话是看不到的

查看PLAN_TABLE中返回的执行计划和成本的2种方法

第1种:使用DBMS_XPLAN中的DISPLAY函数。

SELECT * FROM table(dbms_xplan.display(NULL,'zqd20240226','ADVANCED'))
输出结果

第2种:使用SELECT语句,利用树形查询语法进行查询。这种方法的优点是可以根据个人需求与喜好自己定制执行计划要看哪些内容;但缺点也很明显,需要自己耗时去编写;且新手对于执行计划并不了解,或者对于树形查询语法不熟悉,无从下笔。下面例子仅仅是一个最简单的示例,可以根据自己需求改写。

SELECT
id,
LPAD(' ',2 * (LEVEL-1)) || operation operation,
options,
object_name,
cost,
object_alias,
position
FROM plan_table
START WITH id = 0
            AND statement_id = 'zqd20240226'
CONNECT BY PRIOR ID = parent_id
            AND statement_id = 'zqd20240226'
ORDER BY id
输出结果

三、DBMS_XPLAN包

DBMS_XPLAN包定义了几个表函数,可以用来显示执行计划。

1、DISPLAY

这个函数在上文的EXPLAIN PLAN章节中已经提及过了,下面再进一步进行说明。DISPLAY表函数展示的是PLAN_TABLE表中的执行计划。官方描述中,只要一个表的结构与PLAN_TABLE一致,那么DISPLAY函数就可以展示这个表中的计划;但知道有这回事儿就行了,不需要重新搞个表去存执行计划,默认的PLAN_TABLE挺好用的。

下面是DISPLAY函数的入参描述,使用例子已经在EXPLAIN PLAN章节描述过,不再赘述。

  function display(table_name   varchar2      default 'PLAN_TABLE',
                   statement_id varchar2      default null,
                   format       varchar2      default 'TYPICAL',
                   filter_preds varchar2      default null)
  return dbms_xplan_type_table
参数说明
table_name默认是PLAN_TABLE,但可以指定读取某张表中的执行计划,但前提是这张表的结构要与PLAN_TABLE一致。
statement_id默认是null。这个就是我们在用EXPLAIN PLAN方式获取执行计划时指定的statement_id。当该参数为null时,会显示计划表中最近的一个执行计划(前提是filter_preds参数也为null)。
format

该参数表示输出格式。

BASIC:只显示执行计划

TYPICAL:显示执行计划、谓词信息(默认值)

SERIAL:显示执行计划、谓词信息

ALL:显示执行计划、查询块名/对象别名、谓词信息、列投影信息

ADVANCED:显示执行计划、查询块名/对象别名、Outline Data、谓词信息、列投影信息

filter_preds 默认是null。过滤谓词。用于过滤从plan_table表中返回的记录,简单理解为where条件。当该值为NULL时,执行计划显示最近插入计划表中的执行计划。

下图是PLAN_TABLE中的执行计划,statement_id一致,但plan_id不一致。如果filter_preds为空,那会展示PLAN_ID为52的执行计划,因为它执行时间最近。

输出结果

如果还想获取PLAN_ID为51的执行计划,filter_preds参数的写法如下代码所示,就是一个简单的where条件。(其实filter_preds不为空的这个应用场景不是很多的,朋友们了解就行了,无聊的时候可以玩一玩)

SELECT * FROM table(dbms_xplan.display(NULL,'zqd20240226','SERIAL','plan_id = 51'))

2、DISPLAY_CURSOR

该函数用于获取内存中shard_pool游标缓存。其参数如下。

  -- display from V$SQL_PLAN (or V$SQL_PLAN_STATISTICS_ALL)
  function display_cursor(sql_id           varchar2 default  null,
                          cursor_child_no  integer  default  0,
                          format           varchar2 default  'TYPICAL')
  return dbms_xplan_type_table

参数说明

参数说明
sql_id指定游标缓存中SQL语句的SQL_ID。可以通过查询V$SQL或V$SQLAREA中的SQL_ID列来获取相应的值。或者,您可以为V$ session中的特定会话选择列PREV_SQL_ID。该参数默认为NULL,在这种情况下,显示会话执行的最后一个游标的计划。
cursor_child_no要显示的游标的子编号。如果不提供,则显示匹配sql_id参数的child_number=0游标的执行计划。只有指定了sql_id后,才可以指定child_number。
format
  • BASIC:展示执行计划的最小信息;只有操作ID、操作名称及其对象
  • TYPICAL:这是format的默认值,在BASIC基础上,在执行计划中增加了Rows、Bytes、Cost、Time列信息;且增加了谓词信息等信息。
  • SERIAL:与TYPICAL类似,除了不显示并行信息,即使计划并行执行
  • ALL:最大用户级别,可以显示所有用户的执行计划;且在TYPICAL级别上增加了Query Block Name / Object Alias、Column Projection Information信息
  • ADAPTIVE:显示最终计划,如果执行尚未完成,则显示当前计划;

为了更好地控制显示输出,您可以将以下关键字添加到上述格式选项中,以自定义其默认行为。每个关键字要么表示计划表列的逻辑组。

格式关键字必须用逗号或空格分隔:

  • LAST:默认情况下,显示所有游标执行的计划统计信息。可以指定关键字LAST以仅查看上次执行的统计信息。
  • IOSTATS :假设在执行SQL语句时收集基本计划统计信息(通过使用gather_plan_statistics提示或通过将参数statistics_level设置为ALL),该格式显示游标执行的所有IO统计信息。
  • ALLSTATS :表示获取访问次数(starts)、实际行数(A-rows)、实际时间(A-time)等实际信息
  • ADVANCED:显示高级别的信息,作用是显示Query Block Name / Object Alias、Outline Data、Column Projection等信息

以上是常用关键字的说明,其他的关键字还有ROWS 、BYTES 、COST 、PARTITION 、PARALLEL 、PREDICATE 、PROJECTION 、ALIAS 、REMOTE 、NOTE 、MEMSTATS 等

以下格式在最新版本中已弃用,但支持向后兼容:

  • RUNSTATS_TOT :RUNSTATS_TOT—与IOSTATS相同,即显示指定游标的所有执行的IO统计信息。
  • RUNSTATS_LAST :与IOSTATS LAST相同,即显示游标最后一次执行的运行时统计信息

您可以使用'-'作为关键字的前缀,以排除指定的信息。例如,'-PROJECTION'排除投影信息。

DBMS_XPLAN包中给出了一些使用例子,现在将这些例子呈现给朋友们。但需要提前说明一个注意事项,在使用DISPLAY_CURSOR时需要将统计级别更改成ALL,该级别可以在会话级或者系统级更改,但如果在系统级更改会降低数据库性能;所以推荐大家更改会话级即可。代码如下:

SQL> show parameter statistics_level;

NAME                                 TYPE                   VALUE
------------------------------------ ---------------------- ------------------------------
statistics_level                     string                 TYPICAL
SQL> alter session set statistics_level =ALL;

会话已更改。

SQL> show parameter statistics_level;

NAME                                 TYPE                   VALUE
------------------------------------ ---------------------- ------------------------------
statistics_level                     string                 ALL

除了更改统计级别,还可以在目标SQL语句中添加HINT:SELECT /*+gather_plan_statistics*/ ....

但这种方法略麻烦,还是推荐更改会话级的统计级别。

下面开始说例子:

例1、展示当前会话的当前或者最近一次的执行语句的执行计划

这个获取的是当前会话中的当前或者最近一次的SQL语句执行计划,不能获取其他会话的SQL语句执行计划。代码如下所示:

SELECT * FROM TABLE(dbms_xplan.display_cursor);
查询最近一次SQL语句执行的执行计划

   ***因为涉及到执行计划输出结果展示,下面的例子统一在SQLPLUS中进行,方便文章编写***

例2、展示指定会话当前的或者最近一次的执行计划

识别目标SQL语句的sql_id 和 child_number,使用它们作为DISPLAY_CUSRSOR的参数。代码如下所示。如果是新手朋友可能对session不太了解,但没关系,这只是一个例子;且您可以根据v$session视图中的program和machine大概定位到您想要查看的会话。

SQL> SELECT prev_sql_id, prev_child_number FROM v$session WHERE sid=130;

PREV_SQL_ID                PREV_CHILD_NUMBER
-------------------------- -----------------
fsv2yh2b2ja1t                              0

SQL> SELECT * FROM TABLE(dbms_xplan.display_cursor('fsv2yh2b2ja1t', 0));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  fsv2yh2b2ja1t, child number 0
-------------------------------------
SELECT * FROM scott.js js WHERE js.object_id < 200

Plan hash value: 215799804

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |     5 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| JS           |   152 | 14592 |     5   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | PK_OBJECT_ID |   152 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

   2 - access("JS"."OBJECT_ID"<200)


已选择19行。

也可以将上述两个SQL合并在一起,代码如下。

SQL> SELECT t.*
  2  FROM v$session s,
  3  TABLE(dbms_xplan.display_cursor(s.prev_sql_id,s.prev_child_number)) t
  4  WHERE s.sid=130;

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  fsv2yh2b2ja1t, child number 0
-------------------------------------
SELECT * FROM scott.js js WHERE js.object_id < 200

Plan hash value: 215799804

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |     5 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| JS           |   152 | 14592 |     5   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | PK_OBJECT_ID |   152 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

   2 - access("JS"."OBJECT_ID"<200)


已选择19行。

 为了方便复制,下面把SQL单独拎出来:

SELECT t.*
FROM v$session s,
TABLE(dbms_xplan.display_cursor(s.prev_sql_id,s.prev_child_number)) t
WHERE s.sid=130;

例3、显示包含区分大小写字符串’xxxx‘的所有游标,不包括SYS解析的SQL。

这个例子其实挺好用的,因为要优化一个SQL获取它的执行计划时,一般懒得去查它的sql_id,所以直接根据SQL中包含的一些唯一字符内容,获取其执行计划很方便。但是朋友们也不要认死理,获取执行计划的方式这么多就是为了应对不同的场景,一个方法的好不好用要和使用场景关联起来聊。该例子代码如下:

SQL> SELECT t.*
  2  FROM v$sql s,
  3  TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number)) t
  4  WHERE s.sql_text LIKE '%700%' AND s.parsing_user_id <> 0;

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  683agnzwmsrg8, child number 0
-------------------------------------
SELECT * FROM scott.js js WHERE js.object_id < 700

Plan hash value: 215799804

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |    12 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| JS           |   535 | 51360 |    12   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | PK_OBJECT_ID |   535 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

   2 - access("JS"."OBJECT_ID"<700)


已选择19行。

 为了方便复制,下面把SQL单独拎出来:

SELECT t.*
FROM v$sql s,
TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number)) t
WHERE s.sql_text LIKE '%700%' AND s.parsing_user_id <> 0;

例4、显示所有包含不区分大小写字符串'xxxx'的游标的所有信息,包括SYS解析的SQL

该例子的format参数是ALL。

代码如下:

SQL> SELECT t.*
  2  FROM v$sql s,
  3  TABLE(dbms_xplan.display_cursor(s.sql_id,s.child_number, 'ALL')) t
  4  WHERE UPPER(s.sql_text) like '%JS.OBJECT_NAME%';

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  bz071f64y60x4, child number 0
-------------------------------------
SELECT JS.object_name FROM scott.js js WHERE js.object_id < 700

Plan hash value: 215799804

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |    12 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| JS           |   535 | 16050 |    12   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | PK_OBJECT_ID |   535 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / JS@SEL$1
   2 - SEL$1 / JS@SEL$1

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

   2 - access("JS"."OBJECT_ID"<700)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "JS"."OBJECT_NAME"[VARCHAR2,128]
   2 - "JS".ROWID[ROWID,10]


已选择31行。

SQL>

 为了方便复制,下面把SQL单独拎出来:

SELECT t.*
FROM v$sql s,
TABLE(dbms_xplan.display_cursor(s.sql_id,s.child_number, 'ALL')) t
WHERE UPPER(s.sql_text) like '%JS.OBJECT_NAME%';

例5、显示所有包含字符串’xxxx‘ ,且不区分大小写的游标最近一次的执行计划,包括SYS用户解析的SQL。

该例子使用的format参数是ALLSTATS LAST

代码如下:

SQL> SELECT first_name,email FROM HR.employees WHERE salary >1000 AND job_id = 'AD_VP';

FIRST_NAME           EMAIL
-------------------- -------------------------
Neena                NKOCHHAR
Lex                  LDEHAAN

SQL> SELECT t.* FROM v$sql s,TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number,'ALLSTATS LAST')) t WHERE UPPER(s.sql_text) LIKE '%AD_VP%';

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------

SQL_ID  d1gfgmfwbqs9k, child number 1
-------------------------------------
SELECT first_name,email FROM HR.employees WHERE salary >1000 AND job_id
= 'AD_VP'

Plan hash value: 2096651594

------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |      1 |        |      2 |00:00:00.01 |       4 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES  |      1 |      2 |      2 |00:00:00.01 |       4 |
|*  2 |   INDEX RANGE SCAN                  | EMP_JOB_IX |      1 |      2 |      2 |00:00:00.01 |       2 |
------------------------------------------------------------------------------------------------------------

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

   1 - filter("SALARY">1000)
   2 - access("JOB_ID"='AD_VP')


48 rows selected.

 为了方便复制,下面把SQL单独拎出来:

SELECT t.* 
FROM v$sql s,
TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number,'ALLSTATS LAST')) t 
WHERE UPPER(s.sql_text) LIKE '%AD_VP%';

例6、显示由'XXX'用户解析的包含’xxx‘字符的,且区分大小写的所有游标的执行计划

该例子使用的format参数是RUNSTATS_TOT 

SQL> SELECT t.* FROM v$sql s, dba_users u,TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number,'RUNSTATS_TOT')) t WHERE s.sql_text like '%MK_MAN%' AND u.user_id=s.parsing_user_id AND u.username='HR';

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  0j21c70c63ntg, child number 0
-------------------------------------
SELECT first_name,email FROM HR.employees WHERE salary >1000 AND job_id
= 'MK_MAN'

Plan hash value: 2096651594

------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |      1 |        |      1 |00:00:00.01 |       2 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES  |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  2 |   INDEX RANGE SCAN                  | EMP_JOB_IX |      1 |      1 |      1 |00:00:00.01 |       1 |
------------------------------------------------------------------------------------------------------------

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

   1 - filter("SALARY">1000)
   2 - access("JOB_ID"='MK_MAN')


21 rows selected.

 为了方便复制,下面把SQL单独拎出来: 

SELECT t.*
FROM v$sql s, dba_users u,
TABLE(dbms_xplan.display_cursor(s.sql_id, s.child_number,'RUNSTATS_TOT')) t
WHERE s.sql_text like '%MK_MAN%'
AND u.user_id=s.parsing_user_id
AND u.username='HR';

3、DISPLAY_AWR  

这个表函数显示存储在AWR中的执行计划的内容。

官方的最新文档中,表示已将DISPLAY_AWR已经被DISPLAY_WORKLOAD_REPOSITORY表函数 取代了。但是这个事儿吧,朋友们也不用多想,反正19C及之前的版本的DISPLAY_AWR都还在,20C及以上版本咱没用过。

DBMS_XPLAN.DISPLAY_AWR( 
   sql_id            IN      VARCHAR2,
   plan_hash_value   IN      NUMBER DEFAULT NULL,
   db_id             IN      NUMBER DEFAULT NULL,
   format            IN      VARCHAR2 DEFAULT TYPICAL);

 参数说明:

参数说明
sql_idSQL语句的SQL_ID。可以通过查询DBA_HIST_SQLTEXT中的SQL_ID列来检目标SQL语句的适当值
plan_hash_value指定SQL语句的PLAN_HASH_VALUE。可选参数。如果省略,表函数返回给定SQL_ID的所有存储的执行计划。
db_id指定database_id,用于显示由SQL_ID标识的SQL语句的计划。如果没有提供,则使用本地数据库的database_id,如V$ database所示。
format

控制执行计划的详细级别。它接受四个值:

  • BASIC:显示计划中的最小信息——操作ID、操作名称及其选项。
  • TYPICAL:这是format的默认值,在BASIC基础上,在执行计划中增加了Rows、Bytes、Cost、Time列信息;且增加了谓词信息等信息。
  • SERIAL:与TYPICAL类似,除了不显示并行信息,即使计划并行执行。
  • ALL:最大用户级别。包括以TYPICAL级别显示的信息以及附加信息(如果操作是分布式的,则包括投影、ALIAS和关于REMOTE SQL的信息)。

为了更好地控制显示输出,可以将以下关键字添加到上述四个标准格式选项中,以自定义其默认行为。每个关键字要么表示计划表列的逻辑组(如PARTITION),要么表示对基本计划表输出的逻辑添加(如PREDICATE)。格式关键字必须用逗号或空格分隔:

  • ROWS 
  • BYTES 
  • COST 
  • PARTITION 
  • PARALLEL 
  • PREDICATE 
  • PROJECTION 
  • ALIAS 
  • REMOTE 
  • NOTE 

格式关键字的前缀可以加上'-',以排除指定的信息。例如,'-PROJECTION'排除投影信息。

用法注意事项:

要使用DISPLAY_AWR功能,用户必须对DBA_HIST_SQL_PLAN、AWR_ROOT_SQL_PLAN、AWR_PDB_SQL_PLAN、DBA_HIST_SQLTEXT、AWR_ROOT_SQLTEXT、AWR_PDB_SQLTEXT和V$DATABASE具有SELECT或READ权限,否则将显示相应的错误消息。默认情况下,这些视图的选择权限被授予select_catalog角色。

下面的例子会展示使用format参数的不同例子

  • 当format为 ’BASIC ROWS‘ 时,会显示基本信息和优化器估计的行数
  • 当format为 ’ALL -PROJECTION -NOTE‘ 时,会显示除了projection 和note以外的所有信息
  • 当format为 ’TYPICAL PROJECTION‘ 时,会显示带有“Column Projection Information”的典型格式,由于typical是默认值,因此使用简单的'PROJECTION'是等效的。
  • 当format为 ’BYTES -COST -PREDICATE‘ 时,以典型格式显示,但不包括优化器成本和字节大小估计以及谓词部分。

 下面是DISPLAY_AWR 的使用例子:

(1)执行目标SQL

SQL> SELECT
  2  e.empno,e.job,d.dname
  3  FROM scott.emp e,scott.dept d
  4  WHERE e.deptno = d.deptno
  5  AND e.job = 'SALESMAN';

     EMPNO JOB                DNAME
---------- ------------------ ----------------------------
      7499 SALESMAN           SALES
      7844 SALESMAN           SALES
      7654 SALESMAN           SALES
      7521 SALESMAN           SALES

(2)手动创建数据库快照

可以在DBA_HIST_SNAPSHOT表中查询到刚刚创建的数据库快照,下面的代码示例中SNAP_ID为12298的数据,就说明刚刚创建数据库快照成功了;快照周期开始时间是2024-02-29 15:00:06,快照周期结束时间是2024-02-29 15:34:07。手动创建快照的目的是确保我们刚刚执行的SQL包含于快照中。

SQL> EXECUTE DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT('ALL');

PL/SQL 过程已成功完成。

SQL> SELECT * FROM
  2  (SELECT
  3  DHS.snap_id,
  4  DHS.dbid,
  5  TO_CHAR(DHS.startup_time,'YYYY-MM-DD HH24:MI:SS')  startup_time,
  6  TO_CHAR(DHS.begin_interval_time,'YYYY-MM-DD HH24:MI:SS')  begin_interval_time,
  7  TO_CHAR(DHS.end_interval_time,'YYYY-MM-DD HH24:MI:SS')  end_interval_time
  8  FROM DBA_HIST_SNAPSHOT DHS
  9  ORDER BY DHS.begin_interval_time DESC)
 10  WHERE ROWNUM < 5
 11  ;

   SNAP_ID       DBID STARTUP_TIME                           BEGIN_INTERVAL_TIME                    END_INTERVAL_TIME
---------- ---------- -------------------------------------- -------------------------------------- --------------------------------------
     12298 1608366025 2024-02-29 08:13:15                    2024-02-29 15:00:06                    2024-02-29 15:34:07
     12297 1608366025 2024-02-29 08:13:15                    2024-02-29 14:16:16                    2024-02-29 15:00:06
     12296 1608366025 2024-02-29 08:13:15                    2024-02-29 13:58:08                    2024-02-29 14:16:16
     12295 1608366025 2024-02-29 08:13:15                    2024-02-29 13:56:20                    2024-02-29 13:58:08

 (3)使用DISPLAY_AWR表函数查看执行计划

SQL> SELECT tf.*  FROM   DBA_HIST_SQLTEXT ht, table(DBMS_XPLAN.DISPLAY_AWR(ht.sql_id,null, null,  'ALL' )) tf WHERE  ht.sql_text like '%SALESMAN%';

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 8k5uj73ms559k
--------------------
SELECT e.empno,e.job,d.dname FROM scott.emp e,scott.dept d WHERE
e.deptno = d.deptno AND e.job = 'SALESMAN'

Plan hash value: 844388907

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |       |       |     6 (100)|          |
|   1 |  MERGE JOIN                  |         |     3 |    84 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     4 |    52 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | PK_DEPT |     4 |       |     1   (0)| 00:00:01 |
|   4 |   SORT JOIN                  |         |     3 |    45 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | EMP     |     3 |    45 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / D@SEL$1
   3 - SEL$1 / D@SEL$1
   5 - SEL$1 / E@SEL$1


已选择26行。

 为了方便复制,下面把SQL单独拎出来: 

SELECT tf.*  
FROM   DBA_HIST_SQLTEXT ht, 
table(DBMS_XPLAN.DISPLAY_AWR(ht.sql_id,null, null,  'ALL' )) tf 
WHERE  ht.sql_text like '%SALESMAN%';

四、Sql Developer 工具

Sql Developer工具是Oracle官方提供的一个SQL客户端连接工具,免费好用。在SQL优化这方面,个人觉得 Sql Developer比PL/Sql Developer工具其实要好用很多。下面简单描述下如何使用Sql Developer获取执行计划。

(1)选中目标SQL语句

(2)点击左上角红框圈注的”SQL优化指导“,或者CTRL+F12;就会输出SQL优化指导信息。

(3)在详细信息模块就会有执行计划

除了执行计划以外, Sql Developer在概览模块还会提供优化建议、原理等信息;非常方便优化工作,对新手朋友也十分友好。当然因为我本地搭建的数据库没啥复杂的SQL,所以这个工具也没有啥优化建议了。

五、总结与分析 

每种方法都有它适合的使用场景,不能抛开场景去评价一个方法的好坏。下面就总结上文提到的4种方法。

1、虚假和真实

眼见不一定为真,执行计划也有虚假的计划和真实的计划。

虚假的执行计划:

上文中提到的AUTOTRACE、EXPLAIN PLAN、Sql Developer工具的执行计划都是估算出来的,真实的执行计划不应该是被估算的,应该是真正执行过的。而AUTOTRACE虽然也可以真正执行SQL,但它的执行计划其实还是来自于PLAN_TABLE,而非共享池的v$sql_plan。这些执行计划是依赖于统计信息估算出来的,如果统计信息过旧,那怎么保证执行计划的真实性呢?

真实的执行计划:

上文提到的DBMS_XPLAN包中的DISPLAY_CURSOR、DISPLAY_AWR这两个表函数都可以获得真实的执行计划。这两个表函数一个是获取的共享池中的执行计划,一个获取的是存储在AWR中的执行计划。用这些表函数获取执行计划的前提是目标SQL都必须执行过。

2、使用场景建议

  • 上线SQL审核

系统上线前的SQL审核,有很多SQL不方便直接执行;所以推荐使用EXPLAIN PLAN、Sql Developer、AUTOTRACE。这些都可以不执行SQL,直接根据统计信息获取执行计划。

  • 目标SQL执行耗时巨长

有的SQL写的非常复杂,且查询数据又大,访问路径、连接方式也很烂。所以执行耗时很恐怖,不可能一直等它执行完,所以也建议使用EXPLAIN PLAN、Sql Developer、AUTOTRACE。

  • 真实执行计划

当没有其他特殊情况时,肯定还是获取真实的执行计划最好,所以此时没有推荐,就是需要DBMS_XPLAN.DISPLAY_CURSOR、DBMS_XPLAN.DISPLAY_AWR方法获取执行计划。

  • 获取目标SQL的多个执行计划

需要获取目标SQL的多个执行计划时,调整好format参数,DBMS_XPLAN.DISPLAY_CURSOR、DBMS_XPLAN.DISPLAY_AWR这两个表函数就可以满足要求,其实PLAN_TABLE中也能获取,但比较麻烦还有些谓词过滤条件,不是很推荐。

当然,场景还有很多很多,这里只是举了几个我遇到过的例子。朋友们如果真遇到了奇怪情况,那就仁者见仁智者见智了,做好记录总结,都是财富啊。

3、个人习惯

获取执行计划方法有很多,但说到底我们也只是为了获取到执行计划;真正的优化还得是看后面的分析与调整。所以朋友们一定要能提炼出来自己用着最舒服的执行计划,当遇到需要优化的SQL时,没那么多时间让我们去挑选该用哪一种方法,都是本能的用自己最熟悉的,所以到底该用哪种方法,要养成习惯哦。

同时也和各位朋友聊下我习惯使用的方法:

当遇到查询时间不是很长的的目标SQL,我会本能的使用DBMS_XPLAN.DISPLAY_CURSOR。这个方法既准确也不用想着手动收集快照,并且编写一个PLSQL方便获取计划,代码如下,觉得有需要的朋友可以看下。但是有个小问题

DECLARE 
x  VARCHAR2(36);
BEGIN 
  EXECUTE IMMEDIATE 'alter session set statistics_level=ALL';
x := 'display_cursor';
  FOR text IN (
               /*业务SQL(sql后面不需要加";")*/

        ) LOOP
    NULL;
  END LOOP;
  for i in (SELECT p.plan_table_output
              FROM TABLE(dbms_xplan.display_cursor(NULL, NULL,'ADVANCED ALLSTATS LAST')) p) LOOP
    dbms_output.put_line(i.plan_table_output);
  END LOOP;
  ROLLBACK;
END;

在 Sql Developer的dbms输出框里可以看到上面代码的执行计划。再提醒一点,dbms输出是默认不显示的;需要在Sql Developer左上角点击【查看】,选择dbms输出,并对dbms输出进行连接才可以。(我有个朋友,曾经搞了半天)

当遇到又臭又长(耗时长)的SQL时

我还是会倾向使用 Sql Developer工具,但如果是远程其他项目的数据库服务器时,没有工具使用,那我本能反应还是AUTOTRACE。

4、最后、最后

除了上文提到的4种方法,获取方法还有不少:10046事件、PL/SQL工具、Oracle的EM和GridControl图形化查看工具等。我也是大家学习路上的同行者,有些方法我也没用过,有些奇怪情况我也没遇到过,所以也很期待解锁更多的知识。但还是认为有余力或者缘分到了的话可以多多研究几种新玩意儿,平时还是要搞清楚我们的主线任务,有些东西够用就行了。

最后的最后,谢谢您能阅读到文章结尾。如果能给您带来些许帮助,这是我的荣幸;如果因为我的技术水平有限,出现错误,请您指正;他日见张禄,绨袍怀旧恩!

  • 22
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
oracle数据库的性能优化直接关系到系统的运行效率,而影响数据库性能的一个重要因素就是sql性能问题。本书是作者十年磨一剑的成果之一,深入分析与解剖oracle sql优化与调优技术,主要内容包括: 第一篇“执行计划”详细介绍各种执行计划的含义与操作,为后面的深入分析打下基础。重点讲解执行计划sql语句执行的生命周期中所处的位置和作用,sql引擎如何生成执行计划以及如何获取sql语句的执行计划,如何从各种数据源显示和查看已经生成的执行计划。 第二篇“sql优化技术”深入分析oraclesql优化技术,包括逻辑优化技术和物理优化技术。用大量示例详尽分析oracle 中现有的各种查询转换技术,先分析oracle如何收集、统计系统和对象的数据,然后推导各种代价估算公式,给出各种情形下的代价计算演示。 第三篇“sql调优技术”深入剖析oracle提供的各项调优技术。先对语句实际运行的性能统计数据进行了深度分析,介绍各项统计数据是由什么操作导致的以及如何统计。然后介绍如何对sql语句进行优化以获得稳定、高效的性能。最后,依据对sql优化及调优技术的分析,介绍如何快速优化sql的思路。 《oracle 高性能sql引擎剖析:sql优化与调优机制详解》内容丰富且深入,破解了oracle技术的很多秘密,适合oracle数据库管理员、应用开发人员参考。
Oracle数据库的性能优化直接关系到系统的运行效率,而影响数据库性能的一个重要因素就是SQL性能问题。本书是作者十年磨一剑的成果之一,深入分析与解剖Oracle SQL优化与调优技术,主要内容包括: 第一篇“执行计划”详细介绍各种执行计划的含义与操作,为后面的深入分析打下基础。重点讲解执行计划SQL语句执行的生命周期中所处的位置和作用,SQL引擎如何生成执行计划以及如何获取SQL语句的执行计划,如何从各种数据源显示和查看已经生成的执行计划。 第二篇“SQL优化技术”深入分析OracleSQL优化技术,包括逻辑优化技术和物理优化技术。用大量示例详尽分析Oracle 中现有的各种查询转换技术,先分析Oracle如何收集、统计系统和对象的数据,然后推导各种代价估算公式,给出各种情形下的代价计算演示。 第三篇“SQL调优技术”深入剖析Oracle提供的各项调优技术。先对语句实际运行的性能统计数据进行了深度分析,介绍各项统计数据是由什么操作导致的以及如何统计。然后介绍如何对SQL语句进行优化以获得稳定、高效的性能。最后,依据对SQL优化及调优技术的分析,介绍如何快速优化SQL的思路。 《Oracle 高性能SQL引擎剖析:SQL优化与调优机制详解》内容丰富且深入,破解了Oracle技术的很多秘密,适合Oracle数据库管理员、应用开发人员参考。
OracleSQL 执行计划Oracle 数据库优化和性能调优的重要工具之一。SQL 执行计划是一个详细的指令列表,显示了 Oracle 数据库如何执行查询。以下是 SQL 执行计划的解读方法: 1. 查看执行计划的方式 可以使用 EXPLAIN PLAN 或者 AUTOTRACE 语句来获取 SQL 执行计划。例如: ``` EXPLAIN PLAN FOR SELECT * FROM employees; ``` 或者 ``` SET AUTOTRACE ON; SELECT * FROM employees; ``` 2. 查看执行计划的基本信息 执行计划的第一行显示了查询的 SELECT 语句和查询类型(如 SELECT STATEMENT)。接下来的行显示了每个步骤的详细信息,包括操作类型、对象名称、操作选项、数据行数等。 3. 查看执行计划的操作类型 操作类型(Operation)是执行计划中最重要的指标之一,它表示数据库执行操作的方式。常见的操作类型包括: - SELECT:从表中检索数据。 - TABLE ACCESS:访问表中的数据。 - INDEX:使用索引查找数据。 - SORT:对结果进行排序。 - HASH JOIN:使用哈希连接将两个表合并。 - NESTED LOOPS:使用嵌套循环连接两个表。 4. 查看执行计划的成本估算 每个操作都有一个成本估算(Cost),它表示执行该操作所需要的资源和时间。成本估算越高,操作的效率越低。可以使用成本估算来优化查询的性能。 5. 查看执行计划的数据行数估算 数据行数估算(Cardinality)是一个操作返回的行数的估算值。数据行数估算可以用来分析查询的效率和精度。 6. 查看执行计划的优化器模式 Oracle 的优化器有多种模式,可以使用不同的模式来执行查询。执行计划中的 Optimizer Mode 显示了优化器使用的模式,通常应该选择最优模式来执行查询。 7. 查看执行计划的索引信息 如果查询使用了索引,执行计划会显示索引名称、索引类型、索引字段、索引选择性等信息。可以使用这些信息来评估索引的效率和优化查询的性能。 8. 查看执行计划的备注信息 执行计划中的备注信息可以用来分析查询的性能和优化效果。例如,如果出现了“FULL TABLE SCAN”等高成本操作,可能需要优化查询的语句或者增加索引等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜豆豆耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值