Оптимизация запроса с соединением таблиц

Есть такой запрос:

select o.OPERATION_ID, o.SERVICE_ID, o.PARTNER_OPERATION_ID, o.AMOUNT_IN, o.USER_ID, o.TRY_STATUS, o.FIN_DATE, o.FACT_DATE,
       o.TRY_LATER_COUNT, o.CANCELLED_OPERATION_ID, o.CUSTOMER_ALIAS, o.CANCELLING_OPERATION_ID, o.SUBTYPE, o.LAST_CHANGE,
       o.CORRECTED, o.SOLUTION_E, s.PARTNER_ID 
from OPERATIONS o 
join SERVICES s on o.SERVICE_ID = s.SERVICE_ID
where o.FACT_DATE between TO_DATE('2021-06-25','yyyy-mm-dd') and TO_DATE('2021-06-28','yyyy-mm-dd')
order by o.OPERATION_ID  OFFSET 0 ROWS FETCH NEXT 134 ROWS ONLY

Колонка FACT_DATE имеет тип timestamp и хранит дату, например:

fromDate -> "2019-06-25 00:00:00.0"

Kогда число выборок превышает 57-100к (в примере я подставил 827678), время выполенния увеличивается с 10 минут до нескольких часов.

Я запрос смотрю и, честно сказать, не вижу как его можно оптимизировать. Вроде нигде индекс не переиспользуется.


БД Oracle 10g.


Ответы (1 шт):

Автор решения: 0xdb

Для оптимизации SQL запроса часто требуется гораздо больше информации, чем для его написания и устранения логических ошибок.

Скопировать и опубликовать здесь сам запрос явно недостаточно. В большинстве случаев потребуются следующая информация:

  1. Структура таблиц в виде запросов на их создание, или как минимум, описание таблиц:
create table SERVICES (
    SERVICE_ID number primary key, 
    PARTNER_ID number
);
create table OPERATIONS (
    OPERATION_ID number primary key, 
    SERVICE_ID number references SERVICES, 
    TRY_STATUS varchar2 (8), 
    FACT_DATE timestamp
);

desc OPERATIONS

Name         Null?    Type         
------------ -------- ------------ 
OPERATION_ID NOT NULL NUMBER       
SERVICE_ID            NUMBER       
TRY_STATUS            VARCHAR2(8)  
FACT_DATE             TIMESTAMP(6) 
  1. Все существующие индексы созданые на таблицах:
create index OPERATIONS_factdate_idx on OPERATIONS (FACT_DATE);
  1. По возможности, написать генератор тестовых данных, которые наиболее приближенны к реальным данным, и в заключение, обновить статистику:
insert all 
    when 1=1 then into SERVICES   values (rn, mod (rn,9)+1)
    when 1=1 then into OPERATIONS values (rn, rn, 'complete', ts)
    select rownum rn, timestamp'2021-06-20 00:00:00'+((rownum-1)*10/(24*60*60)) ts
    from dual connect by level<=1e5;
commit;

exec dbms_stats.gather_table_stats (user, 'SERVICES', cascade=>true)

exec dbms_stats.gather_table_stats (user, 'OPERATIONS', cascade=>true)
  1. Сам запрос и, что важно, план его выполнения. Как получить план выполнения, наиболее полно показано, например, в этой теме. Субъективно предпочитаю - SQL*Plus с autotrace:
SQL> set lines 999 autotrace traceonly
SQL> select o.OPERATION_ID, o.SERVICE_ID, o.FACT_DATE, s.PARTNER_ID
  2  from OPERATIONS o
  3  join SERVICES s on o.SERVICE_ID = s.SERVICE_ID
  4  where o.FACT_DATE between DATE'2021-06-25' and DATE'2021-06-28'
  5  order by o.OPERATION_ID
  6  OFFSET 0 ROWS FETCH NEXT 134 ROWS ONLY;

134 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2383740585

------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |              |   134 | 10452 |   142   (0)| 00:00:01 |
|*  1 |  VIEW                           |              |   134 | 10452 |   142   (0)| 00:00:01 |
|*  2 |   WINDOW NOSORT STOPKEY         |              |   137 |  3973 |   142   (0)| 00:00:01 |
|   3 |    NESTED LOOPS                 |              |   137 |  3973 |   142   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                |              |   137 |  3973 |   142   (0)| 00:00:01 |
|*  5 |      TABLE ACCESS BY INDEX ROWID| OPERATIONS   | 25922 |   531K|     5   (0)| 00:00:01 |
|   6 |       INDEX FULL SCAN           | SYS_C0013710 |   521 |       |     2   (0)| 00:00:01 |
|*  7 |      INDEX UNIQUE SCAN          | SYS_C0013709 |     1 |       |     0   (0)| 00:00:01 |
|   8 |     TABLE ACCESS BY INDEX ROWID | SERVICES     |     1 |     8 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

   1 - filter("from$_subquery$_004"."rowlimit_$$_rownumber"<=134 AND
              "from$_subquery$_004"."rowlimit_$$_rownumber">0)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY "O"."OPERATION_ID")<=134)
   5 - filter("O"."FACT_DATE">=TIMESTAMP' 2021-06-25 00:00:00' AND
              "O"."FACT_DATE"<=TIMESTAMP' 2021-06-28 00:00:00')
   7 - access("O"."SERVICE_ID"="S"."SERVICE_ID")

Следует упомянуть, что информации может быть достаточно много, и часть её можно разместить на внешнем SQL fiddle, например, на db<>fiddle. В вопросе же уделить внимание другим деталям, например, попытке самостоятельной оптимизировать запрос - что было сделано, что не получилось, предположения - почему не получилось.

В заключение, информацию следует публиковать в текстовом виде, а не картинками.

→ Ссылка