|
你得承认,oracle的优化器还是存在一些个问题的.但更多时候,优化器选择了糟糕的执行计划,其实都是我们自己造成的.我觉得主要存在以下几点因素:
1.优化器统计信息陈旧或者是我们收集优化器统计信息的方法不对.比如说我上面提到的该收集柱状图统计信息的时候没有收集,或者是不该收集的时候收集了柱状图统计信息等等!
2.缺少必要的约束信息.你只把数据堆放在那里,不告诉ORACLE其它任何的信息,你就指望它给你选择一个最优的执行计划?!它毕竟是人而不是神.当然它也越来越神化了.比如说10G的时候,gather_stats_job会自动为你收集陈旧的统计信息(即使你以前没有任何的收集统计信息的JOB.当然你可以根据自己的实际情况停掉这个JOB,只根据实际需要手工的收集).optimizer_dynamic_sampling的默认值也由1变成了2,这样对于刚导入的数据没有收集优化器统计信息的,绝大多数时候它会动态采样,而不再使用内部的默认值了.当然我觉得也是因为这样一些个改进,10g的时候,optimizer_mode的默认值由choose变成了all_rows.在优化器统计信息收集这一块儿,它主动的替我们做了很多的工作(虽然说还是存在一些个问题的),但在约束这一块儿,它却不能替我们做任何的决定.一个有1000W行数据的表的字段A上,在收集统计信息的时候,发现它不存在任何的NULL值,但你的表定义中这个字段是NULL的,那么它就必须按这个字段是可以为NULL的去处理,而不能替我们做出这样的决定:这个字段是NOT NULL的,它绝不会这样做的.但假如说实际上这个字段确实是不会为NULL的,你却没有NOT NULL的约束限制,那么有些可选的执行计划,ORACLE也只能放弃掉了:比如说因为没有NOT NULL的约束限制,有些本可以通过索引扫描避免的排序操作也只能放弃,而选择排序操作了.比如说NOT IN操作,本来可以ANTI JOIN的,它也只能放弃,而选择有些时候并不好的FILTER操作了,所以很多时候,我们在遇到not in的时候,使用not exists,外连接+判断为NULL这样一些个替代方案,not in被放弃了,有时候人们也纳闷:为什么not in这种很符合人的思维习惯的标准SQL,oracle却做的这么不好呢?我们没有意识到,因为缺少必要的约束限制,oracle只能放弃一些个转换,因为它们不是等价的,但实际上它们是等价的,因为这样的约束限制其实就数据本身来说是存在的,只是我们没有通过表定义告诉数据库而已.
3.oracle有时候还不足够智能(虽说它已经很智能了),有些我们的思维习惯它还理解不了,所以我们需要去理解它的思维习惯,去适应它的这种思维习惯,尽量按照它可以理解的方式去写SQL.比如说这样一个例子:
product表中id是主键,productaverpoint表中productid也是主键,product表的pageview字段是可以为空的,pageview上有一个单列索引
select * from
(
select tt.*,rownum n from
(
select p.image1,p.id,p.productname,p.pageview,p.brandname,p.brandid,
nvl(a.appearpoint,0) as appearpoint,pcount as pcount
from product p
left join productaverpoint a on a.productid=p.id
left join (select productid, count(productid) pcount from productpic group by productid) pic on p.id = pic.productid
where p.pageview is not null
order by p.pageview desc
) tt
)
where n>0 and n<=15;
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 254K| 75M| | 20585 (1)| 00:04:08 |
|* 1 | VIEW | | 254K| 75M| | 20585 (1)| 00:04:08 |
| 2 | COUNT | | | | | | |
| 3 | VIEW | | 254K| 71M| | 20585 (1)| 00:04:08 |
| 4 | SORT ORDER BY | | 254K| 36M| 77M| 20585 (1)| 00:04:08 |
|* 5 | HASH JOIN RIGHT OUTER | | 254K| 36M| 2576K| 12201 (2)| 00:02:27 |
| 6 | VIEW | | 90779 | 1507K| | 2298 (4)| 00:00:28 |
| 7 | HASH GROUP BY | | 90779 | 354K| 15M| 2298 (4)| 00:00:28 |
| 8 | INDEX FAST FULL SCAN| IDX_P_PIC_PID | 1225K| 4789K| | 832 (2)| 00:00:10 |
|* 9 | HASH JOIN RIGHT OUTER | | 254K| 32M| 4448K| 8038 (1)| 00:01:37 |
| 10 | TABLE ACCESS FULL | PRODUCTAVERPOINT | 252K| 1480K| | 204 (2)| 00:00:03 |
|* 11 | TABLE ACCESS FULL | PRODUCT | 254K| 30M| | 5950 (1)| 00:01:12 |
--------------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
40405 consistent gets
0 physical reads
0 redo size
3036 bytes sent via SQL*Net to client
488 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
15 rows processed
product 用来关联的字段ID是主键,productaverpoint用来关联的字段productid是主键,
(select productid, count(productid) pcount from productpic group by productid) pic 对视图pic来说,productid又可以当作主键来使用.
而且又都是左外连接,order by p.pageview desc只是根据一个表的字段来进行排序,而且是取前n行数据,
所以,你当然可以像下面这样改写:
select p.image1,p.id,p.productname,p.pageview,p.brandname,p.brandid,
nvl(a.appearpoint, 0) as appearpoint,
(select count(productid) pcount from productpic where productid=p.id) pcount
from
(
select prowid,n
from
(
select prowid,rownum n from
(
select rowid prowid from product p where p.pageview is not null order by p.pageview desc
)
where rownum<=15
) where n>0
) ptemp,product p,productaverpoint a
where ptemp.prowid=p.rowid and p.id=a.productid(+)
order by n;
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 2535 | 520 (1)| 00:00:07 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
|* 2 | INDEX RANGE SCAN | IDX_P_PIC_PID | 14 | 56 | 2 (0)| 00:00:01 |
| 3 | SORT ORDER BY | | 15 | 2535 | 520 (1)| 00:00:07 |
| 4 | NESTED LOOPS OUTER | | 15 | 2535 | 519 (1)| 00:00:07 |
| 5 | NESTED LOOPS | | 15 | 2445 | 512 (1)| 00:00:07 |
|* 6 | VIEW | | 15 | 375 | 497 (1)| 00:00:06 |
|* 7 | COUNT STOPKEY | | | | | |
| 8 | VIEW | | 254K| 2982K| 497 (1)| 00:00:06 |
|* 9 | INDEX FULL SCAN DESCENDING| INDEX_PRODUCT_PAGEVIEW | 254K| 3728K| 497 (1)| 00:00:06 |
| 10 | TABLE ACCESS BY USER ROWID | PRODUCT | 1 | 138 | 1 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID | PRODUCTAVERPOINT | 1 | 6 | 1 (0)| 00:00:01 |
|* 12 | INDEX UNIQUE SCAN | PK_PRODUCTAVERPOINT | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
94 consistent gets
0 physical reads
0 redo size
2918 bytes sent via SQL*Net to client
488 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
15 rows processed
但问题在于,我必要的约束都是存在的,其实完全可以product表按pageview排序后取前n行,然后再和其他表关联的,但为什么oracle没有这样做呢?
其实对于第一个SQL语句稍微改写一下就可以:
select * from
(
select tt.*,rownum n from
(
select p.image1,
p.id,p.productname,p.pageview,p.brandname,p.brandid,
nvl(a.appearpoint,0) as appearpoint,
(select count(productid) pcount from productpic where productid=p.id) pcount
from product p
left join productaverpoint a on a.productid=p.id
where p.pageview is not null
order by p.pageview desc
) tt
where rownum<=15
)
where n>0;
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 4635 | 79 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
|* 2 | INDEX RANGE SCAN | IDX_P_PIC_PID | 14 | 56 | 2 (0)| 00:00:01 |
|* 3 | VIEW | | 15 | 4635 | 79 (0)| 00:00:01 |
|* 4 | COUNT STOPKEY | | | | | |
| 5 | VIEW | | 15 | 4440 | 79 (0)| 00:00:01 |
| 6 | NESTED LOOPS OUTER | | 15 | 1980 | 79 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| PRODUCT | 254K| 30M| 72 (0)| 00:00:01 |
|* 8 | INDEX FULL SCAN DESCENDING| INDEX_PRODUCT_PAGEVIEW | 15 | | 2 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID| PRODUCTAVERPOINT | 1 | 6 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | PK_PRODUCTAVERPOINT | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
94 consistent gets
0 physical reads
0 redo size
3036 bytes sent via SQL*Net to client
488 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
15 rows processed
这个执行计划和第二个语句的执行计划看起来有些不太一样,其实你从运行的统计信息来看,它们的执行计划是完全相同的.但它和第一个SQL语句又有什么不一样的呢?
其实我只是把where n>0 and n<=15 改写了一下,让oracle可以进行stopkey操作了而已. |
|