一次排序的调整与优化--下篇

SQL优化与问题解决

SQL问题找到了,那么接下来要分析一下,这个SQL应该如何调整:

SQL> select sql_text

2 from v$sqltext_with_newlines where hash_value='2784903626' order by piece;

SQL_TEXT

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

select PRODUCTMATERIALLISTITEMIDFrom orderedmateriallist where

bomid = :"SYS_B_0"

order by DisplayPriority

此处的ORDEREDMATERIALLIST是一个视图,其创建语句如下:

SQL> select dbms_metadata.get_ddl('VIEW','ORDEREDMATERIALLIST')VIEW_SQL from dual;

VIEW_SQL

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

CREATE OR REPLACE FORCEVIEW "MESW"."ORDEREDMATERIALLIST"

("BOMID", "PRODUCTMATERIALLISTITEMID","DISPLAYPRIORITY") AS

select distinct pli.BOMID, pli.PRODUCTMATERIALLISTITEMID,pt.DISPLAYPRIORITY

from PRODUCTMATERIALLISTITEMpli, PRODUCT p, PRODUCTBASE pb, PRODUCTTYPE pt

where ((pli.PRODUCTID= p.PRODUCTID and p.PRODUCTTYPEID = pt.PRODUCTTYPEID)

or (pli.PRODUCTBASEID = pb.PRODUCTBASEID andpb.REVOFRCDID = p.PRODUCTID and

p.PRODUCTTYPEID= pt.PRODUCTTYPEID))

再来确认一下执行计划的子指针信息:

SQL> SELECT child_number,plan_hash_value, object_status

2 FROMv$sql

3 WHEREhash_value = '2784903626'

4 ORDERBY plan_hash_value

5 /

CHILD_NUMBER PLAN_HASH_VALUEOBJECT_STATUS

------------ --------------- -------------------
0 0
3 0
7 0
10 0
8 0
4 0
11 0
13 0
12 0
1 62016752 INVALID_UNAUTH
2 62016752 INVALID_UNAUTH
5 62016752 INVALID_UNAUTH
9 62016752 VALID
6 62016752 INVALID_UNAUTH

14 rows selected.

我们注意到只有一个有效的执行计划,不存在多执行计划问题,这样调整起来就较为简单了,以下是该SQL被记录的执行计划:

SQL> SELECT operation,options, object_name

2 FROMv$sql_plan

3 WHEREhash_value = '2784903626'

4 ANDchild_number = '9'

5 /

OPERATION OPTIONS OBJECT_NAME
-------------------- -------------------- ------------------------------
SELECT STATEMENT
VIEW
SORT UNIQUE
CONCATENATION
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
TABLE ACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM
INDEX RANGE SCAN PRODMATLLISTITEM1
TABLE ACCESS BY INDEX ROWID PRODUCTBASE
INDEX UNIQUE SCAN PRODUCTBASE310
TABLE ACCESS BY INDEX ROWID PRODUCT
INDEX UNIQUESCAN PRODUCT502
TABLE ACCESS BY INDEX ROWID PRODUCTTYPE
INDEX UNIQUE SCAN PRODUCTTYPE347
HASH JOIN
TABLE ACCESS FULL PRODUCTTYPE
NESTED LOOPS
NESTED LOOPS
TABLE ACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM
INDEX RANGE SCAN PRODMATLLISTITEM1
TABLE ACCESS BY INDEX ROWID PRODUCT
INDEX UNIQUE SCAN PRODUCT502
TABLE ACCESS FULL PRODUCTBASE

24 rows selected.

我们用10046来进一步跟踪,看一下具体执行的过程:

SQL> alter sessionset events '10046 trace name context forever';

Session altered.

Elapsed: 00:00:00.04

SQL> SELECT productmateriallistitemid

2 FROM(SELECT DISTINCT pli.bomid bomid,

3 pli.productmateriallistitemid productmateriallistitemid,

4 pt.displaypriority displaypriority

5 FROM productmateriallistitem pli,

6 product p,

7 productbase pb,

8 producttype pt

9 WHERE ((pli.productid = p.productidAND

10 p.producttypeid = pt.producttypeid) OR

11 (pli.productbaseid = pb.productbaseid AND

12 pb.revofrcdid = p.productid AND

13 p.producttypeid = pt.producttypeid)))

14 WHEREbomid = '0018a68000093231'

15 ORDERBY displaypriority;

PRODUCTMATERIALL

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

0017c080000b5176

0017c080000b5177

0017c080000b5178

0017c080000b517b

0017c080000b517c

0017c080000b5172

0017c080000b517d

0017c080000b519d

0017c080000b519e

0017c080000b519f

0017c080000b5171

0017c080000b5173

0017c080000b5174

0017c080000b5175

0017c080000b5179

0017c080000b517a

16 rows selected.

Elapsed: 00:00:00.83

SQL> alter sessionset events '10046 trace name context off';

Session altered.

在跟踪文件中,找到更为详尽的执行信息:

SELECT productmateriallistitemid

FROM (SELECT DISTINCT pli.bomid bomid,

pli.productmateriallistitemid productmateriallistitemid,

pt.displaypriority displaypriority

FROM productmateriallistitem pli,

product p,

productbase pb,

producttype pt

WHERE ((pli.productid = p.productid AND

p.producttypeid= pt.producttypeid) OR

(pli.productbaseid = pb.productbaseid AND

pb.revofrcdid = p.productid AND

p.producttypeid= pt.producttypeid)))

WHERE bomid = :"SYS_B_0"

ORDER BY displaypriority

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 1 5 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 3 0.64 0.63 0 3148 0 16
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 5 0.64 0.65 1 3153 0 16

Misses in library cacheduring parse: 1

Optimizer goal: CHOOSE

Parsing user id: 24

Rows Row Source Operation
------- ---------------------------------------------------
16 VIEW
16 SORT UNIQUE
251072 CONCATENATION
0 NESTED LOOPS
0 NESTED LOOPS
0 NESTED LOOPS
16 TABLE ACCESS BY INDEX ROWIDPRODUCTMATERIALLISTITEM
16 INDEX RANGE SCAN PRODMATLLISTITEM1(object id 7800)
0 TABLE ACCESS BY INDEX ROWIDPRODUCTBASE
0 INDEX UNIQUE SCAN PRODUCTBASE310(object id 7790)
0 TABLE ACCESS BY INDEX ROWIDPRODUCT
0 INDEX UNIQUE SCAN PRODUCT502(object id 7788)
0 TABLE ACCESS BY INDEX ROWIDPRODUCTTYPE
0 INDEX UNIQUE SCAN PRODUCTTYPE347(object id 7804)
251072 HASH JOIN
30 TABLE ACCESS FULL PRODUCTTYPE
251072 NESTED LOOPS
16 NESTED LOOPS
16 TABLE ACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM
16 INDEX RANGE SCAN PRODMATLLISTITEM1(object id 7800)
16 TABLE ACCESS BY INDEX ROWIDPRODUCT
16 INDEX UNIQUE SCAN PRODUCT502(object id 7788)
251072 TABLE ACCESS FULL PRODUCTBASE

********************************************************************************

alter session set events '10046 trace namecontext off'

SQL共执行了0.65秒,其中CPU工作了0.64秒。这么高的CPU使用率主要用于最后一步sort unique,因为要对251 072行代码进行排序,通过DBMS_XPLAN获得Cursor的执行计划,注意观察其后输出的谓词信息:

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

PLAN_TABLE_OUTPUT

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

| Id |Operation | Name |Rows |Bytes |Cost |
----------------------------------------------------------------------------------------
| 0 |SELECTSTATEMENT | | 18 | 882 |2489 |
| 1 |VIEW | | 18 | 882 |2489 |
| 2 | SORT UNIQUE | | 18 | 2790 | 2489 |
| 3 | CONCATENATION | | | | |
| 4 | NESTED LOOPS | | 191K | 28M | 180 |
| 5 | NESTED LOOPS | | 12 | 1212 | 17 |
| 6 | NESTED LOOPS | |191K | 24M | 173 |
| 7 | TABLE ACCESS BY INDEX ROWID |PRODUCTMATERIALLISTITEM | 12 | 816 | 5 |
|* 8 | INDEX RANGE SCAN |PRODMATLLISTITEM1 | 12 | | 3 |
| 9 | TABLE ACCESS BY INDEX ROWID |PRODUCTBASE |15611 | 518K | 13 |
|* 10 | INDEX UNIQUE SCAN |PRODUCTBASE310 | | | |
| 11 | TABLE ACCESS BY INDEX ROWID |PRODUCT | 1 | 33 | 1 |
|* 12 | INDEX UNIQUE SCAN |PRODUCT502 | 1 | | |
| 13 | TABLE ACCESS BY INDEX ROWID |PRODUCTTYPE | 30 | 600 | 5 |
|* 14 | INDEX UNIQUE SCAN |PRODUCTTYPE347 | 1 | | |
|* 15 | HASH JOIN | | 191K | 28M | 180 |
| 16 | TABLE ACCESS FULL |PRODUCTTYPE | 30 | 600 | 5 |
| 17 | NESTED LOOPS | | 191K | 24M | 173 |
| 18 | NESTED LOOPS | | 12 | 1212 | 17 |
| 19 | TABLE ACCESS BY INDEX ROWID |PRODUCTMATERIALLISTITEM | 12 | 816 | 5 |
|* 20 | INDEX RANGE SCAN |PRODMATLLISTITEM1 | 12 | | 3 |
| 21 | TABLE ACCESS BY INDEX ROWID |PRODUCT | 1 | 33 | 1 |
|* 22 | INDEX UNIQUE SCAN |PRODUCT502 | 1 | | |
|* 23 | TABLE ACCESS FULL |PRODUCTBASE |15611 | 518K | 13 |
----------------------------------------------------------------------------------------

PredicateInformation (identified by operation id):

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

8 - access("PLI"."BOMID"=:Z)

10 -access("PLI"."PRODUCTBASEID"="PB"."PRODUCTBASEID")

12 -access("PB"."REVOFRCDID"="P"."PRODUCTID")

14 -access("P"."PRODUCTTYPEID"="PT"."PRODUCTTYPEID")

15 -access("P"."PRODUCTTYPEID"="PT"."PRODUCTTYPEID")

20 -access("PLI"."BOMID"=:Z)

22 -access("PLI"."PRODUCTID"="P"."PRODUCTID")

23 -filter(LNNVL("PLI"."PRODUCTBASEID"="PB"."PRODUCTBASEID")OR

LNNVL("PB"."REVOFRCDID"="P"."PRODUCTID"))

Note: cpu costing is off

结合10046的跟踪输出及执行计划的谓词信息,就可以看到问题所在了。执行计划的第23步执行了一个过滤(LNNVL函数在9i中没有文档记录,请参考10gR2的Oracle Database SQL Reference):

23 - filter(LNNVL("PLI"."PRODUCTBASEID"="PB"."PRODUCTBASEID")OR
LNNVL("PB"."REVOFRCDID"="P"."PRODUCTID"))

10046这个步骤的结果集由NestLoop产生并一直向上运算传递到顶层:

251072 HASHJOIN
30 TABLEACCESS FULL PRODUCTTYPE
251072 NESTEDLOOPS
16 NESTEDLOOPS
16 TABLEACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM
16 INDEXRANGE SCAN PRODMATLLISTITEM1 (object id 7800)
16 TABLEACCESS BY INDEX ROWID PRODUCT
16 INDEXUNIQUE SCAN PRODUCT502 (object id 7788)
251072 TABLEACCESS FULL PRODUCTBASE

这段执行计划用于实现的是如下谓词约束:

WHERE ((pli.productid = p.productid AND
p.producttypeid = pt.producttypeid)

前面SQL通过filter来过滤掉PRODUCTBASE的连接两个条件,PRODUCTBASE全表 15.6k行和前面的16行结果集作nested loops,就会出现16×15.6k=251k行的结果集,这么大的结果集排序所耗费的CPU会很高。

把查询用union改写:

SELECT productmateriallistitemid

FROM (SELECT DISTINCT pli.bomid,

pli.productmateriallistitemid,

pt.displaypriority

FROM productmateriallistitem pli,

product p,

--productbase pb,

producttype pt

WHERE ((pli.productid= p.productid AND

p.producttypeid= pt.producttypeid)

--OR

--(pli.productbaseid= pb.productbaseid AND pb.revofrcdid = p.productid AND p.producttypeid = pt.producttypeid)

)

UNION

SELECT DISTINCT pli.bomid,

pli.productmateriallistitemid,

pt.displaypriority

FROM productmateriallistitem pli,

product p,

productbase pb,

producttype pt

WHERE (

--(pli.productid = p.productid AND p.producttypeid = pt.producttypeid)

--OR

(pli.productbaseid= pb.productbaseid AND

pb.revofrcdid = p.productid AND

p.producttypeid = pt.producttypeid)))

WHERE bomid = :"SYS_B_0"

ORDER BY displaypriority

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 3 0.01 0.00 0 80 0 16

------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 5 0.02 0.00 0 80 0 16

Misses in library cacheduring parse: 1

Optimizer goal: CHOOSE

Parsing user id: 24

Rows Row SourceOperation

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

16 SORTORDER BY

16 VIEW

16 SORTUNIQUE

16 UNION-ALL

16 NESTED LOOPS

16 NESTED LOOPS

16 TABLE ACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM

16 INDEX RANGE SCAN PRODMATLLISTITEM1 (object id 7800)

16 TABLE ACCESS BY INDEX ROWID PRODUCT

16 INDEX UNIQUE SCAN PRODUCT502 (object id 7788)

16 TABLE ACCESS BY INDEX ROWID PRODUCTTYPE

16 INDEX UNIQUE SCAN PRODUCTTYPE347 (object id 7804)

0 NESTED LOOPS

0 NESTED LOOPS

0 NESTED LOOPS

16 TABLE ACCESS BY INDEX ROWID PRODUCTMATERIALLISTITEM

16 INDEX RANGE SCAN PRODMATLLISTITEM1 (object id 7800)

0 TABLE ACCESS BY INDEX ROWID PRODUCTBASE

0 INDEX UNIQUE SCAN PRODUCTBASE310 (object id 7790)

0 TABLE ACCESS BY INDEX ROWID PRODUCT

0 INDEX UNIQUE SCAN PRODUCT502 (object id 7788)

0 TABLE ACCESS BY INDEX ROWID PRODUCTTYPE

0 INDEX UNIQUE SCAN PRODUCTTYPE347 (object id 7804)

********************************************************************************

alter session set events '10046 trace namecontext off'

分割成两个SQL作union后避免了filter减少了须排序的集,同时也减少了逻辑读(其中的distinct可以从SQL中去掉,因为union本身就有distinct的作用,但是为了阅读方便,保留这个写法)。

文章来源:《OracleDBA手记1》第19章 一次排序的调整与优化 作者:段林仲

配图来源:http://rioit.co.uk/sites/default/files/remote-IT-support-figure_710x396.jpg

回复“手记1”可下载本书前三篇内容:DBA工作手记/诊断案例篇/SQL调优篇(建议复制链接至电脑端)

20727f0de104aaa2b3081196279bdf04.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值