Oracle数据库中的优化器是SQL分析和执行的优化工具,它负责制定SQL的执行计划,也就是它负责保证SQL执行效率最高.
比如优化器决定Oracle是以什么样的方式方式访问数据,是全表扫描(Full Table Scan) 、索引范围扫描(Index Range Scan)还是全索引快速扫描(INDEX Fast Full Scan, INDEX_FFS).
对于多表关联查询,它负责确定表之间以一种什么样的方式来关联,比如Hash Join 还是 NESTED LOOPS或者是MERGE JOIN。
这些因素直接决定着SQL的执行效率,所以优化器是SQL执行的核心,它做出的执行计划的好坏,直接决定了SQL的执行效率。
Oracle的优化器有两种
从ORACLE10G开始,RBO已经被弃用(但是我们依然可以通过HINT的方式使用它)。
在8i之前,ORACLE使用RBO(Rule Based Optimizer 基于规则的优化器)优化器。
它的执行机制非常简单,就是在优化器里面嵌入各种若干规则,执行的SQL符合那种规则,则按照对应的规则制定出相应的执行计划。
比如表上有索引,如果谓词上有索引的列存在,则ORACLE会选择索引,否则全表扫描。
又比如两个表关联的时候,按照表在SQL中的位置来决定哪个是驱动表,哪个是被驱动表。
RBO选择执行计划的优先级列表如下:
其中,排名越靠前,Oracle认为效率越高。例如:按索引访问的效率肯定高于全表扫描,多字段复合索引的效率高于单字段索引,等等。可见,RBO策略其实很简单。而且在SQL语句存在多个索引时,由于无法判别各个索引的可选性,RBO的分析过程与语句的语法相关,如where条件顺序,或者随机选取。
通俗地讲,RBO就是不关心被访问对象的实际数据分布情况、索引效率等,仅凭想象去决定应该如何去访问数据库。可见,RBO是一种非常粗放型的优化器。
思考:表中有索引,数据就必须选择索引吗?
首先创建一个数据分部非常不均匀的表
Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0
Connected as xxx@xgj
##建表
SQL> create table t as select 1 id ,object_name from dba_objects ;
Table created
##更新一条数据
SQL> update t set id=99 where rownum=1;
1 row updated
SQL> commit;
Commit complete
##创建索引
SQL> create index idx_t on t(id);
Index created
SQL> select id,count(1) from t group by id ;
ID COUNT(1)
---------- ----------
1 35251
99 1
现在表中的数据 id =1 的 35251条记录, id =99 的 1条记录。
如果发出两条这样的SQL
SQL> select * from t where t.id=1;
SQL> select * from t where t.id=99;
在RBO的年代里,执行计划是这样的
select * from t where t.id=99
##首先执行SQL
SQL> select /*+ rule */ * from t where id=99;
ID OBJECT_NAME
---------- -------------------
99 ICOL$
##查找SQL对应的SQL_ID
SQL> SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select /*+ rule */ * from t where id=99%';
SQL_ID CHILD_NUMBER
------------- ------------
axszbzuk341sw 0
##通过DBMS_XPLAN.display_cursor查询执行计划
SQL> select * from table(DBMS_XPLAN.display_cursor('axszbzuk341sw',0));
select * from t where t.id=1;
同样的方式我们得出执行计划
使用 hint /+ rule /的方式强制让ORACLE使用RBO优化器来产生执行计划,结果非常令人失望,不出所料,ORACLE在ID字段有索引的情况下,毫无悬念的全部选择了索引。
实际上我们知道,对于id=1,几乎所有的数据全都符合谓词条件,选择索引只能增加额外的开销(因为ORACLE首先要访问索引数据块,在索引上找到了相应的键值,然后按照键值上的rowid 再去访问表中相应的数据)。
既然我们几乎要访问所有表中的数据,那么全表扫描自然是最优的选择,很遗憾RBO做出了错误的选择。
从8i开始,ORACLE引入了CBO,它的思路是让ORACLE获取所有执行计划相关的信息,通过对这些信息进行计算分析,最后得出一个代价最小的执行计划作为最终的执行计划。
CBO基于成本的优化器,其特点如下。
(1)依赖于数据对象的统计信息,例如表的记录数、消耗的数据块数、索引的可选性(不同的记录值数量)等。
(2)使用精细的成本模型来评估SQL语句的执行性能。例如CPU消耗、逻辑读写(内存消耗)、I/O读写、网络传输量的消耗分析等。
(3)语句优化的整体质量高于RBO。
(4)Oracle的大量新特性只能在CBO下运行。例如:HASH_JOIN操作、Bitmap索引、物化视图等。
可见相比RBO,CBO是一种更加精确而有效的优化器。
同样是使用RBO中的案例,我们来看下CBO的表现。
##减少影响,先清空shared_Pool中的数据
SQL> alter system flush shared_pool;
System altered
##对表做一次分析(cascade => true ,索引也会被一同分析)
SQL> exec dbms_stats.gather_table_stats(user,'t',cascade => true);
PL/SQL procedure successfully completed
SQL>select /*+ all_rows */ * from t where t.id=1;
SQL>SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select /*+ all_rows */ * from t where t.id=1%';
SQL>select * from table(DBMS_XPLAN.display_cursor('d6adsf8y8c6mu',0));
SQL>select /*+ all_rows */ * from t where t.id=99;
SQL>SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select /*+ all_rows */ * from t where t.id=99%';
SQL>select * from table(DBMS_XPLAN.display_cursor('8w6r8hzrkunnu',0));
可以看出 CBO表现的非常完美,
因为这样的选择代价是最小的,为了验证CBO选择的正确性,我们分别让这两条SQL各自做一次全表扫描和和选择索引。
select * from t where id=1;
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> alter session set tracefile_identifier='mytest';
Session altered.
SQL> alter session set sql_trace=true;
Session altered.
SQL> select * from t where id=1;
.......
35251 rows selected.
SQL> alter session set sql_trace=false;
Session altered.
SQL> select name, value from v$parameter where name = 'user_dump_dest';
NAME VALUE
------------ ----------
user_dump_dest /oracle/diag/rdbms/cc/cc/trace
ORACLE主机上查看
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$ls *mytest*
cc_ora_8752_mytest.trc cc_ora_8752_mytest.trm
tkprof 工具汇总分析
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$tkprof cc_ora_8752_mytest.trc mytest.txt sys=no explain=xxx/xgj
TKPROF: Release 11.2.0.4.0 - Development on Tue Dec 27 23:24:32 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
截取部分关键信息:
select /*+ index(t IDX_T) */* from t where id=1;
SQL> alter session set tracefile_identifier='index_scan';
Session altered
SQL> alter session set sql_trace=true;
Session altered
SQL> select /*+ index(t IDX_T) */* from t where id=1;
.......
35251 rows selected.
SQL> alter session set sql_trace=false;
Session altered.
获取到trc原文件后,使用tkprof分析汇总
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$tkprof cc_ora_8709_index_scan.trc index_scan.txt explain=xxx/xgj sys=no
TKPROF: Release 11.2.0.4.0 - Development on Tue Dec 27 23:35:57 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
截取关键信息:
比较:
select * from t where id=1;--全表扫描了2470个数据块
select /*+ index(t IDX_T) */* from t where id=1;--索引扫描了4892个数据块
显然这种情况下,CBO的选择是正确的,使用全表扫描的效率更高,速度更快。
同样的方式我们比较下
select * from t where id=99;
select /*+ full(t) */ * from t where id=99;
select * from t where id=99;
Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0
Connected as xxx@xgj
SQL> alter session set tracefile_identifier='full_scan';
Session altered
SQL> alter session set sql_trace=true;
Session altered
SQL> select * from t where id=99;
ID OBJECT_NAME
---------- ------------------------
99 ICOL$
SQL> alter session set sql_trace=false;
Session altered
SQL>
在oracle主机上使用tkprof分析汇总
[root@entel1 ~]# su - oracle
oracle@entel1:[/oracle]$cd /oracle/diag/rdbms/cc/cc/trace
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$ls *full*
cc_ora_16197_full_scan.trc cc_ora_16197_full_scan.trm
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$tkprof cc_ora_16197_full_scan.trc full_scan.txt explain=zmc/smart sys=no
TKPROF: Release 11.2.0.4.0 - Development on Wed Dec 28 23:41:24 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
oracle@entel1:[/oracle/diag/rdbms/cc/cc/trace]$cat full_scan.txt
部分关键信息如下:
采用同样的方式我们分析下
select /+ full(t) / * from t where id=99;
通过对比上述的执行计划,选择索引的只扫描了3个数据块,而全表扫描则扫描了135个数据块,再此证明了CBO选择的正确性。
从ORACLE10g开始,ORACLE已经彻底丢弃了RBO。
即使在表索引没有被分析的时候,Oracle依然会使用CBO,此时,ORACLE会使用一种叫做动态采样的技术,在分析SQL的时候,动态的搜集表、索引上的一些数据块,使用这些数据块的信息及字典表中关于这些对象的信息来计算出执行计划的代价,从而挑选出最优的执行计划。
举例说明:
SQL> drop table t ;
Table dropped
SQL> create table t (x int ,y varchar2(2000));
Table created
SQL> insert into t select object_id ,object_name from dba_objects;
35254 rows inserted
SQL> commit;
Commit complete
SQL> create index idx_t on t(x);
Index created
SQL> alter session set tracefile_identifier='xgj_test';
Session altered
SQL> alter session set sql_trace=true;
Session altered
SQL> select count(1) from t where x<100;
COUNT(1)
----------
98
SQL> alter session set sql_trace=false;
Session altered
##查看SQL_ID
SQL> SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '% select count(1) from t where x<100%';
##根据SQL_ID查看执行计划
SQL>select * from table(DBMS_XPLAN.display_cursor('5yk2jzsm7cpnh',0));
如下:
可以看到,当表没有做分析的时候,ORACLE 会采用动态采样来搜集统计信息,这个动作只发生在SQL执行的第一次,即硬分析阶段使用,后续的软分析将不再使用动态采样,直接使用第一次SQL硬分析时生成的执行计划。
同时我们也采集了SQL_TRACE的信息,我们使用tkprof分析汇总后来看下
TKPROF: Release 11.2.0.4.0 - Development on Thu Dec 29 00:00:16 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved. Trace file: cc_ora_16197_xgj_test.trc Sort options: default ******************************************************************************** count = number of times OCI procedure was executed cpu = cpu time in seconds executing elapsed = elapsed time in seconds executing disk = number of physical reads of buffers from disk query = number of buffers gotten for consistent read current = number of buffers gotten in current mode (usually for update) rows = number of rows processed by the fetch or execute call ******************************************************************************** SQL ID: 96ckjhxq51nwk Plan Hash: 0 alter session set sql_trace=true call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 2 0.00 0.00 0 0 0 0 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 38 (ZMC) ******************************************************************************** SQL ID: gjz1ws759xbbp Plan Hash: 1807638002 SELECT /* OPT_DYN_SAMP */ /*+ ALL_ROWS IGNORE_WHERE_CLAUSE NO_PARALLEL(SAMPLESUB) opt_param('parallel_execution_enabled', 'false') NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE */ NVL(SUM(C1),:"SYS_B_0"), NVL(SUM(C2),:"SYS_B_1") FROM (SELECT /*+ IGNORE_WHERE_CLAUSE NO_PARALLEL("T") FULL("T") NO_PARALLEL_INDEX("T") */ :"SYS_B_2" AS C1, CASE WHEN "T"."X"<:"SYS_B_3" THEN :"SYS_B_4" ELSE :"SYS_B_5" END AS C2 FROM "ZMC"."T" SAMPLE BLOCK (:"SYS_B_6" , :"SYS_B_7") SEED (:"SYS_B_8") "T") SAMPLESUB call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 1 0.00 0.00 0 69 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 3 0.00 0.00 0 69 0 1 Misses in library cache during parse: 1 Misses in library cache during execute: 1 Optimizer mode: ALL_ROWS Parsing user id: 38 (ZMC) (recursive depth: 1) Number of plan statistics captured: 1 Rows (1st) Rows (avg) Rows (max) Row Source Operation ---------- ---------- ---------- --------------------------------------------------- 1 1 1 SORT AGGREGATE (cr=69 pr=0 pw=0 time=3244 us) 11794 11794 11794 TABLE ACCESS SAMPLE T (cr=69 pr=0 pw=0 time=1955 us cost=2 size=525 card=21) error during execute of EXPLAIN PLAN statement ORA-00907: missing right parenthesis parse error offset: 493 ******************************************************************************** SQL ID: 1pr6146q5bau5 Plan Hash: 3306840186 SELECT /* OPT_DYN_SAMP */ /*+ ALL_ROWS opt_param('parallel_execution_enabled', 'false') NO_PARALLEL(SAMPLESUB) NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE */ NVL(SUM(C1),:"SYS_B_0"), NVL(SUM(C2),:"SYS_B_1"), NVL(SUM(C3),:"SYS_B_2") FROM (SELECT /*+ NO_PARALLEL("T") INDEX("T" IDX_T) NO_PARALLEL_INDEX("T") */ :"SYS_B_3" AS C1, :"SYS_B_4" AS C2, :"SYS_B_5" AS C3 FROM "ZMC"."T" "T" WHERE "T"."X"<:"SYS_B_6" AND ROWNUM <= :"SYS_B_7") SAMPLESUB call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 1 0.00 0.00 4 2 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 3 0.00 0.00 4 2 0 1 Misses in library cache during parse: 1 Misses in library cache during execute: 1 Optimizer mode: ALL_ROWS Parsing user id: 38 (ZMC) (recursive depth: 1) Number of plan statistics captured: 1 Rows (1st) Rows (avg) Rows (max) Row Source Operation ---------- ---------- ---------- --------------------------------------------------- 1 1 1 SORT AGGREGATE (cr=2 pr=4 pw=0 time=301 us) 98 98 98 VIEW (cr=2 pr=4 pw=0 time=318 us cost=33 size=390 card=10) 98 98 98 COUNT STOPKEY (cr=2 pr=4 pw=0 time=216 us) 98 98 98 INDEX RANGE SCAN IDX_T (cr=2 pr=4 pw=0 time=213 us cost=33 size=429 card=33)(object id 79508) Rows Execution Plan ------- --------------------------------------------------- 0 SELECT STATEMENT MODE: HINT: ALL_ROWS 1 SORT (AGGREGATE) 98 VIEW 98 COUNT (STOPKEY) 98 INDEX MODE: ANALYZED (RANGE SCAN) OF 'IDX_T' (INDEX) 上面的信息就是ORACLE做动态采样的SQL语句。 ******************************************************************************** SQL ID: 5yk2jzsm7cpnh Plan Hash: 1500240790 select count(1) from t where x<100 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 2 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 1 0.00 0.00 0 2 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 3 0.00 0.00 0 4 0 1 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 38 (ZMC) Number of plan statistics captured: 1 Rows (1st) Rows (avg) Rows (max) Row Source Operation ---------- ---------- ---------- --------------------------------------------------- 1 1 1 SORT AGGREGATE (cr=2 pr=0 pw=0 time=43 us) 98 98 98 INDEX RANGE SCAN IDX_T (cr=2 pr=0 pw=0 time=110 us cost=2 size=1274 card=98)(object id 79508) Rows Execution Plan ------- --------------------------------------------------- 0 SELECT STATEMENT MODE: ALL_ROWS 1 SORT (AGGREGATE) 98 INDEX MODE: ANALYZED (RANGE SCAN) OF 'IDX_T' (INDEX) 上面是SQL本身执行时的实际信息 ******************************************************************************** OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 2 0.00 0.00 0 2 0 0 Execute 2 0.00 0.00 0 0 0 0 Fetch 1 0.00 0.00 0 2 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 5 0.00 0.00 0 4 0 1 Misses in library cache during parse: 2 OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 2 0.00 0.00 0 0 0 0 Execute 2 0.00 0.00 0 0 0 0 Fetch 2 0.00 0.00 4 71 0 2 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 6 0.00 0.00 4 71 0 2 Misses in library cache during parse: 2 Misses in library cache during execute: 2 4 user SQL statements in session. 0 internal SQL statements in session. 4 SQL statements in session. 2 statements EXPLAINed in this session. ******************************************************************************** Trace file: cc_ora_16197_xgj_test.trc Trace file compatibility: 11.1.0.7 Sort options: default 1 session in tracefile. 4 user SQL statements in trace file. 0 internal SQL statements in trace file. 4 SQL statements in trace file. 4 unique SQL statements in trace file. 2 SQL statements EXPLAINed using schema: ZMC.prof$plan_table Default table was used. Table was created. Table was dropped. 97 lines in trace file. 37 elapsed seconds in trace file.