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 DISTINCTquery 可以被消除

应用以上所有点,我得出以下查询

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 &gt;= date '2020-05-01' AND trx.added_date &lt; date '2020-06-01' 感谢@ThorstenKettner 的建议,我在回答中考虑了它。查询(文本)的清晰度肯定会增加,很可能对查询的性能行为没有影响,因为Oracle在后台使用相同的to_date函数可以看出执行计划的谓词in the other answer【参考方案2】:

性能考虑

您处理一个月的数据,因此可能会有大量交易(例如 100K+)。在这种情况下,最好的方法是全面扫描两个大表并执行HASH JOINs。

你可以期待这个执行计划

    ----------------------------------------------------------------------------------------------
| 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_TRXORDER_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 - 查询优化 - 查询运行时间长的主要内容,如果未能解决你的问题,请参考以下文章

oracle查询的查询优化

ORACLE中这个运行4秒左右的SQL语句如何优化?我想查询少用点时间

如何优化 SQL 查询以减少运行时间?

SQL查询优化Oracle

需要帮助在 12c 中优化 Oracle 查询

oracle 优化时间查询