2013 年一唯品会的朋友有如下语句需要优化。
select *from (select f.*from tms.inf_b2c_djwlzt_f finner join tms.orderstatus os on f.transport_code = os.statuscodewhere f.warehouse = 'VIP_BJ'and f.is_send = 0order by f.created_dtm_loc, os.Sort_No asc)where rownum <= 500;
该 SQL 类似分页语句,因此我们可以用分页语句优化思路对其进行优化。拿到分页语句,我们首先应该检查分页语句是否符合分页语句编写规范。这里该 SQL 排序列来自两个表,不符合分页语句编写规范。我们在第 8 章中讲到,分页语句只能对一个表的列进行排序。该 SQL 排序列来自 f 和 os,并且显示的时候只有 f 表的数据。因此我们建议去掉 os 表的排序字段,如下所示。
select *from (select f.*from tms.inf_b2c_djwlzt_f finner join tms.orderstatus os on f.transport_code = os.statuscodewhere f.warehouse = 'VIP_BJ'and f.is_send = 0order by f.created_dtm_loc)where rownum <= 500;
排序列来自 f 表,需要对 f 表创建索引,因为过滤条件是等值访问,我们可以把过滤条件放在前面,排序列放在后面,于是创建如下索引。
create index idx_f_inf on inf_b2c_djwlzt_f(warehouse,is_send,created_dtm_loc);
然后强制 f 表与 os 走嵌套循环,同时让 f 表作为嵌套循环驱动表,走刚才创建的索引。
select *from (select /*+ use_nl(f,os) leading(f) */f.*from tms.inf_b2c_djwlzt_f finner join tms.orderstatus os on f.transport_code = os.statuscodewhere f.warehouse = 'VIP_BJ'and f.is_send = 0order by f.created_dtm_loc)where rownum <= 500;
执行计划如下。
-------------------------------------------------------------------------------------| Id |Operation | Name | Rows | Bytes | Cost(%CPU)|-------------------------------------------------------------------------------------| 0 |SELECT STATEMENT | | 500 | 725K| 754 (1)||* 1 | COUNT STOPKEY | | | | || 2 | VIEW | | 502 | 728K| 754 (1)|| 3 | NESTED LOOPS | | 502 | 121K| 754 (1)|| 4 | TABLE ACCESS BY INDEX ROWID| INF_B2C_DJWLZT_F | 2419K| 562M| 71 (0)||* 5 | INDEX RANGE SCAN | IDX_F_INF | 502 | | 5 (0)||* 6 | TABLE ACCESS FULL | ORDERSTATUS | 1 | 3 | 1 (0)|-------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1 - filter(ROWNUM<=500)5 - access("F"."WAREHOUSE"='VIP_BJ' AND "F"."IS_SEND"=0)6 - filter("F"."TRANSPORT_CODE"="OS"."STATUSCODE")Statistics----------------------------------------------------------1 recursive calls0 db block gets1736 consistent gets2 physical reads0 redo size67968 bytes sent via SQL*Net to client883 bytes received via SQL*Net from client35 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)500 rows processed
从执行计划中我们看到被驱动表走了全表扫描,嵌套循环被驱动表不能走全表扫描,必须走索引,于是创建如下索引。
create index STATUSCODE_IDX on ORDERSTATUS(STATUSCODE);
创建索引之后的执行计划如下。
-------------------------------------------------------------------------------------| Id |Operation | Name | Rows | Bytes | Cost(%CPU)|-------------------------------------------------------------------------------------| 0 |SELECT STATEMENT | | 500 | 725K| 71 (0)||* 1 | COUNT STOPKEY | | | | || 2 | VIEW | | 502 | 728K| 71 (0)|| 3 | NESTED LOOPS | | 502 | 121K| 71 (0)|| 4 | TABLE ACCESS BY INDEX ROWID| INF_B2C_DJWLZT_F | 2419K| 562M| 71 (0)||* 5 | INDEX RANGE SCAN | IDX_F_INF | 502 | | 5 (0)||* 6 | INDEX RANGE SCAN | STATUSCODE_IDX | 1 | 3 | 0 (0)|-------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1 - filter(ROWNUM<=500)5 - access("F"."WAREHOUSE"='VIP_BJ' AND "F"."IS_SEND"=0)6 - access("F"."TRANSPORT_CODE"="OS"."STATUSCODE")Statistics----------------------------------------------------------1 recursive calls0 db block gets247 consistent gets0 physical reads0 redo size60433 bytes sent via SQL*Net to client883 bytes received via SQL*Net from client35 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)500 rows processed
优化完毕之后,该 SQL 逻辑读只有 247 个,最终该 SQL 可以秒杀。
