首页 / ORACLE / Oracle里常见的执行计划
Oracle里常见的执行计划
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Oracle里常见的执行计划,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含15135字,纯文字阅读大概需要22分钟。
内容图文
本文介绍了Oracle数据库里常见的执行计划,使用的Oracle数据库版本为11.2.0.1。
1 、与表访问相关的执行计划
Oracle 数据库里与表访问有关的两种方法:全表扫描和 ROWID 扫描。反映在执行计划上,与全表扫描对应的执行计划中的关键字是“ TABLE ACCESS FULL” ,与 ROWID 扫描对应的执行计划中关键字是“ TABLE ACCESS BY USER ROWID” 或“ TABLE ACCESS BY INDEX ROWID” 。
scott@MYDB>select empno,ename,rowid from emp where ename=‘SCOTT‘; EMPNO ENAME ROWID ---------- ------------------------------ ------------------ 7788 SCOTT AAAR3xAAEAAAACXAAH
scott@MYDB>select empno,ename,rowid from emp where rowid=‘AAAR3xAAEAAAACXAAH‘; EMPNO ENAME ROWID ---------- ------------------------------ ------------------ 7788 SCOTT AAAR3xAAEAAAACXAAH
scott@MYDB>select empno,ename,rowid from emp where empno=7788; EMPNO ENAME ROWID ---------- ------------------------------ ------------------ 7788 SCOTT AAAR3xAAEAAAACXAAH
从实验中可以看出,第一个 SQL 执行计划走的是对表 EMP 的全表扫描,对应的关键字就是“ TABLE ACCESS FULL” ;第二个 SQL 的执行计划走的是对表 EMP 的 ROWID 扫描,对应的关键字是“ TABLE ACCESS BY USER ROWID” ;第三个 SQL 的执行计划走的是对表 EMP 的 ROWID 扫描,对应的关键字是“ TABLE ACCESS BY INDEX ROWID” 。注意如果 ROWID 来源于用户手工指定则对应的是“ TABLE ACCESS BY USER ROWID” ;如果 ROWID 是来源于索引,则对应的是“ TABLE ACCESS BY INDEX ROWID” 。
2 与 B*Tree 索引相关的执行计划
Oracle 数据库里常见的与 B*Tree 索引访问相关的方法,包括索引唯一扫描、索引范围扫描、索引全扫描、索引快速全扫描和索引跳跃式扫描,反映在执行计划上分别对应 INDEX UNIQUE SCAN 、 INDEX RANGE SCAN 、 INDEX FULL SCAN 、 INDEX FAST FULL SCAN 和 INDEX SKIP SCAN 。
用实验查看相关执行计划
zx@MYDB>create table employee (gender varchar2(1),employee_id number); Table created. zx@MYDB>insert into employee values(‘F‘,99); 1 row created. zx@MYDB>insert into employee values(‘F‘,100); 1 row created. zx@MYDB>insert into employee values(‘M‘,101); 1 row created. zx@MYDB>insert into employee values(‘M‘,102); 1 row created. zx@MYDB>insert into employee values(‘M‘,103); 1 row created. zx@MYDB>insert into employee values(‘M‘,104); 1 row created. zx@MYDB>insert into employee values(‘M‘,105); 1 row created. zx@MYDB>create unique index idx_uni_emp on employee(employee_id); Index created. zx@MYDB>select * from employee where employee_id=100; GEN EMPLOYEE_ID --- ----------- F 100
第一个 SQL 的执行计划走的是对索引 IDX_UNI_EMP 的索引唯一扫描,关键字是“ INDEX UNIQUE SCAN” 。
zx@MYDB>drop index idx_uni_emp; Index dropped. zx@MYDB>create index idx_emp_1 on employee(employee_id); Index created. zx@MYDB>select * from employee where employee_id=100; GEN EMPLOYEE_ID --- ----------- F 100
现在 SQL 的执行计划是对索引 IDX_EMP_1 的索引范围扫描,关键字是“ INDEX RANGE SCAN” 。
zx@MYDB>truncate table employee; Table truncated. zx@MYDB>begin 2 for i in 1..10000 loop 3 insert into employee select decode(mod(i,2),0,‘M‘,‘F‘),i from dual; 4 end loop; 5 end; 6 / PL/SQL procedure successfully completed. zx@MYDB>zx@MYDB>commit; Commit complete. zx@MYDB>select gender,count(*) from employee group by gender; GEN COUNT(*) --- ---------- M 5000 F 5000 zx@MYDB>exec dbms_stats.gather_table_stats(ownname=>USER,tabname=>‘EMPLOYEE‘,estimate_percent=>100,cascade=>true,no_invalidate=>false,method_opt=>‘FOR ALL COLUMNS SIZE 1‘); PL/SQL procedure successfully completed. zx@MYDB>set autotrace traceonly zx@MYDB>select employee_id from employee; 10000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2119105728 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 10000 | 40000 | 7 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| EMPLOYEE | 10000 | 40000 | 7 (0)| 00:00:01 | ------------------------------------------------------------------------------ ...省略部分输出
明明可以扫描索引
IDX_EMP_1
得到结果,却选择了全表扫描,就算使用
Hint
强制让
Oracle
扫描索引
IDX_EMP_1
,结果却是
Hint
失效了。
zx@MYDB>select /* +index(employee idx_emp_1) */employee_id from employee; 10000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2119105728 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 10000 | 40000 | 7 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| EMPLOYEE | 10000 | 40000 | 7 (0)| 00:00:01 | ------------------------------------------------------------------------------ ...省略部分输出
出现这个现象的原因是 Oracle 无论如何总会保证目标 SQL 结果的正确性,可能会得到错误结果的执行路径 Oracle 是不会考虑的。对于索引 IDX_EMP_1 而言,它是一个单键值的 B*Tree 索引,所以 NULL 值不会存储在其中,那么一量 EMPLOYEE_ID 出现了 NULL 值 ( 虽然这里实际上并没有 NULL 值 ) ,则扫描索引的结果就是漏掉那些 EMPLOYEE_ID 为 NULL 值的记录,这也就意味着如果 Oracle 在执行上述 SQL 时选择了扫描 IDX_EMP_1 ,那么执行结果就有可能是不准的。在这种情况下, Oracle 当然不会考虑扫描索引,即使我们使用了 Hint 。
如果想让 Oracle 在执行上述 SQL 时扫描索引 IDX_EMP_1 ,则必须将列 EMPLOYEE_ID 的属性修改为 NOT NULL 。这就相当于告诉 Oracle ,这里列 EMPLOYEE_ID 上不会有 NULL 值,你就放心地扫描索引 IDX_EMP_1 吧。
zx@MYDB>alter table employee modify employee_id not null; Table altered. zx@MYDB>select employee_id from employee; 10000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3918702848 ---------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10000 | 40000 | 7 (0)| 00:00:01 | | 1 | INDEX FAST FULL SCAN| IDX_EMP_1 | 10000 | 40000 | 7 (0)| 00:00:01 | ---------------------------------------------------------------------------------- ...省略部分输出
从上面的输出可以看出,现在 SQL 的执行计划走的是对索引 IDX_EMP_1 的索引快速全扫描,对应的是“ INDEX FAST FULL SCAN” 。
现在加上强制走索引 IDX_EMP_1 的 Hint ,再次执行该 SQL
zx@MYDB>select /*+index(employee idx_emp_1) */employee_id from employee; 10000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 438557521 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 10000 | 40000 | 20 (0)| 00:00:01 | | 1 | INDEX FULL SCAN | IDX_EMP_1 | 10000 | 40000 | 20 (0)| 00:00:01 | ------------------------------------------------------------------------------ ...省略部分输出可以看到现在SQL的执行计划走的是对索引IDX_EMP_1的索引快速全扫描INDEX FULL SCAN (如果是在11.2.0.4版本上执行上以SQL可以以看到还是 INDEX FAST FULL SCAN)
zx@MYDB>drop index idx_emp_1; Index dropped. zx@MYDB>create index idx_emp_2 on employee(gender,employee_id); Index created. zx@MYDB>select * from employee where employee_id=101;
从上面输出可以看出, SQL 的执行计划走的是对索引 IDX_EMP_2 的索引跳跃式扫描,对应“ INDEXSKIP SCAN” 。
3 、与表连接相关的执行计划
Oracle 数据库里常见的与表连接相关的一些方法:排序合并连接、嵌套循环连接、哈希连接等以及反连接和半连接
zx@MYDB>create table t1(col1 number,col2 varchar2(1)); Table created. zx@MYDB>create table t2(col2 varchar2(1),col3 varchar2(2)); Table created. zx@MYDB>insert into t1 values(1,‘A‘); 1 row created. zx@MYDB>insert into t1 values(2,‘B‘); 1 row created. zx@MYDB>insert into t1 values(3,‘C‘); 1 row created. zx@MYDB>insert into t1 values(4,‘D‘); 1 row created. zx@MYDB>insert into t1 values(5,‘E‘); 1 row created. zx@MYDB>insert into t2 values(‘A‘,‘A2‘); 1 row created. zx@MYDB>insert into t2 values(‘B‘,‘B2‘); 1 row created. zx@MYDB>insert into t2 values(‘D‘,‘D2‘); 1 row created. zx@MYDB>insert into t2 values(‘E‘,‘E2‘); 1 row created. zx@MYDB> zx@MYDB>commit; Commit complete. zx@MYDB>select * from t1; COL1 COL ---------- --- 1 A 2 B 3 C 4 D 5 E zx@MYDB>select * from t2; COL COL3 --- ------ A A2 B B2 D D2 E E2 zx@MYDB>select t1.col1,t1.col2,t2.col3 from t1,t2 where t1.col2=t2.col2; COL1 COL COL3 ---------- --- ------ 1 A A2 2 B B2 4 D D2 5 E E2
从上面的输出可以看出, SQL 的执行计划走的是对表 T1 和 T2 的哈希连接,连接条件是 t1.col2=t2.col2 ,对应的关键字是“ HASH JOIN” 。
使用强制走排序合并连接的 Hint 后再次执行 SQL
zx@MYDB>select /*+use_merge(t1,t2) */t1.col1,t1.col2,t2.col3 from t1,t2 where t1.col2=t2.col2; COL1 COL COL3 ---------- --- ------ 1 A A2 2 B B2 4 D D2 5 E E2
从上面的输出可以看出现在 SQL 的执行计划走的是对表 T1 和 T2 的排序合并连接,对应的关键字是“ MERGEJOIN” 和“ SORT JOIN” 。
接着使用强制走嵌套循环连接的 Hint 后再次执行 SQL
zx@MYDB>select /*+use_nl(t1,t2) */t1.col1,t1.col2,t2.col3 from t1,t2 where t1.col2=t2.col2; COL1 COL COL3 ---------- --- ------ 1 A A2 2 B B2 4 D D2 5 E E2
从上面的输出可以看出现在 SQL 的执行计划走的是对表 T1 和 T2 的嵌套循环连接,对应的关键字是“ NESTEDLOOPS”
嵌套循环连接的驱动表是可以变的,我们使用 Hint 将上述 SQL 的驱动表改为 T1 再将执行 SQL
zx@MYDB>select /*+ ordered use_nl(t1,t2) */t1.col1,t1.col2,t2.col3 from t1,t2 where t1.col2=t2.col2; COL1 COL COL3 ---------- --- ------ 1 A A2 2 B B2 4 D D2 5 E E2
从结果中可以看到,嵌套循环连接的驱动表确实已经变为 T1
再看反连接的例子。首先将表 T1 和 T2 的连接列 col2 改为 NOT NULL ,以便能走出我们想要的反连接的执行计划
zx@MYDB>alter table t1 modify col2 not null; Table altered. zx@MYDB>alter table t2 modify col2 not null; Table altered. zx@MYDB>select * from t1 where col2 not in (select col2 from t2 where col3=‘A2‘); COL1 COL ---------- --- 5 E 4 D 2 B 3 C
从输出内容上可以看出, SQL 的执行计划走的是对表 T1 和 T2 的哈希反连接,反连接在执行计划中对应的关键字是“ ANTI” ,哈希反连接对应的就是“ HASH JOIN ANTI” 。
反连接的具体连接方法是可变的,这里使用 Hint 将 SQL 的反连接改为排序合并反连接
zx@MYDB>select * from t1 where col2 not in (select /*+ MERGE_AJ */ col2 from t2 where col3=‘A2‘); COL1 COL ---------- --- 2 B 3 C 4 D 5 E
从输出内容可以看出, SQL 的执行计划走的是对表 T1 和 T2 的排序合并反连接,对应的关键字是“ MERGE JOIN ANTI” 。
再使用 Hint 将 SQL 的反连接方法改为嵌套循环反连接
zx@MYDB>select * from t1 where col2 not in (select /*+ NL_AJ */ col2 from t2 where col3=‘A2‘); COL1 COL ---------- --- 2 B 3 C 4 D 5 E
再看半连接的例子。
zx@MYDB>insert into t2 values(‘E‘,‘E3‘); 1 row created. zx@MYDB>commit; Commit complete. zx@MYDB>select * from t1 where exists(select * from t2 where t1.col2=t2.col2 and col3>‘D2‘); COL1 COL ---------- --- 5 E
从输出可以看出, SQL 的执行计划走的是对表 T1 和 T2 的哈希半连接,半连接在执行计划中对应的关键字是“ SEMI” ,哈希半连接在执行计划中对应的关键字是“ HASH JOIN SEMI” 。
半连接的具体连接方法是可变的,使用 Hint 将 SQL 的半连接方法改为排序合并半连接:
zx@MYDB>select * from t1 where exists(select /*+ MERGE_SJ */* from t2 where t1.col2=t2.col2 and col3>‘D2‘); COL1 COL ---------- --- 5 E
从输出内容可以看出, SQL 的执行计划走的是对表 T1 和 T2 的排序合并半连接,对应的关键字是“ MERGE JOIN SEMI” 。
再使用 Hint 把 SQL 的半连接方法改为嵌套循环半连接:
zx@MYDB>select * from t1 where exists(select /*+ NL_SJ */* from t2 where t1.col2=t2.col2 and col3>‘D2‘); COL1 COL ---------- --- 5 E
从输出内容可以看出, SQL 的执行计划走的是对表 T1 和 T2 的嵌套循环半连接,对应的关键字是“ NESTED LOOPS SEMI”
4 、关于位图索引相关的执行计划
Oracle 数据库里常见的与位图索引访问相关的方法包括如下这些类型:位图索引单键值扫描、位图索引范围扫描、位图索引全扫描、位图索引快速全扫描、位图按位与、位图按位或、位图按位减等。
Oracle 在使用完位图索引后通常会将最后的位图运算结果转化为 ROWID ,这一步转换过程对应的执行计划中的“ BITMAP CONVERSION TO ROWIDS” 。
zx@MYDB>create table customer 2 ( 3 customer# number, 4 marital_status varchar2(10), 5 region varchar2(10), 6 gender varchar2(10), 7 income_level varchar2(10) 8 ); Table created. zx@MYDB>insert into customer values(101,‘single‘,‘east‘,‘male‘,‘bracket_1‘); 1 row created. zx@MYDB>insert into customer values(102,‘married‘,‘central‘,‘female‘,‘bracket_4‘); 1 row created. zx@MYDB>insert into customer values(103,‘married‘,‘west‘,‘female‘,‘bracket_2‘); 1 row created. zx@MYDB>insert into customer values(104,‘divorced‘,‘west‘,‘male‘,‘bracket_4‘); 1 row created. zx@MYDB>insert into customer values(105,‘single‘,‘central‘,‘female‘,‘bracket_2‘); 1 row created. zx@MYDB>insert into customer values(106,‘married‘,‘central‘,‘female‘,‘bracket_3‘); 1 row created. zx@MYDB>commit; Commit complete. zx@MYDB>create bitmap index idx_b_region on customer(region); Index created. zx@MYDB>create bitmap index idx_b_maritalstatus on customer(marital_status); Index created. zx@MYDB>exec dbms_stats.gather_table_stats(ownname=>USER,tabname=>‘CUSTOMER‘,estimate_percent=>100,cascade=>true); PL/SQL procedure successfully completed. zx@MYDB>select /*+ index(customer idx_b_region) */ customer# from customer where region=‘east‘; CUSTOMER# ---------- 101
650) this.width=650;" src="/upload/getfiles/default/2022/11/8/20221108010921664.jpg" title="1.png" /> 从上面的输出内容可以看出,SQL的执行计划走的是对位图索引IDX_B_REGION的位图索引单键值扫描,对就的关键字是“BITMAP INDEX SINGLE VALUE”。
把 SQL 改写为范围查询后再次执行
zx@MYDB>select /*+index(customer idx_b_region) */ customer# from customer where region between ‘east‘ and ‘west‘; CUSTOMER# ---------- 101 103 104
650) this.width=650;" src="/upload/getfiles/default/2022/11/8/20221108010922473.jpg" title="1.png" /> 从输出内容可以看出SQL走的执行计划是对位图索引IDX_B_REGION的位图索引范围扫描,对应的关键字是“BITMAP INDEX RANGE SCAN”。
去掉 where 条件,并且只查询位图索引 IDX_B_REGION 的索引键值列:
zx@MYDB>select region from customer; REGION ------------------------------ central central central east west west
650) this.width=650;" src="/upload/getfiles/default/2022/11/8/20221108010922794.jpg" title="1.png" /> 从输出可以看出SQL走的执行计划是对位图索引IDX_B_REGION的位图索引快速全扫描,对应的关键字是“BIT INDEX FAST FULL SCAN”。
执行如下 SQL :
zx@MYDB>select count(*) from customer where marital_status=‘married‘ and region in (‘central‘,‘west‘); COUNT(*) ---------- 3
从输出内容可以看出 SQL 走的执行计划中,用到了位图按位与操作,对应的关键字是“ BITMAP AND” 和位图按位或操作,对应的关键字是“ BITMAP OR” 。
再构造位图按位减的执行计划, SQL 如下:
zx@MYDB>select /*+index(customer idx_b_maritalstatus) index(customer idx_b_region) */ customer# from customer where marital_status=‘married‘ and region!=‘central‘; CUSTOMER# ---------- 103
650) this.width=650;" src="/upload/getfiles/default/2022/11/8/20221108010923482.jpg" title="1.png" /> 从输出的执行计划中,位图按位减的执行计划对应的关键字是“BITMAP MINUX”。
参考《基于Oracle的SQL优化》
本文出自 “DBA Fighting!” 博客,请务必保留此出处http://hbxztc.blog.51cto.com/1587495/1901416
原文:http://hbxztc.blog.51cto.com/1587495/1901416
内容总结
以上是互联网集市为您收集整理的Oracle里常见的执行计划全部内容,希望文章能够帮你解决Oracle里常见的执行计划所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。