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

[Oracle 9i] Subquery Factoring in 9i (用With语句做公共子查询提取)

2010-01-05 11:27 405 查看
SubqueryFactoring,其实就是平常比较熟悉的With语句,AdrianBillington在他的网站上写了一篇介绍SubqueryFactoring很好的文章,见这里。这篇Blog同样是对他的这篇文章的笔记。

With语句一般有两种用途,一种就是用来把复杂的SQL语句简单化:复杂的SQL语句一般都会嵌套很多层次,无论是写起来还是读起来都很困难,通过用With语句,把子查询语句抽取出来,这样可以使得SQL语句“扁平化”,写起来会很方便,读起来也很容易。With的这种用法其实就是相当于把复杂语句中的inline-view给提取出来作为一个单独的查询语句,并赋予一个名字,这样用起来就跟访问一个视图一样。

With的另外一种用处是可以用来优化SQL语句,如果一个复杂的SQL语句需要重复访问一张表(最好是数据量比较大的表),这个时候如果用With把这部分需要重复访问底层表的SQL语句提取出来(Oracle往往会把这部分数据“物化”到一个临时表中),之后就不用重复多次访问底层表,从而可以提升SQL语句的执行效率。

下面主要看看With在SQL优化方面的一个例子,(注意,我得到的结果跟Adrian很不一样,我用的是10g来run他的例子的!)

首先创建测试用例:

[code]SQL>showuser
USERis"FRANK"
SQL>CREATETABLEsales
2NOLOGGING
3AS
4SELECTal.ownerASregion
5,al.object_typeASproduct
6,al.object_idASorder_amt
7FROMall_objectsal
8,all_objectsa2
9WHEREROWNUM<=1000000;
Tablecreated.
SQL>execDBMS_STATS.GATHER_TABLE_STATS(USER,'sales');
PL/SQLproceduresuccessfullycompleted.
[/code]

首先看看不用With语句的效果,


[code]SQL>setautotraceon
SQL>settimingon
SQL>
SQL>SELECTregion
2,total_sales
3FROM(
4SELECTregion
5,NVL(SUM(order_amt),0)AStotal_sales
6FROMsales
7GROUPBY
8region
9)ilv
10WHEREtotal_sales>(SELECTSUM(order_amt)/3ASone_third_sales
11FROMsales);
REGIONTOTAL_SALES
-----------------------------------------
SYS27760438
Elapsed:00:00:01.68
ExecutionPlan
----------------------------------------------------------
Planhashvalue:302444457
-----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|7|753(26)|00:00:10|
|*1|FILTER||||||
|2|HASHGROUPBY||1|7|753(26)|00:00:10|
|3|TABLEACCESSFULL|SALES|1006K|6877K|597(7)|00:00:08|
|4|SORTAGGREGATE||1|3|||
|5|TABLEACCESSFULL|SALES|1006K|2947K|597(7)|00:00:08|
-----------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter(NVL(SUM("ORDER_AMT"),0)>(SELECTSUM("ORDER_AMT")/3FROM
"SALES""SALES"))
Statistics
----------------------------------------------------------
1recursivecalls
0dbblockgets
5034consistentgets
18physicalreads
0redosize
477bytessentviaSQL*Nettoclient
396bytesreceivedviaSQL*Netfromclient
2SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
1rowsprocessed
SQL>
[/code]

从执行计划可以看到,确实出现了两次访问底层表sales!

这条SQL语句的执行时间是1.68秒,cost是753,物理I/O为18.(这跟Adrian的测试结果差了很多,可能是由于数据量的问题)

然后再看看用With语句把重复访问表的部分抽出来作为临时表的效果,


[code]SQL>WITHregion_salesAS
2(SELECTregion
3,NVL(SUM(order_amt),0)AStotal_sales
4FROMsales
5GROUPBY
6region
7)
8SELECTregion
9,total_sales
10FROMregion_sales
11WHEREtotal_sales>(SELECTSUM(total_sales)/3ASone_third_sales
12FROMregion_sales);
REGIONTOTAL_SALES
-----------------------------------------
SYS27760438
Elapsed:00:00:02.26
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1988045888
--------------------------------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
--------------------------------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|30|757(26)|00:00:10|
|1|TEMPTABLETRANSFORMATION||||||
|2|LOADASSELECT||||||
|3|HASHGROUPBY||1|7|753(26)|00:00:10|
|4|TABLEACCESSFULL|SALES|1006K|6877K|597(7)|00:00:08|
|*5|VIEW||1|30|2(0)|00:00:01|
|6|TABLEACCESSFULL|SYS_TEMP_0FD9D6612_10B3A7|1|7|2(0)|00:00:01|
|7|SORTAGGREGATE||1|13|||
|8|VIEW||1|13|2(0)|00:00:01|
|9|TABLEACCESSFULL|SYS_TEMP_0FD9D6612_10B3A7|1|7|2(0)|00:00:01|
--------------------------------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
5-filter("TOTAL_SALES">(SELECTSUM("TOTAL_SALES")/3FROM(SELECT/*+CACHE_TEMP_TABLE
("T1")*/"C0""REGION","C1""TOTAL_SALES"FROM"SYS"."SYS_TEMP_0FD9D6612_10B3A7""T1")
"REGION_SALES"))
Statistics
----------------------------------------------------------
102recursivecalls
11dbblockgets
2540consistentgets
43physicalreads
1472redosize
477bytessentviaSQL*Nettoclient
396bytesreceivedviaSQL*Netfromclient
2SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
1rowsprocessed
SQL>
[/code]

从执行计划可以看出,这次是访问一次底层表(创建临时表),然后是两次对临时表的访问(代价很明显小多了)

时间花销是2.26秒,代价是757,物理I/O为43,每项数据都比不用With要多!(这个跟Adrian的差得太远了!)这也说明了一点,With语句并不一定会提升性能,从With语句的执行计划中可以看出有一步叫TempTableTransformation,Oracle会创建一个globaltemporarytable,并从底层表sales装载数据到这个临时表中,这些从执行计划都可以看到。这一步带来的代价是很高的(753),所以如果直接访问底层多次的代价比创建临时表的代价还要小的话,用With语句就没有多大价值了。相反,如果底层表的数据量很大,多次访问地层表的代价就很可能超过一次创建临时表的代价,这样With语句的优势就显现出来了!

还有一点要注意的是,用With语句并不代表Oracle一定会创建临时表,因为查询优化器很“聪明”,它会分析值不值得这么做。这个时候,如果我们想明确告诉Oracle,我们需要它来创建这样一个临时表,我们可以用一个hint–materialize.但是由于这是一个没有官方文档说明的hint,所以很难保证这个hint会一直起作用,所以用这个hint也要很小心才行!


关于With语句用法的一些限制性,Adrian说得很清楚,可以参考他的文章。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: