今天在餐饮前端库抓出一条跑的很慢的分页语句,分页语句里没有对主表group by 这些禁忌,但是还跑6秒才跑完,逻辑读大概有 735634个buffers,立马给优化了一下。
SQL 语句如下:
SELECT *
FROM (SELECT (SELECT COUNT(1)
FROM cy_product_sku ps
WHERE ps.product_code = pc.product_code
AND ps.isdel = 0) AS skuNum,
pc.version,
pc.product_code,
pc.product_name,
ps.price,
pc.gross_weight,
pc.quantity,
pc.up_time,
pb.name AS ppName,
f_getcategory(pca.id) AS flName,
ps.product_num,
pt.name AS lxName,
pc.MAINIMGURL
FROM cy_product_code pc
LEFT JOIN cy_product_brand pb
ON pc.brand_id = pb.id
LEFT JOIN cy_product_category pca
ON pca.id = pc.category_id
LEFT JOIN cy_product_type pt
ON pc.type_id = pt.id
LEFT JOIN cy_product_sku ps
ON ps.product_code = pc.product_code
WHERE pc.isup = 1
AND pc.isdel = 0
AND ps.product_sku =
(SELECT MIN(product_sku)
FROM cy_product_sku
WHERE DEFAULT_SKU IN (2, 3)
AND isdel = 0
AND product_code = pc.product_code
AND price = (SELECT MIN(price)
FROM cy_product_sku
WHERE DEFAULT_SKU IN (2, 3)
AND isdel = 0
AND product_code = pc.product_code
GROUP BY PRODUCT_CODE)
GROUP BY product_code)
ORDER BY pc.up_time DESC)
WHERE rownum <= 10
执行计划为:
------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2892 | 2663 (1)| 00:00:32 |
| 1 | SORT AGGREGATE | | 1 | 12 | | |
|* 2 | TABLE ACCESS FULL | CY_PRODUCT_SKU | 2 | 24 | 10 (0)| 00:00:01 |
|* 3 | COUNT STOPKEY | | | | | |
| 4 | VIEW | | 1 | 2892 | 2663 (1)| 00:00:32 |
|* 5 | SORT ORDER BY STOPKEY | | 1 | 180 | 2663 (1)| 00:00:32 |
|* 6 | FILTER | | | | | |
|* 7 | HASH JOIN | | 1644 | 288K| 195 (0)| 00:00:03 |
| 8 | TABLE ACCESS FULL | CY_PRODUCT_SKU | 2027 | 77026 | 10 (0)| 00:00:01 |
|* 9 | HASH JOIN RIGHT OUTER | | 776 | 107K| 185 (0)| 00:00:03 |
| 10 | TABLE ACCESS FULL | CY_PRODUCT_CATEGORY | 532 | 2128 | 4 (0)| 00:00:01 |
|* 11 | HASH JOIN RIGHT OUTER | | 776 | 104K| 181 (0)| 00:00:03 |
| 12 | TABLE ACCESS FULL | CY_PRODUCT_BRAND | 35 | 455 | 4 (0)| 00:00:01 |
|* 13 | HASH JOIN RIGHT OUTER | | 776 | 97000 | 177 (0)| 00:00:03 |
| 14 | TABLE ACCESS FULL | CY_PRODUCT_TYPE | 31 | 496 | 4 (0)| 00:00:01 |
|* 15 | TABLE ACCESS FULL | CY_PRODUCT_CODE | 776 | 84584 | 173 (0)| 00:00:03 |
| 16 | SORT GROUP BY NOSORT | | 1 | 35 | 3 (0)| 00:00:01 |
|* 17 | TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_SKU | 1 | 35 | 3 (0)| 00:00:01 |
|* 18 | INDEX RANGE SCAN | IDX_CY_PRO_SK_ISD_PRICE | 4 | | 1 (0)| 00:00:01 |
| 19 | SORT GROUP BY NOSORT | | 1 | 19 | 10 (0)| 00:00:01 |
|* 20 | TABLE ACCESS FULL | CY_PRODUCT_SKU | 1 | 19 | 10 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("PS"."PRODUCT_CODE"=:B1 AND "PS"."ISDEL"=0)
3 - filter(ROWNUM<=10)
5 - filter(ROWNUM<=10)
6 - filter("PS"."PRODUCT_SKU"= (SELECT MIN("PRODUCT_SKU") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU"
WHERE "PRICE"= (SELECT MIN("PRICE") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU" WHERE "PRODUCT_CODE"=:B1
AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0 GROUP BY "PRODUCT_CODE") AND "ISDEL"=0 AND
"PRODUCT_CODE"=:B2 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) GROUP BY "PRODUCT_CODE"))
7 - access("PS"."PRODUCT_CODE"="PC"."PRODUCT_CODE")
9 - access("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))
11 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))
13 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
15 - filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
17 - filter("PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3))
18 - access("ISDEL"=0 AND "PRICE"= (SELECT MIN("PRICE") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU"
WHERE "PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0 GROUP BY
"PRODUCT_CODE"))
20 - filter("PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)
可以看到这个SQL 有三个问题
(1) select 里面有一个标量子查询 CY_PRODUCT_SKU 表将会扫描N次
(2) 这个分页框架是有问题的,这种分页框架,将会将所有的数据全部查询出来以后,然后order by 排序,最后一步再取前10行,也进而导致了 标量子查询里面的表被扫描N多次。
(3)执行计划里面有一个FILTER,而FILTER的第二个儿子,即ID=16往下的部分,将会被执行N多次,从SQL中看出,引起FILTER的原因就是这一段:
原SQL 里面的部分是:
SELECT MIN(product_sku)
FROM cy_product_sku ps1
WHERE ps1.DEFAULT_SKU IN (2, 3)
AND ps1.isdel = 0
AND product_code = pc.product_code -----和外面的pc 表根据product_code 关联 先去掉
AND ps1.price = (SELECT MIN(ps2.price)
FROM cy_product_sku ps2
WHERE ps2.DEFAULT_SKU IN (2, 3)
AND ps2.isdel = 0
AND ps2.product_code = ps1.product_code
GROUP BY ps2.PRODUCT_CODE)
GROUP BY ps1.product_code
需求就是:访问 cy_product_sku 根据 product_code 分组,求出 每组 product_code 里面price 最小的 product_sku ,如果有两个不同的 product_sku 有相同的最低price 那么就取 product_sku 最小的那个。取完以后 拿 各组的 product_code,product_sku 和外面的进行关联。
等价的意思就是:
SELECT MIN(product_sku), ps1.product_code
FROM cy_product_sku ps1
WHERE ps1.DEFAULT_SKU IN (2, 3)
AND ps1.isdel = 0
AND ps1.price = (SELECT MIN(ps2.price)
FROM cy_product_sku ps2
WHERE ps2.DEFAULT_SKU IN (2, 3)
AND ps2.isdel = 0
AND ps2.product_code = ps1.product_code
GROUP BY ps2.PRODUCT_CODE)
GROUP BY ps1.product_code
等价改写:
select product_sku, product_code
from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
product_sku,
product_code
FROM cy_product_sku
WHERE DEFAULT_SKU IN (2, 3)
AND isdel = 0) RS
WHERE RS.ROW_NUM = 1;
至此,问题3被搞定了。
下面搞标量子查询:
由于select 里面还有个标量子查询,是对ps表的,而ps表本身就在left join里面因此 ps 表等价改成:
(select product_code,
product_sku,
price,
product_num,
COUNT(*) OVER(PARTITION BY product_code) cnt
from cy_product_sku
where isdel = 0) v_ps
原SQL整体就被等价改为:
SELECT *
FROM (SELECT v_ps.cnt AS skuNum,
pc.version,
pc.product_code,
pc.product_name,
v_ps.price,
pc.gross_weight,
pc.quantity,
pc.up_time,
pb.name AS ppName,
f_getcategory(pca.id) AS flName,
v_ps.product_num,
pt.name AS lxName,
pc.MAINIMGURL
FROM cy_product_code pc
LEFT JOIN cy_product_brand pb
ON pc.brand_id = pb.id
LEFT JOIN cy_product_category pca
ON pca.id = pc.category_id
LEFT JOIN cy_product_type pt
ON pc.type_id = pt.id
LEFT JOIN (select product_code,
product_sku,
price,
product_num,
COUNT(*) OVER(PARTITION BY product_code) cnt
from cy_product_sku
where isdel = 0) v_ps
ON v_ps.product_code = pc.product_code
inner join (select product_sku, product_code
from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
product_sku,
product_code
FROM cy_product_sku
WHERE DEFAULT_SKU IN (2, 3)
AND isdel = 0) RS
WHERE RS.ROW_NUM = 1) v_tab
on pc.product_code = v_tab.product_code
and v_ps.product_sku = v_tab.product_sku
WHERE pc.isup = 1
AND pc.isdel = 0
ORDER BY pc.up_time DESC)
WHERE rownum <= 10
下面看一下新SQL的执行计划:
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2892 | 205 (2)| 00:00:03 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 1 | 2892 | 205 (2)| 00:00:03 |
|* 3 | SORT ORDER BY STOPKEY | | 1 | 483 | 205 (2)| 00:00:03 |
|* 4 | HASH JOIN OUTER | | 1 | 483 | 204 (1)| 00:00:03 |
| 5 | NESTED LOOPS OUTER | | 1 | 479 | 200 (1)| 00:00:03 |
|* 6 | HASH JOIN OUTER | | 1 | 466 | 199 (2)| 00:00:03 |
|* 7 | HASH JOIN | | 1 | 450 | 195 (2)| 00:00:03 |
|* 8 | HASH JOIN | | 807 | 115K| 184 (1)| 00:00:03 |
|* 9 | VIEW | | 995 | 37810 | 11 (10)| 00:00:01 |
|* 10 | WINDOW SORT PUSHED RANK| | 995 | 34825 | 11 (10)| 00:00:01 |
|* 11 | TABLE ACCESS FULL | CY_PRODUCT_SKU | 995 | 34825 | 10 (0)| 00:00:01 |
|* 12 | TABLE ACCESS FULL | CY_PRODUCT_CODE | 776 | 84584 | 173 (0)| 00:00:03 |
| 13 | VIEW | | 1831 | 541K| 11 (10)| 00:00:01 |
| 14 | WINDOW SORT | | 1831 | 75071 | 11 (10)| 00:00:01 |
|* 15 | TABLE ACCESS FULL | CY_PRODUCT_SKU | 1831 | 75071 | 10 (0)| 00:00:01 |
| 16 | TABLE ACCESS FULL | CY_PRODUCT_TYPE | 31 | 496 | 4 (0)| 00:00:01 |
| 17 | TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_BRAND | 1 | 13 | 1 (0)| 00:00:01 |
|* 18 | INDEX UNIQUE SCAN | CY_PRODUCT_BRAND_PK | 1 | | 0 (0)| 00:00:01 |
| 19 | TABLE ACCESS FULL | CY_PRODUCT_CATEGORY | 532 | 2128 | 4 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=10)
3 - filter(ROWNUM<=10)
4 - access("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))
6 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
7 - access("V_PS"."PRODUCT_SKU"="PRODUCT_SKU" AND "V_PS"."PRODUCT_CODE"="PC"."PRODUCT_CODE")
8 - access("PC"."PRODUCT_CODE"="PRODUCT_CODE")
9 - filter("RS"."ROW_NUM"=1)
10 - filter(ROW_NUMBER() OVER ( PARTITION BY "PRODUCT_CODE" ORDER BY
"PRICE","PRODUCT_SKU")<=1)
11 - filter(("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)
12 - filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
15 - filter("ISDEL"=0)
18 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))
可以看到FILTER和标量子查询部分已经没有了,逻辑读从之前的75万减少到了14万。
下面再优化分页语句解决第二个问题:
首先创建相应的索引:
create index idx_yws on cy_product_code(up_time,isup,isdel);
create index idx_yws2 on CY_PRODUCT_SKU(product_code);
create index idx_yws3 on cy_product_type(id);
SQL 等价改写为:
select *
from (select *
from (select rownum rn, a.*
from (SELECT /*+ index_desc(pc idx_yws) use_nl(pc,v_ps) use_nl(pc,v_tab) use_nl(pc,pt) use_nl(pc,pca) leading(pc) */
v_ps.cnt AS skuNum,
pc.version,
pc.product_code,
pc.product_name,
v_ps.price,
pc.gross_weight,
pc.quantity,
pc.up_time,
pb.name AS ppName,
f_getcategory(pca.id) AS flName,
v_ps.product_num,
pt.name AS lxName,
pc.MAINIMGURL
FROM cy_product_code pc
LEFT JOIN cy_product_brand pb
ON pc.brand_id = pb.id
LEFT JOIN cy_product_category pca
ON pca.id = pc.category_id
LEFT JOIN cy_product_type pt
ON pc.type_id = pt.id
LEFT JOIN (select product_code,
product_sku,
price,
product_num,
COUNT(*) OVER(PARTITION BY product_code) cnt
from cy_product_sku
where isdel = 0) v_ps
ON v_ps.product_code = pc.product_code
inner join (select product_sku, product_code
from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
product_sku,
product_code
FROM cy_product_sku
WHERE DEFAULT_SKU IN (2, 3)
AND isdel = 0) RS
WHERE RS.ROW_NUM = 1) v_tab
on pc.product_code = v_tab.product_code
and v_ps.product_sku = v_tab.product_sku
WHERE pc.isup = 1
AND pc.isdel = 0
ORDER BY pc.up_time DESC) a)
where rownum <= 10)
where rn >= 1
最后的执行计划为:
---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2905 | 8041 (10)| 00:01:37 |
|* 1 | VIEW | | 1 | 2905 | 8041 (10)| 00:01:37 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 1 | 2905 | 8041 (10)| 00:01:37 |
| 4 | COUNT | | | | | |
| 5 | VIEW | | 1 | 2892 | 8041 (10)| 00:01:37 |
| 6 | NESTED LOOPS OUTER | | 1 | 541 | 8041 (10)| 00:01:37 |
| 7 | NESTED LOOPS OUTER | | 1 | 537 | 8037 (10)| 00:01:37 |
| 8 | NESTED LOOPS OUTER | | 1 | 521 | 8036 (10)| 00:01:37 |
| 9 | NESTED LOOPS | | 1 | 508 | 8035 (10)| 00:01:37 |
| 10 | NESTED LOOPS | | 807 | 138K| 5613 (14)| 00:01:08 |
| 11 | TABLE ACCESS BY INDEX ROWID | CY_PRODUCT_CODE | 776 | 84584 | 2509 (1)| 00:00:31 |
|* 12 | INDEX FULL SCAN DESCENDING | IDX_YWS | 2709 | | 14 (0)| 00:00:01 |
|* 13 | VIEW PUSHED PREDICATE | | 1 | 67 | 4 (25)| 00:00:01 |
|* 14 | WINDOW SORT PUSHED RANK | | 1 | 35 | 4 (25)| 00:00:01 |
|* 15 | TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_SKU | 1 | 35 | 3 (0)| 00:00:01 |
|* 16 | INDEX RANGE SCAN | IDX_YWS2 | 7 | | 1 (0)| 00:00:01 |
|* 17 | VIEW PUSHED PREDICATE | | 1 | 332 | 3 (0)| 00:00:01 |
| 18 | WINDOW BUFFER | | 2 | 82 | 3 (0)| 00:00:01 |
|* 19 | TABLE ACCESS BY INDEX ROWID | CY_PRODUCT_SKU | 2 | 82 | 3 (0)| 00:00:01 |
|* 20 | INDEX RANGE SCAN | IDX_YWS2 | 7 | | 1 (0)| 00:00:01 |
| 21 | TABLE ACCESS BY INDEX ROWID | CY_PRODUCT_BRAND | 1 | 13 | 1 (0)| 00:00:01 |
|* 22 | INDEX UNIQUE SCAN | CY_PRODUCT_BRAND_PK | 1 | | 0 (0)| 00:00:01 |
| 23 | TABLE ACCESS BY INDEX ROWID | CY_PRODUCT_TYPE | 1 | 16 | 1 (0)| 00:00:01 |
|* 24 | INDEX RANGE SCAN | IDX_YWS3 | 1 | | 0 (0)| 00:00:01 |
|* 25 | TABLE ACCESS FULL | CY_PRODUCT_CATEGORY | 1 | 4 | 4 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=1)
2 - filter(ROWNUM<=10)
12 - access("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
13 - filter("RS"."ROW_NUM"=1)
14 - filter(ROW_NUMBER() OVER ( PARTITION BY "PRODUCT_CODE" ORDER BY "PRICE","PRODUCT_SKU")<=1)
15 - filter(("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)
16 - access("PRODUCT_CODE"="PC"."PRODUCT_CODE")
17 - filter("V_PS"."PRODUCT_SKU"="PRODUCT_SKU")
19 - filter("ISDEL"=0)
20 - access("PRODUCT_CODE"="PC"."PRODUCT_CODE")
22 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))
24 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
25 - filter("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))