Поведение Orcale с левым внешним соединением или условием - PullRequest
1 голос
/ 09 марта 2019

Недавно я столкнулся со странностью в поведении Oracle12c (12.2.0.1) с или -условными левыми объединениями.

У нас есть две схематически идентичные базы данных с небольшими различиями в данных, одна из которых является базой данных среды тестирования, а другая - производственной базой данных. Параметры оптимизатора одинаковы в обеих базах данных.

Учитывая запрос:

SELECT A.Id as ObjectID,A.GlobalId as GlobalId,A.Type as Type,
(SELECT listagg(CLASS, ',') within group (order by CLASS) from D_VIEW D_SEPARATE_QUERY 
 where D_SEPARATE_QUERY.A_ID = A.OBJECTID) as D_CLASS_LIST, 
Max(CASE WHEN (D.REASON = 1) Then 1 Else 0 END) as CONDITION_1,
Max(CASE WHEN (D.REASON in  (2, 3, 4, 5, 6, 7, 8, 9, 504)) Then 1 Else 0 END) as CONDITION_2
FROM A_VIEW A
LEFT OUTER JOIN B_VIEW B on B.A_GLOBALID = A.GLOBALID 
LEFT OUTER JOIN C_VIEW C on C.B_GLOBALID = B.GLOBALID 
LEFT OUTER JOIN D_VIEW D on
    D.C_ID = C.OBJECTID or 
    D.A_ID = A.OBJECTID

WHERE 1 = 1 AND A.TYPE=:TYPE0 
GROUP BY A.ObjectID

В нашей тестовой среде план выполнения выглядит следующим образом:


| Id  | Operation                                | Name                | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |                     |   207K|   439M|       |  5109M  (4)|110:52:41 |
|   1 |  HASH GROUP BY                           |                     |   207K|   439M|  4872M|  5109M  (4)|110:52:41 |
|   2 |   MERGE JOIN OUTER                       |                     |  2182K|  4631M|       |  5108M  (4)|110:52:15 |
|*  3 |    HASH JOIN OUTER                       |                     |  2182K|  4604M|   449M|   290K  (2)| 00:00:23 |
|*  4 |     HASH JOIN OUTER                      |                     |  2182K|   424M|   113M|   244K  (2)| 00:00:20 |
|*  5 |      HASH JOIN OUTER                     |                     |   741K|   104M|    41M| 56975   (2)| 00:00:05 |
|*  6 |       TABLE ACCESS BY INDEX ROWID BATCHED| A                   |   610K|    34M|       | 37393   (2)| 00:00:03 |
|*  7 |        INDEX RANGE SCAN                  | I1439TYPE           |  1627K|       |       |  2801   (3)| 00:00:01 |
|*  8 |       INDEX FAST FULL SCAN               | IDX$$_973C0001      |  2785K|   236M|       |  6227   (3)| 00:00:01 |
|   9 |      VIEW                                | index$_join$_009    |  7908K|   422M|       |   159K  (2)| 00:00:13 |
|* 10 |       HASH JOIN                          |                     |       |       |       |            |          |
|* 11 |        INDEX RANGE SCAN                  | IDX$$_973C0002      |  7908K|   422M|       | 54686   (2)| 00:00:05 |
|* 12 |        INDEX FAST FULL SCAN              | GDB_CT2_265         |  7908K|   422M|       | 67651   (2)| 00:00:06 |
|  13 |     VIEW                                 | VW_SSQ_1            |   285K|   547M|       |  3965   (6)| 00:00:01 |
|  14 |      SORT GROUP BY                       |                     |   285K|  2511K|    19M|  3965   (6)| 00:00:01 |
|  15 |       TABLE ACCESS FULL                  | D                   |   995K|  8753K|       |  2320   (3)| 00:00:01 |
|  16 |    BUFFER SORT                           |                     |     1 |    13 |       |  5109M  (4)|110:52:41 |
|  17 |     VIEW                                 | VW_LAT_DAF4C663     |     1 |    13 |       |  2340   (3)| 00:00:01 |
|* 18 |      TABLE ACCESS FULL                   | D                   |     1 |   121 |       |  2340   (3)| 00:00:01 |
------------------------------------------------------------------------------------------------------------------------

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

   3 - access("ITEM_1"(+)="OBJECTID")
   4 - access("B_GLOBALID"(+)="GLOBALID")
   5 - access("A_GLOBALID"(+)="GLOBALID")
   6 - filter("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
   7 - access("TYPE"=TO_NUMBER(:TYPE0))
   8 - filter("GDB_TO_DATE"(+)=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  10 - access(ROWID=ROWID)
  11 - access("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  12 - filter("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  18 - filter("D"."A_ID"="OBJECTID" OR "D"."C_ID"="OBJECTID")

И план выполнения производственной среды:

    -------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name                | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |                     |   303K|   643M|       |    14M  (1)| 00:18:57 |
|   1 |  HASH GROUP BY                            |                     |   303K|   643M|     9G|    14M  (1)| 00:18:57 |
|   2 |   MERGE JOIN OUTER                        |                     |  4509K|  9568M|       |    13M  (1)| 00:18:03 |
|*  3 |    HASH JOIN OUTER                        |                     |  2254K|  4756M|   464M|   310K  (2)| 00:00:25 |
|*  4 |     HASH JOIN OUTER                       |                     |  2254K|   438M|   121M|   257K  (2)| 00:00:21 |
|*  5 |      HASH JOIN OUTER                      |                     |   797K|   112M|    43M| 62800   (2)| 00:00:05 |
|*  6 |       TABLE ACCESS BY INDEX ROWID BATCHED | A                   |   643K|    36M|       | 41711   (2)| 00:00:04 |
|*  7 |        INDEX RANGE SCAN                   | I1439TYPE           |  1830K|       |       |  3213   (3)| 00:00:01 |
|*  8 |       INDEX FAST FULL SCAN                | IDX$$_973C0001      |  2965K|   251M|       |  6889   (3)| 00:00:01 |
|   9 |      VIEW                                 | index$_join$_009    |  8125K|   433M|       |   166K  (2)| 00:00:14 |
|* 10 |       HASH JOIN                           |                     |       |       |       |            |          |
|* 11 |        INDEX RANGE SCAN                   | IDX$$_973C0002      |  8125K|   433M|       | 56959   (1)| 00:00:05 |
|* 12 |        INDEX FAST FULL SCAN               | GDB_CT2_265         |  8125K|   433M|       | 70534   (2)| 00:00:06 |
|  13 |     VIEW                                  | VW_SSQ_1            |   334K|   640M|       |  4902   (6)| 00:00:01 |
|  14 |      SORT GROUP BY                        |                     |   334K|  2939K|    23M|  4902   (6)| 00:00:01 |
|  15 |       TABLE ACCESS FULL                   | D                   |  1203K|    10M|       |  2926   (3)| 00:00:01 |
|  16 |    BUFFER SORT                            |                     |     2 |    26 |       |    14M  (1)| 00:18:57 |
|  17 |     VIEW                                  | VW_LAT_DAF4C663     |     2 |    26 |       |     6   (0)| 00:00:01 |
|  18 |      VIEW                                 | VW_ORE_DF336D34     |     2 |    26 |       |     6   (0)| 00:00:01 |
|  19 |       UNION-ALL                           |                     |       |       |       |            |          |
|  20 |        TABLE ACCESS BY INDEX ROWID BATCHED| D                   |     1 |     9 |       |     3   (0)| 00:00:01 |
|* 21 |         INDEX RANGE SCAN                  | G1126CID            |     1 |       |       |     2   (0)| 00:00:01 |
|* 22 |        TABLE ACCESS BY INDEX ROWID BATCHED| D                   |     1 |    15 |       |     3   (0)| 00:00:01 |
|* 23 |         INDEX RANGE SCAN                  | G1126AID            |     1 |       |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------------------

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

   3 - access("ITEM_1"(+)="OBJECTID")
   4 - access("B_GLOBALID"(+)="GLOBALID")
   5 - access("A_GLOBALID"(+)="GLOBALID")
   6 - filter("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
   7 - access("TYPE"=TO_NUMBER(:TYPE0))
   8 - filter("GDB_TO_DATE"(+)=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  10 - access(ROWID=ROWID)
  11 - access("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  12 - filter("GDB_TO_DATE"=TIMESTAMP' 9999-12-31 23:59:59.000000000')
  21 - access("D"."C_ID"="OBJECTID")
  22 - filter(LNNVL("D"."C_ID"="OBJECTID"))
  23 - access("D"."A_ID"="OBJECTID")

Как вы можете видеть, наша производственная Oracle на самом деле понимает, что условие или условие последнего левого соединения может быть расширено, и выполняет lnnvl-фильтр для индексированного доступа в обоих условиях, который всегда должен быть НАМНОГО более эффективным, чем полный доступ к таблице с нашим объемы данных. В нашей тестовой среде Oracle, похоже, так не считает, и всегда выполняет полное сканирование таблицы для последнего соединения, что абсолютно снижает производительность запроса, примерно в 100 раз замедляя его выполнение.

Откуда может возникнуть эта разница в поведении?

PS Я знаю, что условное левое соединение может быть переписано в два отдельных объединения для индексированного доступа при обоих условиях, но в данный момент я более склонен выяснить причину различия в поведении и можно ли это исправить в Сам Оракул.

ОБНОВЛЕНИЕ 1:

ПОКАЗАТЬ ОПТИМИЗАТОР ПАРАМЕТРОВ:

NAME                                               TYPE        VALUE                                                                                                
-------------------------------------------------- ----------- ---------------------------------------------------------------------------------------------------- 
optimizer_adaptive_plans                           boolean     TRUE                                                                                                 
optimizer_adaptive_reporting_only                  boolean     FALSE                                                                                                
optimizer_adaptive_statistics                      boolean     FALSE                                                                                                
optimizer_capture_sql_plan_baselines               boolean     FALSE                                                                                                
optimizer_dynamic_sampling                         integer     4                                                                                                    
optimizer_features_enable                          string      12.2.0.1                                                                                             
optimizer_index_caching                            integer     0                                                                                                    
optimizer_index_cost_adj                           integer     100                                                                                                  
optimizer_inmemory_aware                           boolean     TRUE                                                                                                 
optimizer_mode                                     string      ALL_ROWS                                                                                             
optimizer_secure_view_merging                      boolean     TRUE                                                                                                 
optimizer_use_invisible_indexes                    boolean     FALSE                                                                                                
optimizer_use_pending_statistics                   boolean     FALSE                                                                                                
optimizer_use_sql_plan_baselines                   boolean     TRUE  
...