一位上海的朋友说以下 SQL 执行不出结果。
with tab as
(
select bb.card_no cashier_shop_no, aa.card_open_owner merch_id,aa.card_no ,bb.txn_date,bb.last_txn_date,bb.merch_loc_name
from tb_card aa
join
(select t.card_no,a.merch_id,a.merch_loc_name ,t.cust_id txn_date,
to_char(last_day( add_months(to_date(t.cust_id,'yyyymmdd'),1)) ,'yyyymmdd') last_txn_date
from tb_bill_test t join tb_merch a on t.card_no=a.cashier_shop_no
where t.nbr_group='161' and a.merch_id not like '0%' )bb
on aa.card_open_owner=bb.merch_id
where aa.mbr_reg_date between bb.txn_date and bb.last_txn_date
)
select bb.cashier_shop_no, bb.merch_loc_name,aa.txn_date,
case
when aa.merch_id=bb.merch_id and aa.card_no=bb.card_no
then '本店会员'
else '他店会员'
end shop_no,
count(distinct aa.card_no) card_num,
sum( case when aa.p_code in ('7646','7686','7208') then -1 else 1 end ) count_num,
sum(case
when aa.p_code in ('7646','7686','7208') then 0-
case
when aa.txn_amt>aa.earning_amt
then aa.txn_amt
else aa.earning_amt
end
else
case
when aa.txn_amt>aa.earning_amt
then aa.txn_amt
else aa.earning_amt
end
end) txn_amt
from tb_trans aa join tab bb on aa.merch_id=bb.merch_id
where aa.txn_date between bb.txn_date and bb.last_txn_date and
aa.p_code in ('7647','7687','7207','7646','7686','7208')
and aa.status in ('1','R')
group by bb.cashier_shop_no, bb.merch_loc_name,aa.txn_date,aa.merch_id,
case when aa.merch_id=bb.merch_id and aa.card_no=bb.card_no then '本店会员' else '他店会员' end
order by aa.merch_id, aa.txn_date;
执行计划如下。
Plan hash value: 4271695044
-------------------------------------------------------------------------------------
| Id |Operation |Name |Rows|Bytes|Cost(%CPU)|
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 15|1650|96737 (1)|
| 1 | SORT GROUP BY | | 15|1650|96737 (1)|
| 2 | VIEW |VW_DAG_0 | 15|1650|96735 (1)|
| 3 | HASH GROUP BY | | 15|2430|96735 (1)|
| 4 | NESTED LOOPS | | 15| 430|96734 (1)|
| 5 | NESTED LOOPS | |2213|2430|96734 (1)|
| 6 | NESTED LOOPS | | 1| 103| 542 (1)|
| 7 | NESTED LOOPS | | 1| 65| 58 (0)|
| 8 | TABLE ACCESS BY INDEX ROWID |TB_BILL_TEST | 36| 864| 20 (0)|
|* 9 | INDEX RANGE SCAN |TMP_INDEX_BILL_01 | 37| | 3 (0)|
|*10 | TABLE ACCESS BY INDEX ROWID |TB_MERCH | 1| 41| 3 (0)|
|*11 | INDEX RANGE SCAN |I1_MERCH | 1| | 1 (0)|
|*12 | TABLE ACCESS BY INDEX ROWID |TB_CARD | 2| 76| 484 (1)|
|*13 | INDEX RANGE SCAN |I1_CARD_OPEN_OWNER|3855| | 24 (0)|
|*14 | INDEX RANGE SCAN |I2_TRANS |2213| |95972 (1)|
|*15 | TABLE ACCESS BY GLOBAL INDEX ROWID|TB_TRANS | 56|3304|96193 (1)|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
9 - access("T"."NBR_GROUP"='161')
filter("T"."CARD_NO" IS NOT NULL)
10 - filter("A"."MERCH_ID" NOT LIKE '0%')
11 - access("T"."CARD_NO"="A"."CASHIER_SHOP_NO")
12 - filter("AA"."MBR_REG_DATE">="T"."CUST_ID" AND "AA"."MBR_REG_DATE"<=TO_CHAR(LAST_DAY(ADD_MONTHS
(TO_DATE("T"."CUST_ID",'yyyymmdd'),1)),'yyyymmdd'))
13 - access("AA"."CARD_OPEN_OWNER"="A"."MERCH_ID")
filter("AA"."CARD_OPEN_OWNER" IS NOT NULL)
14 - access("AA"."TXN_DATE">="T"."CUST_ID" AND "AA"."MERCH_ID"="AA"."CARD_OPEN_OWNER" AND "AA"."TXN_DATE"<=TO_CHAR(LAST_DAY(ADD_MONTHS(TO_DATE("T"."CUST_ID",'yyyymmdd'),1)),'yyyymmdd'))
filter("AA"."MERCH_ID"="AA"."CARD_OPEN_OWNER")
15 - filter(("AA"."P_CODE"='7207' OR "AA"."P_CODE"='7208' OR "AA"."P_CODE"='7646' OR "AA"."P_CODE"='7647'
OR "AA"."P_CODE"='7686' OR "AA"."P_CODE"='7687') AND ("AA"."STATUS"='1' OR "AA"."STATUS"='R'))
我们拿到一条需要优化的 SQL 语句,怎么入手呢?首先要看 SQL 写法。可以利用 SQL 三段分拆方法,先观察 SQL 语句。该 SQL 语句有个 with as 子句取名为 tab,主查询中就是 tb_trans 与 tab 进行关联。with as 子句一共返回 6 000 多行数据,可以 1 秒内出结果,tb_trans 有两亿条数据。执行计划中,with as 子查询作为一个整体并且作为嵌套循环驱动表,tb_trans 作为嵌套循环被驱动表,乍一看,这也符合嵌套循环关联原则,小表驱动大表,大表走索引。但是该SQL执行不出结果,最大的可能就是 tab 与 tb_trans 关联之后返回数据量太多,因为返回结果集太多,被驱动表走索引,也就是说该 SQL 可能是被驱动表走索引返回数据量太多导致性能问题。于是检查被驱动表连接列 merch_id 基数,基数很低,tab:tb_trans 是 1 比几十万关系。
因为被驱动表 tb_trans 与 tab 是几十万比 1 的关系,这时就不能走嵌套循环了,只能走 HASH 连接,于是使用 HINT:use_hash(aa,bb)
优化 SQL,最终该 SQL 可以在 1 小时左右执行完毕。如果开启并行查询可以更快。