Oracle 查询调优建议

Posted

技术标签:

【中文标题】Oracle 查询调优建议【英文标题】:Oracle query tuning advice 【发布时间】:2019-12-20 08:42:35 【问题描述】:

我有一个 SQL 查询,它每天使用一个过程生成一个结果集,该结果集被插入到meter_read_alert 表中。

这个过程每天大约需要 3 小时。解释计划如下所示。

如何优化查询?以及通过查看说明计划有哪些问题区域?

该表具有范围间隔分区和本地索引。每天大约有 900 万行插入发生。

查询:

select * from (

        SELECT
            'SBILL' Scenario,
            mfg_serial_num,
            device_id,
            channel_id,
            read_value,
            read_time,
            date_val,
            reading_hourly,
            reading_daywise,
           CASE 
                WHEN read_value IS NULL THEN'04'--(Meter goes stale)' 
                WHEN event_name = 'BACKFLOW' THEN'01' 
                WHEN event_name = 'TAMPER' THEN'02' 
                WHEN event_name = 'Tamper' THEN'02'
                WHEN event_name = 'EMPTYPIPE' THEN '03' END AS CodeAlert_HOUR, 

            CASE WHEN reading_daywise BETWEEN CON.min_value AND CON.max_value THEN CON.alert_code  END AS CodeAlert_DAY, 

            CASE
                WHEN read_value IS NULL THEN'4' 
                WHEN event_name = 'BACKFLOW' THEN'4' 
                WHEN event_name = 'TAMPER' THEN'4'
                WHEN event_name = 'Tamper' THEN'4'
                WHEN event_name = 'EMPTYPIPE' THEN'4' END AS Priority_HOUR, 

            CASE WHEN reading_daywise BETWEEN CON.min_value AND CON.max_value THEN CON.priority END AS Priority_DAY,

            CASE WHEN val_status='VAL' THEN 'A' WHEN val_status='EST' THEN 'E' ELSE  val_status END Val_status,
             desc_text,
             CASE WHEN reading_hourly BETWEEN CONH.min_value AND CONH.max_value THEN CONH.alert_code  END AS ConAlert_per_hour
FROM
   (

SELECT   d.mfg_serial_num,
         d.id device_id,
         mdr.channel_id,
         mdr.read_value,
         mdr.read_time,
         To_date(To_char(mdr.read_time, 'dd/mm/yyyy')) date_val,
         mdr.read_value - Lag(mdr.read_value, 1, 0) over ( PARTITION BY mdr.channel_id ORDER BY mdr.read_time) reading_hourly,
         Max(mdr.read_value) keep (dense_rank last ORDER BY To_char (mdr.read_time, 'HH24:MI:SS')) over ( PARTITION BY mdr.channel_id,To_date(To_char(mdr.read_time, 'dd/mm/yyyy'))) -
         Min(mdr.read_value) keep (dense_rank first ORDER BY To_char (mdr.read_time, 'HH24:MI:SS') ) over ( PARTITION BY mdr.channel_id,To_date(To_char(mdr.read_time, 'dd/mm/yyyy')))  as reading_daywise,
         det.event_desc desc_text,
         det.event_name,
         mdr.val_status

FROM (select channel_id,read_value,read_time,val_status,device_id from mudr.register_read 
        WHERE read_time  between (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)

        --ORDER BY channel_id,read_time
        )Mdr 
left join (select channel_id, device_id from EIP.device_channel_rel --where eff_end_time is null
) DCR ON dcr.channel_id = mdr.channel_id 
left join  (select id, mfg_serial_num from  EIP.device) d ON d.id = DCR.device_id 
left join (select device_id, device_event_type_id,event_time ,
           case when to_char(event_time,'mi') between '01' and '59' then trunc(event_time,'HH24') + interVal '60' minute else event_time end New_Event_Time
           from MUDR.device_event 
           where event_time  between  (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)

            ) de ON mdr.device_id = de.device_id
            and mdr.read_time= de.New_Event_Time 

left join (select device_event_type_id,event_desc,event_name from MUDR.device_event_type) det ON det.device_event_type_id= de.device_event_type_id
--ORDER BY channel_id,read_time
)t1 
left join (SELECT * FROM condition  WHERE scenario = 'SBILL' AND unit_of_period_check='DAY' )CON ON t1.reading_daywise BETWEEN CON.min_value AND CON.max_value
left join (SELECT * FROM condition  WHERE scenario = 'SBILL' AND unit_of_period_check='HOUR' )CONH ON t1.reading_hourly BETWEEN CONH.min_value AND CONH.max_value

ORDER BY channel_id,read_time) a
where not exists 
(
select 1 
from meter_read_alert b
WHERE a.scenario=b.scenario 
AND a.mfg_serial_num= b.mfg_serial_num
AND a.device_id = b.device_id
and a.channel_id=b.channel_id
AND a.read_time=b.read_time
and b.read_time between (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)
);

计划哈希值:618507408

------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                                  | Name                  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                           |                       |   106K|    46M|       |   153K  (1)| 00:00:07 |       |       |
|   1 |  SORT ORDER BY                                             |                       |   106K|    46M|    48M|   153K  (1)| 00:00:07 |       |       |
|   2 |   MERGE JOIN OUTER                                         |                       |   106K|    46M|       |   153K  (1)| 00:00:07 |       |       |
|   3 |    SORT JOIN                                               |                       |   106K|    44M|    92M|   153K  (1)| 00:00:07 |       |       |
|   4 |     MERGE JOIN OUTER                                       |                       |   106K|    44M|       |   153K  (1)| 00:00:07 |       |       |
|   5 |      SORT JOIN                                             |                       |   106K|    41M|    87M|   153K  (1)| 00:00:07 |       |       |
|*  6 |       HASH JOIN RIGHT ANTI                                 |                       |   106K|    41M|  3432K|   145K  (1)| 00:00:06 |       |       |
|   7 |        PARTITION RANGE ITERATOR                            |                       | 87775 |  2400K|       | 20495   (1)| 00:00:01 |   KEY |   KEY |
|   8 |         VIEW                                               | VW_SQ_1               | 87775 |  2400K|       | 20495   (1)| 00:00:01 |       |       |
|*  9 |          TABLE ACCESS BY LOCAL INDEX ROWID BATCHED         | METER_READ_ALERT      | 87775 |  2914K|       | 20495   (1)| 00:00:01 |   KEY |   KEY |
|* 10 |           INDEX RANGE SCAN                                 | READTIME_ALL_IDX      | 87775 |       |       |    81   (0)| 00:00:01 |   KEY |   KEY |
|  11 |            FAST DUAL                                       |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|  12 |            FAST DUAL                                       |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|  13 |        VIEW                                                |                       |   155K|    56M|       |   124K  (1)| 00:00:05 |       |       |
|  14 |         WINDOW SORT                                        |                       |   155K|    24M|    26M|   124K  (1)| 00:00:05 |       |       |
|  15 |          WINDOW SORT                                       |                       |   155K|    24M|    26M|   124K  (1)| 00:00:05 |       |       |
|* 16 |           HASH JOIN RIGHT OUTER                            |                       |   155K|    24M|       |   119K  (1)| 00:00:05 |       |       |
|  17 |            TABLE ACCESS FULL                               | DEVICE_EVENT_TYPE     |  1513 |   124K|       |     8   (0)| 00:00:01 |       |       |
|* 18 |            HASH JOIN RIGHT OUTER                           |                       | 52840 |  4076K|       |   119K  (1)| 00:00:05 |       |       |
|  19 |             PARTITION RANGE ITERATOR                       |                       |     3 |    54 |       |     1   (0)| 00:00:01 |   KEY |   KEY |
|  20 |              PARTITION HASH ALL                            |                       |     3 |    54 |       |     1   (0)| 00:00:01 |     1 |  LAST |
|  21 |               VIEW                                         |                       |     3 |    54 |       |     1   (0)| 00:00:01 |       |       |
|  22 |                TABLE ACCESS BY LOCAL INDEX ROWID BATCHED   | DEVICE_EVENT          |     3 |    48 |       |     1   (0)| 00:00:01 |   KEY |   KEY |
|* 23 |                 INDEX RANGE SCAN                           | IX_DEVICE_EVENT_1     |     1 |       |       |     1   (0)| 00:00:01 |   KEY |   KEY |
|  24 |                  FAST DUAL                                 |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|  25 |                  FAST DUAL                                 |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|* 26 |             HASH JOIN OUTER                                |                       | 52840 |  3147K|  2944K|   119K  (1)| 00:00:05 |       |       |
|* 27 |              HASH JOIN OUTER                               |                       | 52840 |  2322K|  2312K|   115K  (1)| 00:00:05 |       |       |
|  28 |               VIEW                                         |                       | 51466 |  1708K|       |   113K  (1)| 00:00:05 |       |       |
|  29 |                SORT ORDER BY                               |                       | 51466 |  1357K|  2240K|   113K  (1)| 00:00:05 |       |       |
|  30 |                 PARTITION RANGE ITERATOR                   |                       | 51466 |  1357K|       |   112K  (1)| 00:00:05 |   KEY |   KEY |
|  31 |                  PARTITION HASH ALL                        |                       | 51466 |  1357K|       |   112K  (1)| 00:00:05 |     1 |  LAST |
|  32 |                   TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| REGISTER_READ         | 51466 |  1357K|       |   112K  (1)| 00:00:05 |   KEY |   KEY |
|* 33 |                    INDEX SKIP SCAN                         | UK_REGISTER_READ      |     1 |       |       |   112K  (1)| 00:00:05 |   KEY |   KEY |
|  34 |                     FAST DUAL                              |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|  35 |                     FAST DUAL                              |                       |     1 |       |       |     2   (0)| 00:00:01 |       |       |
|  36 |               INDEX FAST FULL SCAN                         | UK_DEVICE_CHANNEL_REL |  1482K|    15M|       |  1443   (3)| 00:00:01 |       |       |
|  37 |              TABLE ACCESS FULL                             | DEVICE                |  1096K|    16M|       |  4063   (2)| 00:00:01 |       |       |
|* 38 |      FILTER                                                |                       |       |       |       |            |          |       |       |
|* 39 |       SORT JOIN                                            |                       |     1 |    26 |       |     3  (34)| 00:00:01 |       |       |
|* 40 |        EXTERNAL TABLE ACCESS FULL                          | CONDITION             |     1 |    26 |       |     2   (0)| 00:00:01 |       |       |
|* 41 |    FILTER                                                  |                       |       |       |       |            |          |       |       |
|* 42 |     SORT JOIN                                              |                       |     2 |    46 |       |     3  (34)| 00:00:01 |       |       |
|* 43 |      EXTERNAL TABLE ACCESS FULL                            | CONDITION             |     2 |    46 |       |     2   (0)| 00:00:01 |       |       |
------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("T1"."MFG_SERIAL_NUM"="ITEM_1" AND "T1"."DEVICE_ID"="ITEM_2" AND "T1"."CHANNEL_ID"="ITEM_3" AND "T1"."READ_TIME"="ITEM_4")
   9 - filter("B"."SCENARIO"='SBILL')
  10 - access("B"."READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "B"."READ_TIME"<= (SELECT 
              TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
  16 - access("DEVICE_EVENT_TYPE_ID"(+)="DE"."DEVICE_EVENT_TYPE_ID")
  18 - access("MDR"."DEVICE_ID"="DE"."DEVICE_ID"(+) AND "MDR"."READ_TIME"="DE"."NEW_EVENT_TIME"(+))
  23 - access("EVENT_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "EVENT_TIME"<= (SELECT 
              TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
  26 - access("ID"(+)="DEVICE_ID")
  27 - access("CHANNEL_ID"(+)="MDR"."CHANNEL_ID")
  33 - access("READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "READ_TIME"<= (SELECT 
              TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
       filter("READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "READ_TIME"<= (SELECT 
              TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
  38 - filter("T1"."READING_DAYWISE"<="CONDITION"."MAX_VALUE"(+))
  39 - access(INTERNAL_FUNCTION("T1"."READING_DAYWISE")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
       filter(INTERNAL_FUNCTION("T1"."READING_DAYWISE")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
  40 - filter("SCENARIO"(+)='SBILL' AND "UNIT_OF_PERIOD_CHECK"(+)='DAY')
  41 - filter("T1"."READING_HOURLY"<="CONDITION"."MAX_VALUE"(+))
  42 - access(INTERNAL_FUNCTION("T1"."READING_HOURLY")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
       filter(INTERNAL_FUNCTION("T1"."READING_HOURLY")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
  43 - filter("SCENARIO"(+)='SBILL' AND "UNIT_OF_PERIOD_CHECK"(+)='HOUR') 

【问题讨论】:

edit您的问题并将您正在使用的查询添加为formatted text 我对第 38-43 行中针对外部表的过滤器和排序合并有点怀疑。这可能是时间去的地方吗?如果你把它们拿出来会快很多吗?也许您可以在物化 WITH 子句中从 CONDITION 中选择您想要的行。 你能发布execution - not explain - plan这个查询吗?这包括 E(估计)行、A(实际)行和缓冲区 请解释一下,为什么有两个不同的设备ID。一个是mudr.register_read.device_id,一个是eip.device_channel.device_id。您的查询分别与两者一起使用。这是故意的吗? 顺便说一句,可能与性能无关,但我不确定我是否喜欢 (select to_timestamp(sysdate-1) - interval '1' second from dual) 用于指定时间戳的标量子查询。一方面,to_timestamp(date_expression) 是一种隐式转换,因为to_timestamp() 需要一个字符串。我更喜欢cast(trunc(sysdate) - interval '1 00:00:01' day to second as timestamp) 【参考方案1】:

您要选择的数据只有两个限制。最主要的是时间跨度。另一种是您要排除meter_read_alert 中已经存在的数据。您可以像这样开始查询,尽早应用限制:

with main as
(
  select
    s.scenario,
    mdr.channel_id,
    mdr.read_value,
    mdr.read_time,
    mdr.val_status,
    mdr.device_id as mdr_device_id,
    dcr.device_id as dcr_device_id,
    d.mfg_serial_num as dcr_mfg_serial_num
  from (select 'SBILL' as scenario from dual) s
  cross join mudr.register_read mdr
  left join from eip.device_channel_rel dcr on dcr.channel_id = mdr.channel_id
  left join eip.device d on d.id = dcr.device_id
  where mdr.read_time >= systimestamp - interval '7' day and mdr.read_time < systimestamp
  and not exists
  (
    select null
    from meter_read_alert mra
    where mra.scenario = s.scenario
    and mra.mfg_serial_num = d.mfg_serial_num
    and mra.device_id = mdr.device_id
    and mra.channel_id = mdr.channel_id
    and mra.read_time = mdr.read_time
  )
)
select ...

(顺便说一句,您确定要在此处进行外连接吗?如果内连接足够,请进行这些内连接。)

您的时间范围条件看起来很复杂。我希望我的查询是正确的。如果您希望它略有不同,请更改它。对于日期/时间,我们通常不使用 BETWEEN,但 &gt;=&lt; 在时间精度上是独立的(例如,somedate &lt; date '2019-12-01' 将获得完整的 11 月,直到最后一毫秒、纳秒或其他任何时间,但不包括任何十二月)。

加速查询主要是为了提供适当的索引。我建议:

create index idx1 on mudr.register_read (read_time, channel_id);
create index idx2 on eip.device_channel_rel (channel_id, device_id);
create index idx3 on eip.device (device_id);
create index idx4 on meter_read_alert (read_time, channel_id, device_id, mfg_serial_num, scenario);

甚至使用覆盖索引:

create index idx1 on mudr.register_read (read_time, channel_id, read_value, val_status, device_id);
create index idx2 on eip.device_channel_rel (channel_id, device_id);
create index idx3 on eip.device (device_id);
create index idx4 on meter_read_alert (read_time, channel_id, device_id, mfg_serial_num, scenario);

【讨论】:

感谢您的建议。我将尝试更正日期范围。提到的索引已经存在于这些表中。 排除meter_read_alert中已经存在的数据:为此,我将尝试您建议的其他方式并检查性能。

以上是关于Oracle 查询调优建议的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 查询/SQL 调优

sql Oracle调优查询

Oracle SQL查询的性能调优

oracle 中热门查询的自动 SQL 调优顾问报告

oracle日期转换函数怎么调优

Oracle / SQL 调优顾问