第8章 调优技巧
8.1、查看真实的基数(Rows)
8.2、使用union代替OR
8.3、分页语句优化思路
8.3.1、单表分页优化思想
错误的分页(会产生严重的性能问题)
TEST01@testora>create table t_page as select * from dba_objects ; Table created. select * from (select t.*, rownum rn from (select * from t_page order by object_id) t) where rn >= 1 5 and rn <= 10; 10 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3603170480 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 91112 | 19M| | 4444 (1)| 00:00:54 | |* 1 | VIEW | | 91112 | 19M| | 4444 (1)| 00:00:54 | | 2 | COUNT | | | | | | | | 3 | VIEW | | 91112 | 17M| | 4444 (1)| 00:00:54 | | 4 | SORT ORDER BY | | 91112 | 17M| 21M| 4444 (1)| 00:00:54 | | 5 | TABLE ACCESS FULL| T_PAGE | 91112 | 17M| | 344 (1)| 00:00:05 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("RN"<=10 AND "RN">=1) Note ----- - dynamic sampling used for this statement (level=2) Statistics ---------------------------------------------------------- 7 recursive calls 0 db block gets 1378 consistent gets 0 physical reads 0 redo size 2075 bytes sent via SQL*Net to client 519 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 10 rows processed 从执行计划中我们可以看到该SQL走了全表扫描,假如t_page有上亿条记录,会产生严重的性能问题。所以该SQL不能走全表扫描,必须走索引扫描。 TEST01@testora>create index idx_page on t_page(object_id ,0); TEST01@testora>alter session set statistics_level = all ; Session altered. select * from (select t.*, rownum rn from (select /*+index(t_page idx_page)*/ * from t_page order by object_id) t) where rn >= 1 8 and rn <= 10; ---省略输出--- TEST01@testora>select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) 2 ; PLAN_TABLE_OUTPUT ----------------------------------------------------------------------------------------- SQL_ID 6hy652qjmgujd, child number 1 ------------------------------------- select * from (select t.*, rownum rn from (select /*+index(t_page idx_page)*/ * from t_page order by object_id) t) where rn >= 1 and rn <= 10 Plan hash value: 3119682446 ----------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | PLAN_TABLE_OUTPUT ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.13 | 1545 | |* 1 | VIEW | | 1 | 91112 | 10 |00:00:00.13 | 1545 | | 2 | COUNT | | 1 | | 86313 |00:00:00.11 | 1545 | | 3 | VIEW | | 1 | 91112 | 86313 |00:00:00.09 | 1545 | | 4 | TABLE ACCESS BY INDEX ROWID| T_PAGE|1|91112|86313 |00:00:00.07 | 1545 | | 5 | INDEX FULL SCAN | IDX_PAGE | 1 | 91112 | 86313 |00:00:00.02 | 217 | ----------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT ----------------------------------------------------------------------------------------- 1 - filter(("RN"<=10 AND "RN">=1)) Note ----- - dynamic sampling used for this statement (level=2) 29 rows selected.
复制
正确的分页
分页框架:
select *
from (select *
from (select a.*, rownum rn
from (需要分页的SQL) a)
where rownum <= 10)
where rn >= 1;
TEST01@testora>select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID 95puvbgwqw5mh, child number 0
-------------------------------------
select * from (select * from (select a.*, rownum rn
from (select /*+index(t_page idx_page)*/
* from t_page
order by object_id) a) where rownum <= 10) where rn >= 1
Plan hash value: 1201925926
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 5 |
|* 1 | VIEW | | 1 | 10 | 10 |00:00:00.01 | 5 |
|* 2 | COUNT STOPKEY | | 1 | | 10 |00:00:00.01 | 5 |
| 3 | VIEW | | 1 | 91112 | 10 |00:00:00.01 | 5 |
| 4 | COUNT | | 1 | | 10 |00:00:00.01 | 5 |
| 5 | VIEW | | 1 | 91112 | 10 |00:00:00.01 | 5 |
| 6 | TABLE ACCESS BY INDEX ROWID| T_PAGE | 1 | 91112 | 10 |00:00:00.01 | 5 |
| 7 | INDEX FULL SCAN | IDX_PAGE | 1 | 91112 | 10 |00:00:00.01 | 3 |
-----------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=1)
2 - filter(ROWNUM<=10)
Note
-----
- dynamic sampling used for this statement (level=2)
32 rows selected.
复制
分页语句优化思路:
要利用索引已经排序特性,将order by的列包含在索引中,同时也要利用rownum的COUNT STOPKEY特性来优化分页SQL。如果分页中没有排序,可以直接利用rownum的COUNT STOPKEY特性来优化分页SQL。
现在我们继续完善分页语句的优化思路:如果分页语句中有排序(order by),要利用索引已经排序特性,将order by的列按照排序的先后顺序包含在索引中,同时要注意排序是升序还是降序。如果分页语句中有过滤条件,我们要注意过滤条件是否有等值过滤条件,如果有等值过滤条件,要将等值过滤条件优先组合在一起,然后将排序列放在等值过滤条件后面,最后将非等值过滤列放排序列后面。如果分页语句中没有等值过滤条件,我们应该先将排序列放在索引前面,将非等值过滤列放后面,最后利用rownum的COUNT STOPKEY特性来优化分页SQL。如果分页中没有排序,可以直接利用rownum的COUNT STOPKEY特性来优化分页SQL。
select *
from (select *
from (select a.*, rownum rn
from (select /*+index(t_page idx_page)*/
*
from t_page
where owner = 'SYS'
order by object_id) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
由于
select *
from (select *
from (select a.*, rownum rn
from (select /*+index(t_page idx_page)*/
*
from t_page
where owner = 'SCOTT'
order by object_id) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
这是因为第一条SQL过滤条件是owner='SCOTT',owner='SCOTT'在表中只有很少数据,通过扫描object_id列的索引,然后回表再去匹配owner='SCOTT',因为owner='SCOTT'数据量少,要搜索大量数据才能匹配上。而第二条SQL的过滤条件是owner='SYS',因为owner='SYS'数据量多,只需要搜索少量数据就能匹配上。
想要优化第一条SQL,就需要让其在索引扫描的时候读取少量数据块就取得10行数据,这就需要将过滤列(owner)包含在索引中,排序列是object_id,那么现在我们创建组合索引。
TEST01@testora>create index idx_page_ownerid on t_page(owner,object_id) ;
Index created.
begin
dbms_stats.gather_table_stats(ownname => 'TEST01',
tabname => 'T_PAGE',
estimate_percent => 100,
method_opt => 'for all columns size 1',
degree => 1,
cascade => true);
end;
select *
from (select *
from (select a.*, rownum rn
from (select /*+index(t_page idx_page_ownerid)*/
*
from t_page
where owner = 'SYS'
order by object_id) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
由于
select *
from (select *
from (select a.*, rownum rn
from (select /*+index(t_page idx_page_ownerid)*/
*
from t_page
where owner = 'SCOTT'
order by object_id) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
复制
8.3.2、多表关联分页优化思路
TEST01@testora>create table t_page2 as select * from dba_objects ;
Table created.
TEST01@testora>alter session set statistics_level = all ;
select *
from (select *
from (select a.owner,
a.object_id,
a.subobject_name,
a.object_name,
rownum rn
from (select t1.owner,
t1.object_id,
t1.subobject_name,
t2.object_name
from t_page t1, t_page2 t2
where t1.object_id = t2.object_id
order by t2.object_name) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
创建索引后可得到优化后的执行计划:
TEST01@testora>create index idx_page2_name on t_page2(object_name,0) ;
Index created.
select *
from (select *
from (select a.owner,
a.object_id,
a.subobject_name,
a.object_name,
rownum rn
from (select /*+ index(t2 idx_page2_name) leading(t2) use_nl(t2,t1) */ t1.owner,
t1.object_id,
t1.subobject_name,
t2.object_name
from t_page t1, t_page2 t2
where t1.object_id = t2.object_id
order by t2.object_name) a)
where rownum <= 10)
where rn >= 1;
以上排序列 t2.object_name 来自一个列,可以消除 SORT ORDER BY ;如果排序列来自两个表,则无法消除 SORT ORDER BY ,如:
select *
from (select *
from (select a.owner,
a.object_id,
a.subobject_name,
a.object_name,
rownum rn
from (select t1.owner,
t1.object_id,
t1.subobject_name,
t2.object_name
from t_page t1, t_page2 t2
where t1.object_id = t2.object_id
order by t2.object_name, t1.subobject_name) a)
where rownum <= 10)
where rn >= 1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
复制
总结一下多表关联分页优化思路,多表关联分页语句,如果有排序,只能对其中一个表进行排序,让参与排序的表作为嵌套循环的驱动表,并且要控制驱动表返回的数据顺序与排序的顺序一致,其余表的连接列要创建好索引。如果有外连接,我们只能选择主表的列作为排序列,语句中不能有distinct、group by、max、min、avg、union、union all,执行计划中不能出现SORT ORDER BY。
8.4、使用分析函数优化自连接
ZIYOO0830@o11201g>set autotrace traceonly
select ename, deptno, sal
from scott.emp a
3 where sal = (select max(sal) from scott.emp b where a.deptno = b.deptno);
Execution Plan
----------------------------------------------------------
Plan hash value: 2649664444
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 33 | 6 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | EMP | 14 | 462 | 3 (0)| 00:00:01 |
| 3 | SORT AGGREGATE | | 1 | 26 | | |
|* 4 | TABLE ACCESS FULL| EMP | 1 | 26 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL"= (SELECT MAX("SAL") FROM "SCOTT"."EMP" "B" WHERE
"B"."DEPTNO"=:B1))
4 - filter("B"."DEPTNO"=:B1)
Note
-----
- dynamic sampling used for this statement (level=2)
以上SQL为查询每个部门工资最高的员工的所有信息,访问了EMP表两次。
使用分析函数优化SQL:
ZIYOO0830@o11201g>
select ename, deptno, sal
from (select a.*, max(sal) over(partition by deptno) max_sal
from scott.emp a)
4 where sal = max_sal;
Execution Plan
----------------------------------------------------------
Plan hash value: 4130734685
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 644 | 4 (25)| 00:00:01 |
|* 1 | VIEW | | 14 | 644 | 4 (25)| 00:00:01 |
| 2 | WINDOW SORT | | 14 | 462 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS FULL| EMP | 14 | 462 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL"="MAX_SAL")
Note
-----
- dynamic sampling used for this statement (level=2)
复制
8.5、超大表与超小表关联优化方法
ZIYOO0830@o11201g>create table b as select * from dba_objects ; ZIYOO0830@o11201g>create table b as select * from dba_objects ; ZIYOO0830@o11201g>insert into b select * from b ; ZIYOO0830@o11201g>insert into b select * from b ; ZIYOO0830@o11201g>insert into b select * from b ; ZIYOO0830@o11201g>insert into b select * from b ; ZIYOO0830@o11201g>insert into b select * from b ; 72469 rows created. select * from a,b where a.object_id = b.object_id ; 表a有30MB,表b有30GB,量表关联猴返回大量数据,应该走hash连接,a表为驱动表,b为被驱动表; 开启并行执行(小表作为广播表): explain plan for select /*+ parallel(6) use_hash(a,b) pq_distribute(a noe,broadcast) */ * from a,b where a.object_Id = b.object_id ; 以上均可以使用set autotrace traceonly查看相应的执行计划;
复制
8.6、超大表与超大表关联优化方法
ZIYOO0830@o11201g>create table c as select * from b; ZIYOO0830@o11201g>create table c as select * from b; ZIYOO0830@o11201g>insert into c select * from c ;
复制
8.7、LIKE语句优化方法
8.8、DBLINK优化
hint:driving_site 改变传输表
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。
文章被以下合辑收录
评论
怎么没看到07
5年前

评论
相关阅读
【纯干货】Oracle 19C RU 19.27 发布,如何快速升级和安装?
Lucifer三思而后行
638次阅读
2025-04-18 14:18:38
Oracle RAC 一键安装翻车?手把手教你如何排错!
Lucifer三思而后行
618次阅读
2025-04-15 17:24:06
XTTS跨版本迁移升级方案(11g to 19c RAC for Linux)
zwtian
514次阅读
2025-04-08 09:12:48
Oracle数据库一键巡检并生成HTML结果,免费脚本速来下载!
陈举超
510次阅读
2025-04-20 10:07:02
【ORACLE】记录一些ORACLE的merge into语句的BUG
DarkAthena
475次阅读
2025-04-22 00:20:37
Oracle 19c RAC更换IP实战,运维必看!
szrsu
452次阅读
2025-04-08 23:57:08
【活动】分享你的压箱底干货文档,三篇解锁进阶奖励!
墨天轮编辑部
451次阅读
2025-04-17 17:02:24
【ORACLE】你以为的真的是你以为的么?--ORA-38104: Columns referenced in the ON Clause cannot be updated
DarkAthena
448次阅读
2025-04-22 00:13:51
一页概览:Oracle GoldenGate
甲骨文云技术
447次阅读
2025-04-30 12:17:56
火焰图--分析复杂SQL执行计划的利器
听见风的声音
393次阅读
2025-04-17 09:30:30