Объединение Oracle SQL на вычисляемых столбцах разбивает производительность запросов - PullRequest
0 голосов
/ 18 марта 2019

Предположим, есть две таблицы ...

-----------------
-- contains 10 million rows
create table TST_ITEMEVENT
(
  eventId   NUMBER(10) not null,
  itemId    VARCHAR2(20) not null,
  line      VARCHAR2(10),
  type      VARCHAR2(20),
  timepoint DATE
);

alter table TST_ITEMEVENT add primary key (EVENTID)

create index IDX_ITEMEVENT on TST_ITEMEVENT (line, type, timepoint);

-----------------
-- contains 1000 rows
create table TST_SNAPSHOT
(
  snapshotId    NUMBER(10) not null,
  timeTarget    DATE not null
);

… и два взгляда на них:

CREATE OR REPLACE VIEW tst_SnapshotRelation_V AS
  SELECT H.SnapshotId,
         H.TimeTarget,    
         LAG(H.SnapshotId) OVER ( ORDER BY H.TimeTarget) AS PrevSnapshotId    
    FROM tst_Snapshot H;


CREATE OR REPLACE VIEW tst_ItemAppearance_V AS
 SELECT T.SnapshotId,
        T.ItemId,
        CASE WHEN MAX(T.DateExcluded) >= MAX(T.DateIncluded) THEN 0
             WHEN MAX(T.DateIncluded) IS NULL                THEN 0
                                                             ELSE 1
         END                       AS InStock
   FROM (

        SELECT
               V.SnapshotId        AS SnapshotId,
               ME.ItemId           AS ItemId,
               CASE WHEN ME.Line = 'LINE-A' THEN ME.TimePoint END AS DateIncluded,
               CASE WHEN ME.Line = 'LINE-B' THEN ME.TimePoint END AS DateExcluded

          FROM tst_SnapshotRelation_V V,
               tst_ItemEvent ME
         WHERE (ME.Line, ME.Type) IN (
                            ('LINE-A', 'Type41'),
                            ('LINE-B', 'Type25')
               )
           AND ME.TimePoint < V.TimeTarget
      ) T
  GROUP BY T.SnapshotId, T.ItemId;

Предполагается, что они будут использоваться в таких запросах, как

-- "Heavy query"
select * 
  from tst_SnapshotRelation_V R,
       tst_ItemAppearance_V A       
 where A.SnapshotId = R.PrevSnapshotId
   and A.InStock = 1  
   and R.snapshotid = :X;

Реальная мощность этого запроса не превышает 5000 строк.

План «Тяжелый запрос»:

---------------------------------------------------------------------------------------------------------------
| Id   | Operation                           | Name                   | Rows    | Bytes     | Cost | Time     |
---------------------------------------------------------------------------------------------------------------
|    0 | SELECT STATEMENT                    |                        | 4133931 | 421660962 | 1056 | 00:00:13 |
|    1 |   MERGE JOIN                        |                        | 4133931 | 421660962 | 1056 | 00:00:13 |
|    2 |    SORT JOIN                        |                        | 4013525 | 232784450 | 1050 | 00:00:13 |
|    3 |     VIEW                            | TST_ITEMAPPEARANCE_V   | 4013525 | 232784450 | 1050 | 00:00:13 |
|  * 4 |      FILTER                         |                        |         |           |      |          |
|    5 |       HASH GROUP BY                 |                        | 4013525 | 549852925 | 1050 | 00:00:13 |
|    6 |        NESTED LOOPS                 |                        |         |           |      |          |
|    7 |         NESTED LOOPS                |                        | 4013525 | 549852925 |  724 | 00:00:09 |
|    8 |          TABLE ACCESS FULL          | TST_SNAPSHOT           |     103 |      2266 |    3 | 00:00:01 |
|    9 |          INLIST ITERATOR            |                        |         |           |      |          |
| * 10 |           INDEX RANGE SCAN          | IDX_ITEMEVENT          |      11 |           |    3 | 00:00:01 |
|   11 |         TABLE ACCESS BY INDEX ROWID | TST_ITEMEVENT          |   38966 |   4481090 |    7 | 00:00:01 |
| * 12 |    SORT JOIN                        |                        |     103 |      4532 |    5 | 00:00:01 |
| * 13 |     VIEW                            | TST_SNAPSHOTRELATION_V |     103 |      4532 |    4 | 00:00:01 |
|   14 |      WINDOW SORT                    |                        |     103 |      2266 |    4 | 00:00:01 |
|   15 |       TABLE ACCESS FULL             | TST_SNAPSHOT           |     103 |      2266 |    3 | 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 4 - filter(CASE WHEN MAX(CASE "ME"."LINE" WHEN 'QMHRP' THEN "ME"."TIMEPOINT" END )>=MAX(CASE "ME"."LINE" WHEN 'BM' THEN "ME"."TIMEPOINT" END ) THEN 0 WHEN MAX(CASE "ME"."LINE" WHEN 'BM' THEN
  "ME"."TIMEPOINT" END ) IS NULL THEN 0 ELSE 1 END =1)
* 10 - access(("ME"."LINE"='BM' AND "ME"."TYPE"='Entstehung' OR "ME"."LINE"='QMHRP' AND "ME"."TYPE"='Aenderung') AND "ME"."TIMEPOINT"<"H"."TIMETARGET")
* 12 - access("A"."SNAPSHOTID"="R"."PREVSNAPSHOTID")
* 12 - filter("A"."SNAPSHOTID"="R"."PREVSNAPSHOTID")
* 13 - filter("R"."SNAPSHOTID"=TO_NUMBER(:X))

На самом деле на получение результата уходит целая вечность (не менее 5 минут), потому что кажется, что на первом шаге исполнитель получает все строки из tst_ItemAppearance_V, а затем фильтрует их с помощью A.SnapshotId = R.PrevSnapshotId.

Может ли быть так, что оптимизатор решит использовать полное сканирование таблицы tst_ItemEvent, но это не показано в плане?

Этот эффект появляется только в том случае, если для соединения используется столбец R.PrevSnapshotId, который рассчитывается.

Когда, например, мы используем A.SnapshotId = R.SnapshotId - запрос доставляет результат за 2 секунды на моем компьютере. То же самое остается, когда есть фильтр R.PrevSnapshotId = :X

-- "Fast query" example
select * 
  from tst_SnapshotRelation_V R,
       tst_ItemAppearance_V A       
 where A.SnapshotId = R.PrevSnapshotId
   and A.InStock = 1   
   and R.PrevSnapshotId = :X;

План «Быстрого запроса»:

--------------------------------------------------------------------------------------------------------------
| Id   | Operation                          | Name                   | Rows    | Bytes     | Cost | Time     |
--------------------------------------------------------------------------------------------------------------
|    0 | SELECT STATEMENT                   |                        | 4133905 | 421658310 |   52 | 00:00:01 |
|  * 1 |   HASH JOIN                        |                        | 4133905 | 421658310 |   52 | 00:00:01 |
|  * 2 |    VIEW                            | TST_SNAPSHOTRELATION_V |     103 |      4532 |    4 | 00:00:01 |
|    3 |     WINDOW SORT                    |                        |     103 |      2266 |    4 | 00:00:01 |
|    4 |      TABLE ACCESS FULL             | TST_SNAPSHOT           |     103 |      2266 |    3 | 00:00:01 |
|    5 |    VIEW                            | TST_ITEMAPPEARANCE_V   |   40135 |   2327830 |   13 | 00:00:01 |
|  * 6 |     FILTER                         |                        |         |           |      |          |
|    7 |      HASH GROUP BY                 |                        |   40135 |   5498495 |   13 | 00:00:01 |
|    8 |       NESTED LOOPS                 |                        |         |           |      |          |
|    9 |        NESTED LOOPS                |                        |   40135 |   5498495 |   10 | 00:00:01 |
| * 10 |         TABLE ACCESS FULL          | TST_SNAPSHOT           |       1 |        22 |    3 | 00:00:01 |
|   11 |         INLIST ITERATOR            |                        |         |           |      |          |
| * 12 |          INDEX RANGE SCAN          | IDX_ITEMEVENT          |      11 |           |    3 | 00:00:01 |
|   13 |        TABLE ACCESS BY INDEX ROWID | TST_ITEMEVENT          |   38966 |   4481090 |    7 | 00:00:01 |
--------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("A"."SNAPSHOTID"="R"."PREVSNAPSHOTID")
* 2 - filter("R"."PREVSNAPSHOTID"=TO_NUMBER(:X))
* 6 - filter(CASE WHEN MAX(CASE "ME"."LINE" WHEN 'LINE-B' THEN "ME"."TIMEPOINT" END )>=
                       MAX(CASE "ME"."LINE" WHEN 'LINE-A' THEN "ME"."TIMEPOINT" END )
                       THEN 0 
                  WHEN MAX(CASE "ME"."LINE" WHEN 'LINE-A' 
                       THEN "ME"."TIMEPOINT" END ) IS NULL THEN 0 ELSE 1 END =1)
* 10 - filter("H"."SNAPSHOTID"=TO_NUMBER(:X))
* 12 - access(("ME"."LINE"='LINE-A' AND "ME"."TYPE"='Type14' OR "ME"."LINE"='LINE-B' AND "ME"."TYPE"='Type25') AND "ME"."TIMEPOINT"<"H"."TIMETARGET")

Пожалуйста, помогите мне понять, как заставить оптимизатор использовать разумный план выполнения.

...