Oracle - 查询优化 - 查询运行时间长
Posted
技术标签:
【中文标题】Oracle - 查询优化 - 查询运行时间长【英文标题】:Oracle - Query Optimization - Query runs for a long time 【发布时间】:2020-06-19 05:14:39 【问题描述】:我有一个每月执行一次的 oracle 查询来处理订单详细信息。这个查询需要花费大量时间来执行。 (超过三十分钟)。因此,我正在尝试对此进行优化。我对甲骨文有相当的了解,我将解释我到目前为止所做的尝试。不过,完成大约需要 20 分钟。这是查询。 Oracle 版本是 11g。
SELECT store_typ, store_no, COUNT(order_no) FROM
(
SELECT DISTINCT(order_no), store.store_no, store.store_typ FROM
(
SELECT trx.order_no,trx.ADDED_DATE, odr.prod_typ, odr.store_no FROM daily_trx trx
LEFT OUTER JOIN
(
SELECT odr.order_no,odr.prod_typ,prod.store_no FROM order_main odr
LEFT OUTER JOIN ORDR_PROD_TYP prod
on odr.prod_typ = prod.prod_typ
) odr
ON trx.order_no= odr.order_no
) daily_orders ,
(SELECT store_no,store_typ FROM main_stores ) store
WHERE 1=1
and daily_orders.order_no !='NA'
and store.store_no = daily_orders.store_no
AND to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') >= to_date('01-05-2020 00:00:00','DD-MM-YYYY HH24:MI:SS')
AND to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') <= to_date('31-05-2020 23:59:59','DD-MM-YYYY HH24:MI:SS')
)
GROUP BY store_typ, store_no
背景
order_main - 此表有超过 400 万条记录 我为 order_no 列引入了索引,减少了执行时间。我的问题如下。
1) 如果我像这样在内部查询中移动日期验证会有帮助吗?
SELECT store_typ, store_no, COUNT(order_no) FROM
(
SELECT DISTINCT(order_no), store.store_no, store.store_typ FROM
(
SELECT trx.order_no,trx.ADDED_DATE, odr.prod_typ, odr.store_no FROM daily_trx trx
LEFT OUTER JOIN
(
SELECT odr.order_no,odr.prod_typ,prod.store_no FROM order_main odr
LEFT OUTER JOIN ORDR_PROD_TYP prod
on odr.prod_typ = prod.prod_typ
) odr
ON trx.order_no= odr.order_no
WHERE to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') >= to_date('01-05-2020 00:00:00','DD-MM-YYYY HH24:MI:SS')
AND to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') <= to_date('31-05-2020 23:59:59','DD-MM-YYYY HH24:MI:SS')
) daily_orders ,
(SELECT store_no,store_typ FROM main_stores ) store
WHERE 1=1
and daily_orders.order_no !='NA'
and store.store_no = daily_orders.store_no
--AND to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') >= to_date('01-05-2020 00:00:00','DD-MM-YYYY HH24:MI:SS')
--AND to_timestamp(to_char(daily_orders.ADDED_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY HH24:MI:SS') <= to_date('31-05-2020 23:59:59','DD-MM-YYYY HH24:MI:SS')
)
GROUP BY store_typ, store_no
2) 有人可以建议对此查询进行的任何其他改进吗?
3) 额外的索引将有助于任何其他表/列?只有daily_trx 和order_main 表是包含大量数据的表。
【问题讨论】:
ADDED_DATE
是日期数据类型列。应该直接在where条件下使用,不要转成char和timestamp
请 1) 决定是否要使用 ANSI 连接或 Oracle 连接语法,但不要将它们结合起来。 2) 删除 irrelevant 1=1 AND
和 3) 提供有关 所有 表和 执行计划 的信息 - 你可能会发现一些提示 here
DISTINCT(order_no)
?您知道DISTINCT
不是在单个列上调用的函数,而是占整行的关键字? DISTINCT
通常是次优查询的指标。为什么你首先得到重复?请告诉我们这些表的唯一键是什么。
如前所述,我们必须知道表的键才能给出合理的答案。如果您描述了每个表格代表的内容以及您想要计算的内容,这可能会有所帮助。一个订单是指一家商店还是可以指多家商店?您是否只是想计算每家商店在 6 月份至少有一笔交易的订单数?
【参考方案1】:
一些一般性的建议
不要在一个查询中结合使用 ANSI 和 Oracle 连接语法
如果可以使用内连接就不要使用外连接
您的内部子查询使用外部联接,但到main_stores
的最终联接是内部联接
消除带有store_no is null
的所有行 - 您可以使用具有相同结果的内连接。
-
提前过滤行
次优做法是先加入子查询,然后使用where
条件过滤相关行
-
使用简单谓词
如果你想限制 DATE
列,这样做
trx.ADDED_DATE >= to_date('01-05-2020 00:00:00','DD-MM-YYYY HH24:MI:SS')
-
如果合适,请使用
count distinct
如果使用COUNT(DISTINCT order_no)
,第三行中的select DISTINCT
query 可以被消除
应用以上所有点,我得出以下查询
select
store.store_no, store.store_typ, count(DISTINCT trx.order_no) order_no_cnt
from daily_trx trx
join order_main odr on trx.order_no = odr.order_no
join ordr_prod_typ prod on odr.prod_typ = prod.prod_typ
join main_stores store on store.store_no = prod.store_no
where trx.ADDED_DATE >= date'2020-05-01' and
trx.ADDED_DATE < date'2020-06-01' and
trx.order_no !='NA'
group by store.store_no, store.store_typ
【讨论】:
感谢您的回答。我会尝试您的建议并让您知道:) 我建议使用日期文字。日期范围条件可以写成WHERE trx.added_date >= date '2020-05-01' AND trx.added_date < date '2020-06-01'
。
感谢@ThorstenKettner 的建议,我在回答中考虑了它。查询(文本)的清晰度肯定会增加,很可能对查询的性能行为没有影响,因为Oracle在后台使用相同的to_date
函数可以看出执行计划的谓词in the other answer【参考方案2】:
性能考虑
您处理一个月的数据,因此可能会有大量交易(例如 100K+)。在这种情况下,最好的方法是全面扫描两个大表并执行HASH JOIN
s。
你可以期待这个执行计划
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 199K| 5850K| | 592 (2)| 00:00:08 |
|* 1 | HASH JOIN | | 199K| 5850K| | 592 (2)| 00:00:08 |
| 2 | TABLE ACCESS FULL | MAIN_STORES | 26 | 104 | | 3 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 199K| 5070K| | 588 (2)| 00:00:08 |
| 4 | TABLE ACCESS FULL | ORDR_PROD_TYP | 26 | 104 | | 3 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 199K| 4290K| 1960K| 584 (1)| 00:00:08 |
|* 6 | TABLE ACCESS FULL| ORDER_MAIN | 100K| 782K| | 69 (2)| 00:00:01 |
|* 7 | TABLE ACCESS FULL| DAILY_TRX | 200K| 2734K| | 172 (2)| 00:00:03 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("STORE"."STORE_NO"="PROD"."STORE_NO")
3 - access("ODR"."PROD_TYP"="PROD"."PROD_TYP")
5 - access("TRX"."ORDER_NO"="ODR"."ORDER_NO")
6 - filter("ODR"."ORDER_NO"<>'NA')
7 - filter("TRX"."ADDED_DATE"<TO_DATE(' 2020-06-01 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "TRX"."ORDER_NO"<>'NA' AND "TRX"."ADDED_DATE">=TO_DATE(' 2020-05-01
00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
如果您有一个可用的分区选项,您将通过定义每月分区模式(替代每日分区大量利润 /em>) 在DAILY_TRX
和ORDER_MAIN
两个表上。
如果上述假设不正确,并且您在选定的时间间隔内事务非常少(比如低于 1K) - 您会更好地使用 索引访问 和NESTED LOOPS
加入。
您将需要这组索引
create index daily_trx_date on daily_trx(ADDED_DATE);
create unique index order_main_idx on order_main (order_no);
create unique index ORDR_PROD_TYP_idx1 on ORDR_PROD_TYP(prod_typ);
create unique index main_stores_idx1 on main_stores(store_no);
预期方案如下
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 92 | 2760 | 80 (4)| 00:00:01 |
|* 1 | HASH JOIN | | 92 | 2760 | 80 (4)| 00:00:01 |
|* 2 | TABLE ACCESS BY INDEX ROWID | DAILY_TRX | 92 | 1288 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | DAILY_TRX_DATE | 92 | | 3 (0)| 00:00:01 |
|* 4 | HASH JOIN | | 100K| 1564K| 75 (3)| 00:00:01 |
| 5 | MERGE JOIN | | 26 | 208 | 6 (17)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| MAIN_STORES | 26 | 104 | 2 (0)| 00:00:01 |
| 7 | INDEX FULL SCAN | MAIN_STORES_IDX1 | 26 | | 1 (0)| 00:00:01 |
|* 8 | SORT JOIN | | 26 | 104 | 4 (25)| 00:00:01 |
| 9 | TABLE ACCESS FULL | ORDR_PROD_TYP | 26 | 104 | 3 (0)| 00:00:01 |
|* 10 | TABLE ACCESS FULL | ORDER_MAIN | 100K| 782K| 69 (2)| 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("TRX"."ORDER_NO"="ODR"."ORDER_NO")
2 - filter("TRX"."ORDER_NO"<>'NA')
3 - access("TRX"."ADDED_DATE">=TO_DATE(' 2020-06-01 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "TRX"."ADDED_DATE"<TO_DATE(' 2020-07-01 00:00:00', 'syyyy-mm-dd
hh24:mi:ss'))
4 - access("ODR"."PROD_TYP"="PROD"."PROD_TYP")
8 - access("STORE"."STORE_NO"="PROD"."STORE_NO")
filter("STORE"."STORE_NO"="PROD"."STORE_NO")
10 - filter("ODR"."ORDER_NO"<>'NA')
查看here如何获取查询的执行计划
【讨论】:
以上是关于Oracle - 查询优化 - 查询运行时间长的主要内容,如果未能解决你的问题,请参考以下文章