您的位置:首页 > 数据库 > Oracle

Oracle SQL计划

2014-09-04 23:59 190 查看
深入读解Oracle SQL计划

【概要】

这里所谓SQL计划就是大家平时所说的执行计划。从10g开始,Oracle倾向于把执行计划称为SQL计划,到了11g,这个倾向就更加明显了,本文也顺应这个潮流,使用SQL计划这个概念,也简称计划。

大家在SQL调优的过程中,不可避免的要跟SQL计划打交道,或多或少会遇到一些问题:

ü 为什么explain plan得到的计划跟实际的不同呢?

ü Autotrace实际上执行了SQL,怎么还是得出错误的计划?

ü SQL计划有哪些信息值得我们关注?

ü SQL计划的那个步骤最耗费资源,哪个步骤花的时间最长,为什么?

ü ……

本文从梳理概念出发,介绍各种查看和分析SQL计划的方法,逐一解答这些疑问。

1 关于SQL计划(SQL PLAN)

Oracle把执行SQL语句分解成很多步骤:或者是访问索引或表数据,或者是对这些数据进行各种运算操作。Oracle把这些步骤称为行源操作(row source operation)。这些行源操作按顺序组合在一起就叫做SQL计划。SQL计划描述了Oracle执行SQL语句的过程,它对分析SQL性能至关重要。

SQL计划可分为执行计划和解析计划两类。

1.1 执行计划(execution plan)

SQL语句第一次,优化器将对它进行分析,并产生一个最佳的SQL计划。这个计划被保存在Shared Pool中,供以后执行相同的SQL时直接使用,我们把它称为执行计划。通过V$SQL_PLAN视图可以查看到保存在Shared Pool中的SQL计划。也就是说,V$SQL_PLAN存放着SQL语句最后一次使用过的执行计划(会话在invalid状态),或者正在使用的执行计划(会话在valid状态)。

1.2 解析计划(explain plan)

有些SQL可能会执行很长时间,如数天。这种情况下,如果要等待SQL执行完毕才从V$SQL_PLAN中分析它的计划,效率就太低了。Oracle提供了explain plan命令,可以不执行SQL语句,直接让优化器分析SQL,并把当前环境下最优的计划写入到PLAN_TABLE表,这种SQL计划称为解析计划。如:

SQL> explain plan set statement_id='T_PLAN' for select * from t_plan;

Explained

这样,“select * from t_plan”的解析计划就被保存到了PLAN_TABLE。

解析计划代表了Oracle在进行SQL解析的这个时间点将会怎样去执行SQL语句,不能说明SQL语句曾经使用过或者将会使用这个计划。

1.3 SQL计划为什么会变化?

SQL计划是由优化器产生的,优化器的输入条件变了,SQL计划随之而变。输入条件包括SQL语句本身、统计信息还有其它一些环境变量。特别是CBO根据成本选择执行计划,更容易因环境变化导致SQL计划不稳定。下面列出一些可能会SQL计划变化的因素:

ü 用户模式不同:在不同的数据库、或不同的用户模式下观察到的SQL计划可能会不同;即使是在同一用户下,有可能SQL语句访问的对象发生了变化,如增加或删除了索引、同义词或视图指向了另一个表等,这些情况也会导致SQL计划变化。

ü 成本发生了变化:数据量或统计信息、绑定变量类型、优化器模式或其它响优化器的初始化参数等因素都可能影响优化器的成本计算,如果这些因素发生了变化,可能导致SQL计划随之而变。

ü 解析计划本身的问题:在绑定变量的情况下,解析计划会变得不准确。

ü 优化器的BUG也可能导致解析计划跟实际使用的执行计划不同。

2 DBMS_XPLAN——格式化显示SQL计划

前面提到,实际的执行计划和解析计划分别可以直接通过V$SQL_PLAN和PLAN_TABLE查看,但这种方式不便于阅读和分析。Oracle提供了一些格式化显示SQL计划的工具,方便我们分析,其中最重要的就是DBMS_XPLAN,它也是本文分析SQL计划的主要工具,这里先介绍它的用途和用法。

2.1 DBMS_XPLAN

DBMS_XPLAN是Oracle 9iR2引入的一个标准包,它提供了一系列函数按预定义格式输出SQL计划。它可以从PLAN_TABLE读取解析计划,也可以从V$SQL_PLAN读取执行计划,甚至还可以从AWR等其它地方读取SQL计划。

DBMS_XPLAN在Oracle 9i时只有一个函数,10g时有6个函数,到11g时达到14个函数。其中有些函数是Oracle内部使用的,下面列出用来查看SQL计划的函数:

函数名

支持版本

功能说明

信息来源

DISPLAY

9i/10g/11g

格式化显示解析计划

PLAN_TABLE

DISPLAY_CURSOR

10g/11g

格式化显示游标(Shared Pool中的SQL)的执行计划

V$SQL_PLAN, V$SESSION,

V$SQL_PLAN_STATISTICS_ALL

DISPLAY_AWR

10g/11g

格式化显示保存在AWR中的SQL执行计划

DBA_HIST_SQL_PLAN,

DBA_HIST_SQLTEXT, V$DATABASE

DISPLAY_SQLSET

10g/11g

格式化显示保存在SQL tuning set中的SQL执行计划

ALL_SQLSET_STATEMENTS,

ALL_SQLSET_PLANS

DISPLAY_SQL_PLAN

_BASELINE

11g

格式化显示保存在SQL 计划基线(PLAN BASELINESQL)的执行计划

DBA_SQL_PLAN_BASELINES

这些函数中,以DISPLAY和DISPLAY_CURSOR最为常用,下面给出详细的使用说明。

2.2 DBMS_XPLAN.DISPLAY——查看解析计划

DISPLAY可以查看由explain plan命令产生的解析计划。

DISPLAY函数有3个参数:

参数

说明

table_name

指定保存计划的表。其默认值是PLAN_TABLE,也就是explain plan命令默认保存解析计划的表

statement_id

PLAN_TABLE的statement_id字段

format

显示格式,有4个取值:

BASIC – 显示执行计划的最基本信息

TYPICAL – 这是默认值,比BASIC模式多了谓词信息、分区信息、并行处理等。

SERIAL – 有点像TYPICAL,但即使是并行执行也不显示并行处理信息。

ALL – 显示所有信息,在TYPICAL的基础上证据了projections, alias等信息。

示例:

SQL> explain plan for select count(*) from user_objects;

SQL> select * from table(dbms_xplan.display);

2.3 DBMS_XPLAN.DISPLAY_CURSOR——查看游标的执行计划

这里所谓游标是指Shared Pool中的共享SQL。DISPLAY_CURSOR能够显示SQL语句最近使用的执行计划,甚至还能显示这个计划的统计信息(如I/O,执行时间等)。

DISPLAY_CURSOR函数有3个参数:

参数

说明

sql_id

SQL语句的SQL_ID,可从V$SQL或V$SQLAREA获得. 默认值null表示当前会话中的最后一个游标。

child_number

一个SQL_ID可能对应了多个子游标,可以通过child_number指定显示其中一个。

format

显示格式,有4种基本格式:

BASIC – 显示执行计划的最基本信息

TYPICAL – 这是默认值,比BASIC模式多了谓词信息、分区信息、并行处理等。

SERIAL – 有点像TYPICAL,但即使是并行执行也不显示并行处理信息。

ALL – 显示所有信息,在TYPICAL的基础上证据了projections, alias等信息。

DISPLAY_CURSOR还通过以下附加格式化关键字来更精确地控制输出:

COST - 成本

PARTITION - 分区修剪信息

PARALLEL - 并行处理信息

PREDICATE - 谓词

PROJECTION -映射

ALIAS - 命名查询块和对象别名

REMOTE - 分布式查询信息

IOSTATS - 如果执行SQL语句时收集了统计信息(通过gather_plan_statistics提示,或把参数statistics_level设为ALL), 指定这个参数将输出游标总的IO量。

MEMSTATS - 如果使用了PGA自动管理模式,指定MEMSTATS将输出内存管理统计信息。

ALLSTATS - 同时输出IOSTATS和MEMSTATS

LAST - 默认情况下,统计信息是指游标的累计统计,指定LAST关键字可以只输出最后一次的执行统计信息。

如果要指定多个关键字,则关键字之间用逗号或空格分开。在关键字前加'-'表示不显示指定的信息,如'-PROJECTION'表示不显示映射信息。

示例:

1. 显示当前会话中最后一个执行的SQL的执行计划:

select * from table(dbms_xplan.display_cursor);

2. 显示SQL ID为'atfwcg8anrykp'的所有子游标的执行计划:

select * from table(dbms_xplan.display_cursor('atfwcg8anrykp'));

3. 显示游标的执行计划及其最近一次的执行统计信息:

select * from table(dbms_xplan.display_cursor('atfwcg8anrykp', null, 'allstats last');

3 查看SQL计划的常用方法分析

相对于DBMS_XPLAN,大家平时更多的是使用Autotrace、PL/SQL Developer的Explain Plan Window等工具来查看SQL计划。了解这些工具的特点和局限性,对分析SQL计划至关重要。

3.1 查看解析计划

Autotrace

Autotrace输出的执行计划实际上就是通过explain plan命令产生的解析计划。我们通过对使用Autotrace的过程进行一次SQL TRACE就能证实只一点:

set autotrace on

alter session set sql_trace = true;

select * from dual;

alter session set sql_trace = false;

从trace文件(9i)中我们大概可以看到执行了这样一些语句:

alter session set sql_trace=true

...

select * from dual

DELETE FROM PLAN_TABLE WHERE STATEMENT_ID=:1

EXPLAIN PLAN SET STATEMENT_ID='PLUS261' FOR select * from dual

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY(:"SYS_B_0", :1))

...

alter session set sql_trace=false

可见,Autotrace输出的是解析计划。另外要说明的一点是,Autotrace只能在SQL*PLUS下使用。

PL/SQL Developer的Explain Plan Window

有很多人喜欢用PL/SQL Developer的Explain Plan Window查看SQL语句的执行计划,它跟Autotrace一样,也是查看解析计划。

3.2 不可信解析计划

为了说明解析计划的问题,我们在Oracle 10g上做一个测试:

create table t_plan pctfree 99 pctused 1 as

select 'Y' flag, rpad('x', 30) padding from all_objects where rownum < 20;

insert into t_plan values('N', 'N');

create index idx_plan_flag on t_plan(flag);

analyze table t_plan compute statistics for table for all columns for all indexes;

这里创建了一个表,19条记录的flag为Y,一条为N。由于把pctfree设为99,其效果是使得每条记录占用一个数据块。

对于这样一个表,如果要查询flag为N的记录,我们会期待走索引访问。然而,如果我们使用绑定变量,会看到一个错误的解析计划:

-- 定义变量

var a varchar2(10);

exec :a := 'N';

-- 以绑定变量的方式解析SQL

explain plan for select * from t_plan where flag = :a;

-- 查看解析计划

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 10 | 970 | 7 (0)| 00:00:01 |

|* 1 | TABLE ACCESS FULL| T_PLAN | 10 | 970 | 7 (0)| 00:00:01 |

----------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("FLAG"=:A)

当我们实际去执行SQL时,优化器确实选择了我们期待的计划:

-- 以绑定变量的方式执行查询

select * from t_plan where flag = :a;

-- 显示游标的执行计划

select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT

-------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-------------------------------------------------------------------------------------------

| 0| SELECT STATEMENT | | | | 2 (100)| |

| 1| TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 30 | 2 (0)| 00:00:01 |

|* 2| INDEX RANGE SCAN | IDX_PLAN_FLAG | 1 | | 1 (0)| 00:00:01 |

-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("FLAG"=:A)

前面说过,Autotrace看到的是解析计划,它跟explain plan一样在绑定变量的情况下也会得到一个错误的计划:

set autotrace on;

alter session set sql_trace = true;

select * from t_plan where flag = :a;

Execution Plan

----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 10 | 300 | 7 (0)| 00:00:01 |

|* 1 | TABLE ACCESS FULL| T_PLAN | 10 | 300 | 7 (0)| 00:00:01 |

----------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("FLAG"=:A)

alter session set sql_trace = false;

在Autotrace的过程中,同时做了SQL TRACE,产生trace文件utf810g_ora_1636.trc。用tkprof格式化后将能看到期待的SQL计划:

select * from t_plan where flag = :a

……

Rows Row Source Operation

------- ---------------------------------------------------

1 TABLE ACCESS BY INDEX ROWID T_PLAN

1 INDEX RANGE SCAN IDX_PLAN_FLAG

这个示例是在Oracle 10gR2上进行的,事实上,从9i到11g都会得出这样的结果。它说明,explain plan操作不会发生变量窥视(bind peeking)。更进一步来说,它不会把绑定变量的值反映到执行计划里面,不会去看直方图的数据分布,所以它生成的计划并不可信的。

针对这种问题,建议用explain plan解析SQL时,要用实际值替换SQL语句中的变量。然而,这种做法在cursor_sharing参数设置为similar或force时,会引出一些新问题。以Oracle 9i为例,把cursor_sharing设置为force,执行下面两个查询:

select * from t_plan where flag = 'N';

select * from t_plan where flag = 'Y';

由于cursor_sharing的作用,它们都被转换为同一个查询:

select * from t_plan where flag = :"SYS_B_0"

Oracle 9iR1引入了一个新特性,优化器能在第一次硬分析SQL时窥视绑定变量值,并根据这些变量值来优化查询。因此,第一次查询flag='N'时,优化器能正确的选择了索引扫描,其执行计划如下:

-----------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|

-----------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | | | |

| 1 | TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 30 | 3 (34)|

|* 2 | INDEX RANGE SCAN | IDX_PLAN_FLAG | 1 | | 2 (50)|

-----------------------------------------------------------------------------------

然而,第二次查询flag='Y'时,我们期望做全表扫描,然而由于优化器不再窥视变量,重用了上面这个执行计划。

另一方面,当用explain plan命令对这两个查询做解析时,会出现另一种情况:

explain plan for select * from t_plan where flag = 'N';

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

-----------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|

-----------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 30 | 3 (34)|

| 1 | TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 30 | 3 (34)|

|* 2 | INDEX RANGE SCAN | IDX_PLAN_FLAG | 1 | | 2 (50)|

-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("T_PLAN"."FLAG"='N')

delete from plan_table;

explain plan for select * from t_plan where flag = 'Y';

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

-------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|

-------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 19 | 570 | 14 (8)|

|* 1 | TABLE ACCESS FULL | T_PLAN | 19 | 570 | 14 (8)|

-------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("T_PLAN"."FLAG"='Y')

这表明,解析SQL时,cursor_sharing参数不起作用,SQL语句没有被转换成绑定变量形式。优化器能为SQL生产正确的执行计划。

需要说明的是,当cursor_sharing设置为similar时,情况会变得更复杂,超出了本文的讨论范围。这里要说明的是,如果SQL语句使用了绑定变量,或者是由于cursor_sharing的作用出现了绑定变量,则它的解析计划是不可信的,可能跟实际的执行计划不同。

3.3 查看真实的执行计划

V$SQL_PLAN

真实的执行计划就保存在V$SQL_PLAN,通常我们需要一些工具来格式化输出执行计划。

从Oracle 10g开始,可以用DBMS_XPLAN.DISPLAY_CURSOR来查看V$SQL_PLAN的执行计划。9i并没有这个功能,我们可以写一些SQL脚本仿照DBMS_XPLAN的格式输出执行计划,如:

select lpad(' ', 4 * (level - 1)) || operation "Operation",

options "Options",

decode(id, 0, 'Cost=' || nvl(cost, '0'), object_name) "Object Name",

substr(optimizer, 1, 6) "Optimizer",

access_predicates "Access Predicates",

filter_predicates "Filter Predicates"

from v$sql_plan a

connect by prior id = a.parent_id

and prior a.hash_value = a.hash_value

and prior a.child_number = a.child_number;

start with hash_value = &hash_value

and id = 0;

我们也可以创建一个基于V$SQL_PLAN的视图,然后使用DBMS_XPLAN.DISPLAY来查询执行计划:

-- 以sysdba登录

conn /as sysdba;

-- 创建基于v$sql_plan的视图

create or replace view dynamic_plan_table as

select hash_value || '_' || child_number statement_id,

sysdate timestamp, operation, options, object_node,

object_owner, object_name, 0 object_instance,

optimizer, search_columns, id, parent_id, position,

cost, cardinality, bytes, other_tag, partition_start,

partition_stop, partition_id, other, distribution,

cpu_cost, io_cost, temp_space, access_predicates, filter_predicates

from v$sql_plan;

-- 创建公共同义词

create public synonym dynamic_plan_table for dynamic_plan_table;

-- 赋权给public

grant select on dynamic_plan_table to public;

这样,我们就能在其它用户下用dbms_xplan.display查看执行计划了:

-- 执行查询

select * from t_plan where flag = 'N';

-- 用dbms_xplan.display查看执行计划

select plan_table_output

from table(dbms_xplan.display('dynamic_plan_table',

(select hash_value || '_' || child_number x from v$sql

where sql_text = 'select * from t_plan where flag = ''N''')));

-- 输出执行计划

-----------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|

-----------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | | | |

| 1 | TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 30 | 3 (34)|

|* 2 | INDEX RANGE SCAN | IDX_PLAN_FLAG | 1 | | 2 (50)|

-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("FLAG"='N')

10046追踪事件

10046追踪事件(包括SQL_TRACE)可以从Shared Pool中捕获SQL真实的执行计划,记录在trace文件以“STAT”为开头的行中。原始的trace文件可读性差,通常需要用tkprof对其进行格式化。这种方法可以在各个版本的Oracle中使用。

3.4 tkprof的奇怪现象

前面提到过,tkprof用来格式化10046跟踪事件(或SQL Trace)产生的文件。使用tkprof时,如果带explain=user/password参数,将会为SQL产生解析计划,它可能是跟实际执行计划不同的。也就说,有可能从tkprof格式化后的trace文件看到一个SQL语句有两个不同的计划!

如用下面命令格式化trace文件:

tkprof utf810g_ora_1636.trc sql_plan.prf explain=system/sys

从sql_plan.prf中,将看到SQL语句有两个计划:

select * from t_plan where flag = :a

……

Rows Row Source Operation

------- ---------------------------------------------------

1 TABLE ACCESS BY INDEX ROWID T_PLAN

1 INDEX RANGE SCAN IDX_PLAN_FLAG

Rows Execution Plan

------- ---------------------------------------------------

0 SELECT STATEMENT MODE: ALL_ROWS

1 TABLE ACCESS MODE: ANALYZED (FULL) OF 'T_PLAN' (TABLE)

如上,第一个就是真正的执行计划,第二个是解析解析计划。

3.5 查看SQL计划的建议

综合前面的讨论,这里给出几点关于查看SQL计划的建议:

ü 要区分你所看到的SQL计划究竟是解析计划还是执行计划。如果你用了查看SQL计划的工具不是前面列出的工具,则应该按本文3.1节分析Autotrace本质的方法来判断这个工具是查看什么类型的SQL计划的。

ü 如果SQL语句使用了绑定变量,或者是由于cursor_sharing的作用出现了绑定变量,则要意识到解析计划可能是不真实的。

ü 尽可能获取SQL真实的执行计划,10g以后建议使用DBMS_XPLAN.DISPLAY_CURSOR, 9i请参考本文3.3节列出的方法。

ü 如果SQL的解析计划跟执行计划不一致,可参考本文1.3节列出的思路来分析。

4 深入分析SQL计划

4.1 SQL计划的内容

SQL计划就是Oracle执行SQL语句的一系列步骤。其中,行源操作是SQL计划的核心部分,包括表或索引的访问路径(access path)、表之间的(join method)和连接顺序(join order),还有一些数据运算操作。

常见的访问路径:

Ÿ 全表扫描(Full Table Scan):适用于小表访问或大量的数据访问。

Ÿ Rowid扫描(Rowid Scan):通过rowid直接定位到数据块,是最快的访问方式。

Ÿ 索引扫描(Index Scan):包括Index Unique Scans, Index Range Scans, Index Skip Scans, Index Full Scans, Fast Full Index Scans等。

常见的连接方法:

Ÿ 嵌套循环连接(Nested Loop Joins)

Ÿ 哈希连接(Hash Joins)

Ÿ 排序合并连接(Sort Merge Joins)

常见的数据操作:

Ÿ 排序(sort)

Ÿ 过滤(filter)

Ÿ 聚集操作(aggregation)

除行源操作外,SQL计划还包括下面这些附加信息:优化信息(如成本、基数等),分区访问信息,并行执行信息等。

大家平时在阅读SQL计划时可能会更多的关注访问路径、连接方法、连接顺序、成本、基数等信息,本文不再重复介绍这些内容。下面我们将从SQL计划中发掘一些大家平时可能忽视的,却对SQL调优非常有用的信息。

4.2 访问谓词(Access Predicates)和过滤谓词(Filter Predicates)

如果一个SQL语句的谓词上有索引,且优化器选择了这个索引作为访问路径,那这个谓词称为访问谓词,否则就叫做过滤谓词。

从Oracle 9i开始,V$SQL_PLAN和PLAN_TABLE都包含了access_predicates, filter_predicates两个字段。Autotrace和DBMS_XPLAN都默认输出这两种谓词信息,如:

create table t_plan pctfree 99 pctused 1 as

select mod(rownum, 10) x, mod(rownum, 100) y, rpad('x', 20) z

from all_objects where rownum < 1001;

create index idx_plan_x on t_plan(x);

analyze table t_plan compute statistics for table for all columns for all indexes;

这里创建了一个包含x, y, z 3个字段的表,在x上创建了索引,作以下查询:

explain plan for select * from t_plan where x = 1 and y = 10;

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

------------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 24 | 101 (0)| 00:00:02 |

|* 1 | TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 24 | 101 (0)| 00:00:02 |

|* 2 | INDEX RANGE SCAN | IDX_PLAN_X | 100 | | 1 (0)| 00:00:01 |

------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter("Y"=1)

2 - access("X"=1)

这个SQL计划首先扫描索引idx_plan_x,谓词“x = 1”为access,返回记录100行,成本为1。然而用索引返回的rowid访问表时用谓词“y = 1”做了一次filter,返回一条记录,成本剧增到101。可见,用过滤谓词从表中过滤大量的数据代价很高。

接着,删除x字段上的索引,在x和y上创建索引,重新执行查询:

drop index IDX_PLAN_X;

create index idx_plan_xy on t_plan(x,y);

analyze table t_plan compute statistics for table for all columns for all indexes;

explain plan for select * from t_plan where x = 1 and y = 1;

select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

-------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-------------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 24 | 2 (0)| 00:00:01 |

| 1 | TABLE ACCESS BY INDEX ROWID| T_PLAN | 1 | 24 | 2 (0)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | IDX_PLAN_XY | 1 | | 1 (0)| 00:00:01 |

-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("X"=1 AND "Y"=1)

这时,Oracle把“x = 1 and y = 1”看作访问谓词,不用进行过滤,成本大幅降低。

可见,通过分析SQL计划,能发现一些导致过滤大量数据的谓词,从而引导我们创建合理的索引。

4.3 投影(Projections)

SQL计划的投影就是从一组字段中选出需要的字段,如从一个表中选出两个字段就是投影操作。投影信息展示每个行源操作访问了哪些字段,每个行源操作的输出是如何投影到下一步操作的。这些信息能帮我们更好地理解Oracle是如何访问和处理数据的,有时候还能从中了解到索引为什么没有被使用。

从Oracle 10g开始,V$SQL_PLAN和PLAN_TABLE的projections字段保存了投影信息。执行DBMS_XPLAN.DISPLAY和DBMS_XPLAY.DISPLAY_CURSOR时,指定format参数为ALL就能显示字段投影信息。如:

create table t_plan as select object_name, object_id, object_type, owner from all_objects;

explain plan for select object_name, object_type from t_plan where owner = 'SYS' order by object_id;

select * from table(dbms_xplan.display('PLAN_TABLE', null, 'ALL'));

PLAN_TABLE_OUTPUT

-------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |

-------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 6655 | 376K| | 112 (2)| 00:00:02 |

| 1 | SORT ORDER BY | | 6655 | 376K| 952K| 112 (2)| 00:00:02 |

|* 2 | TABLE ACCESS FULL| T_PLAN | 6655 | 376K| | 15 (0)| 00:00:01 |

-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - filter("OWNER"='SYS')

Column Projection Information (identified by operation id):

-----------------------------------------------------------

1 - (#keys=1) "OBJECT_ID"[NUMBER,22], "OBJECT_NAME"[VARCHAR2,30],

"OBJECT_TYPE"[VARCHAR2,19]

2 - "OBJECT_NAME"[VARCHAR2,30], "OBJECT_ID"[NUMBER,22],

"OBJECT_TYPE"[VARCHAR2,19]

虽然这个查询只选择了object_name, object_type两列,object_id却也被行源操作2选中了。这是因为object_id要返回给行源操作1做排序。到了行源操作1,object_id由第二列变成了第一列。这是因为object_id在表中是第二列,在行源操作1中要排序被前置到第一位。“#keys=1”表示第一列为排序字段。

可见,投影信息向展现了查询数据流的细节,能帮助我们深刻理解SQL的执行过程。

4.4 行源执行统计(row source execution statistics)

从9iR2开始,Oracle可以收集执行计划每个操作的实际执行统计信息,包括执行时间、逻辑读、物理读等,这些信息称为行源执行统计。行源执行统计信息可以通过V$SQL_PLAN_STATISTICS或V$SQL_PLAN_STATISTICS_ALL两个视图查看。要收集这些统计信息,需要把初始化参数STATISTICS_LEVEL设置为ALL或在SQL语句中使用/*+ gather_plan_statistics */提示。

执行计划的相关视图

视图

说明

V$SQL_PLAN

Shared Pool的游标的执行计划信息。

V$SQL_PLAN_STATISTICS

提供每个游标的行源执行统计信息。

V$SQL_PLAN_STATISTICS_ALL

把V$SQL_PLAN、V$SQL_PLAN_STATISTICS、V$SQL_WORKAREA 3个视图的信息合并起来。

STATISTICS_LEVEL说明

STATISTICS_LEVEL是一个动态初始化参数,默认值为TYPICAL,不包括行源执行统计信息。为了收集这些信息,我们需要在会话级把它设置为ALL(这里要注意,如果在实例级设置为ALL,将会增加系统负荷,导致不稳定)。

输出包含统计信息的执行计划

从Oracle 10g开始,DBMS_XPLAN提供了输出行源统计信息的方法。例如:

create table t_plan pctfree 99 pctused 1 as

select 'Y' flag, rpad('x', 30) padding from all_objects where rownum < 20;

alter session set statistics_level = all;

select * from t_plan where flag = 'Y';

select sql_id, sql_text from v$sql where sql_text like '%select * from t_plan%';

SQL_ID SQL_TEXT

-------------- -------------------------------------------------------------------------------

f14z7cm27svus select * from t_plan where flag = 'Y'

a4g8a1b9hjrsf select sql_id, sql_text from v$sql where sql_text like '%select * from t_plan%'

select * from table(dbms_xplan.display_cursor('f14z7cm27svus', null, 'typical allstats last'));

PLAN_TABLE_OUTPUT

---------------------------------------------------------------------------------------------------

Operation | Name | Starts| E-Rows| Cost(%CPU)| E-Time | A-Rows| A-Time | Buffers| Reads

---------------------------------------------------------------------------------------------------

TABLE ACCESS FULL| T_PLAN| 1 | 19 | 6 (0)| 00:00:01| 19 |00:00:00.01| 22 | 4

---------------------------------------------------------------------------------------------------

各列的含义:

Ÿ Starts:累计执行次数

Ÿ E-Rows:优化器估计的返回行数,这里E是Estimate的简写。

Ÿ E-Time:优化器估计的执行时间

Ÿ Rows :实际的返回行数,这里A是Actual的简写。

Ÿ Time :实际的执行时间

Ÿ Buffer:逻辑读

Ÿ Reads :物理读

这里,我们能看到执行计划的每个步骤实际花费了多少时间,产生了多少次逻辑读、物理读。还可以通过优化器估计值跟实际统计值的对比,发现优化器成本评估的误差。

让10046 trace文件输出行源执行统计

有时候我们会希望用10046来观察游标的执行计划。这时也可以在会话级把STATISTICS_LEVEL设置为ALL,收集行源执行统计信息。其过程如下:

alter session set statistics_level = all; -- 收集行源执行统计

alter session set events '10046 trace name context forever, level 12'; -- 跟踪SQL

<SQL Statement> or <PL/SQL Program> -- 在这里执行SQL或PL/SQL

alter session set events '10046 trace name context off'; -- 停止跟踪

trace文件中以“STAT”开头的行记录了SQL的行源操作及其统计信息:

ü 9i的STAT:

STAT #1 id=1 cnt=19 pid=0 pos=1 obj=93250 op='TABLE ACCESS FULL T_PLAN (cr=22 r=0 w=0 time=191 us)'

ü 10g的STAT:

STAT #4 id=1 cnt=19 pid=0 pos=1 obj=12566 op='TABLE ACCESS FULL T_PLAN (cr=22 pr=0 pw=0 time=242 us)'

ü 11g的STAT

STAT #6 id=1 cnt=19 pid=0 pos=1 obj=58923 op='TABLE ACCESS FULL T_PLAN (cr=9 pr=0 pw=0 time=358 us

cost=3 size=380 card=19)'

可见,9i和10g的STAT结构是一样的,11g增加了cost,size,card。下面给出各项值的含义:

#No

游标在trace文件中的代号。一个游标的跟踪信息可能出现在文件中的不同地方,通过这个代号联系起来。

id

当前的行源操作在整个游标执行计划中的唯一编号。

cnt

行源操作返回的行数。

pid

当前行源操作的父id

pos

行源操作在父id下的序号

obj

行源操作的对象id(dba_objects.object_id)。如果想圈套循环连接这样没有对象的行源操作,这个值为0。

op

行源操作名

cr

一致性读(逻辑读)

r/pr

物理读

w/pw

物理写(像排序这样的操作会写临时表空间)

time

花费的时间,单位微妙(us)

cost

成本(v$sql_plan.cost)

card

基数(v$sql_plan.cardinality)

size

返回的数据量(v$sql_plan.bytes),单位字节

行源执行统计包括了逻辑读、物理读、消耗时间等重要信息,它能告诉我们SQL计划那个步骤最耗费资源,哪个步骤花的时间最长。

【参考资料】

《Oracle Performance and Tuning Guide》

《Oracle Supplied PL/SQL Packages and Types Reference》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: