2013-04-20 9 views
6

W końcu zadaję moje pierwsze pytanie (chociaż jestem długoletnim prześladowcą).SQL WHERE xx IN (wydajność 'qq', 'ww' ...) - więcej wartości, mniej czasu?

Zapytanie SQL zwróciło moją uwagę na drugi dzień w pracy. Problemem jest wydajność w klauzuli WHERE podczas porównywania indeksu do możliwych wartości przy użyciu operatora IN.

SELECT SUM (parts.quantity) AS quantity, 
     concessions.concessionCode, 
     concessions.description AS concessionDesc, 
     parts.type, 
     activities.activityCode, 
     REPLACE (activities.activityCode, activities.lvl2 || '-', '') AS activityCodeDisplay, 
     strings.activityDesc, 
     strings.activityDesc2, 
     strings.activityDesc3 
FROM tb_parts parts, 
     tb_activities activities, 
     tb_strings strings, 
     tb_concessions concessions 
WHERE  parts.activityCode = activities.activityCode 
     AND parts.concessionCode = activities.concessionCode 
     AND activities.concessionCode = concesions.concessionCode 
     AND activities.concessionCode = strings.concessionCode 
     AND activities.activityCode = strings.activityCode 
     AND strings.language = 'ENG' 
     --AND parts.concesionCode IN ('ZD', 'G9', 'TR', 'JS0') 
     AND parts.concesionCode IN ('ZD', 'G9') 
     AND parts.date >= TO_DATE ('01/01/2013 00:00:00', 'DD/MM/YYYY HH24:MI:SS') 
     AND parts.date <= TO_DATE ('30/04/2013 23:59:59', 'DD/MM/YYYY HH24:MI:SS') 
     AND parts.type IN ('U', 'M') 
     AND parts.value = 'E' 
GROUP BY concesions.concessionCode, 
     concesions.description, 
     parts.type, 
     activities.activityCode, 
     REPLACE (activities.activityCode, activities.lvl2|| '-', ''), 
     strings.activityDesc, 
     strings.activityDesc2, 
     strings.activityDesc3 
ORDER BY concesions.concessionCode; 

Problem, który mam jest to - jeśli zapytanie jest prowadzony jak to jest (z dwoma wartościami dla IN), trwa 30s. Jeśli jest uruchamiany z czterema wartościami (jak to jest w linii komentarza), zapytanie trwa 5 sekund. Spodziewam się, że porównanie indeksu z wieloma wartościami zajmie więcej czasu, ale wydaje się, że tak nie jest. Powtórzyłem "test" kilka razy w ciągu dnia i zawsze są one mniej więcej takie same (30 + -1 s, 5 + -1 s).

Każdy wgląd w to, dlaczego zachowuje się w taki sposób, byłby bardziej niż ceniony!

P.S. Przetłumaczyłem nazwy tabel/kolumn tak przepraszam, jeśli są jakieś rozbieżności.

P.P.S. Napisałem ten kod z łączeniami i jest o wiele szybszy, ale powód tej anomalii wciąż mnie dręczy :)

EDIT: Wreszcie w pracy! Po pewnym majsterkowaniu udało mi się stworzyć plany wykonania dla tych dwóch wersji, a nawet dla trzeciej wersji zapytania (z zarówno wartości 4 i 2 w where, czas wynosi około 600 ms). Ponadto, było kilka pytań dotyczących danych zawartych w tabelach, więc oto kilka informacji:

All the stats are analyzed the day that queries were executed 

Table parts 
      total rows   - 3.2 M 
      matches for 2 values - 1.08 M (~34%) 
      matches for 4 values - 1.30 M (~41%) 
Table activities 
      total rows   - 3866 
      matches for 2 values - 321 (~ 8%) 
      matches for 4 values - 644 (~16%) 
Table strings 
      total rows   - 7436 
      matches for 2 values - 642 (~ 8%) 
      matches for 4 values - 1288 (~17%) 

Index in_parts 
      codConcession 
      username 
      date 

W związku z tym, myślę, że nie istnieje zasadnicza różnica (oprócz + 2/3s) przy użyciu dynamicznego próbkowania (jeśli zrobiłem to poprawnie, to znaczy, ze /*+ dynamic_sampling(tb_parts 10) */ po słowie kluczowym SELECT)

Dla dwóch wartości:

----------------------------------------------------------------------------------------------------- 
| Id | Operation       | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
----------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT     |    |  1 | 186 | 864 (1)| 00:00:11 | 
| 1 | SORT ORDER BY      |    |  1 | 186 | 864 (1)| 00:00:11 | 
| 2 | HASH GROUP BY     |    |  1 | 186 | 864 (1)| 00:00:11 | 
|* 3 | TABLE ACCESS BY INDEX ROWID  | tb_parts  |  1 | 37 | 818 (1)| 00:00:10 | 
| 4 |  NESTED LOOPS     |    |  1 | 186 | 862 (1)| 00:00:11 | 
| 5 |  NESTED LOOPS     |    |  1 | 149 | 44 (0)| 00:00:01 | 
| 6 |  NESTED LOOPS     |    | 34 | 2108 | 10 (0)| 00:00:01 | 
| 7 |  INLIST ITERATOR    |    |  |  |   |   | 
| 8 |   TABLE ACCESS BY INDEX ROWID| tb_concesions |  2 | 54 |  2 (0)| 00:00:01 | 
|* 9 |   INDEX UNIQUE SCAN   | pk_concession |  2 |  |  1 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS BY INDEX ROWID | tb_activities | 17 | 595 |  4 (0)| 00:00:01 | 
|* 11 |   INDEX RANGE SCAN   | pk_activity | 17 |  |  2 (0)| 00:00:01 | 
| 12 |  TABLE ACCESS BY INDEX ROWID | tb_strings  |  1 | 87 |  1 (0)| 00:00:01 | 
|* 13 |  INDEX UNIQUE SCAN   | pk_string  |  1 |  |  0 (0)| 00:00:01 | 
|* 14 |  INDEX RANGE SCAN    | in_parts  | 454 |  | 648 (1)| 00:00:08 | 
----------------------------------------------------------------------------------------------------- 

PLAN_TABLE_OUTPUT                      
------------------------------------------------------------------------------------------------------ 

Predicate Information (identified by operation id):             
---------------------------------------------------             
    3 - filter("parts"."value"='E' 
       AND ("parts"."type"='M' OR "parts"."type"='U') 
       AND "parts"."activityCode"="activities"."activityCode") 

    9 - access("concessions"."concessionCode"='G9' 
       OR "concessions"."concessionCode"='ZD') 

    11 - access("activities"."concessionCode"="concessions"."concessionCode")       
     filter("activities"."concessionCode"='G9' 
       OR "activities"."concessionCode"='ZD')    

    13 - access("activities"."concessionCode"="strings"."concessionCode" 
       AND "activities"."activityCode"="strings"."activityCode" 
       AND "strings"."language"='ENG')  
     filter("strings"."concessionCode"='G9' 
       OR "strings"."concessionCode"='ZD')      

    14 - access("parts"."concessionCode"="activities"."concessionCode" 
       AND "parts"."date">=TO_DATE('2013-01-01 00:00:00', 
              'syyyy-mm-dd hh24:mi:ss') 
       AND "parts"."date"<=TO_DATE(' 2013-04-30 23:59:59', 
              'syyyy-mm-dd hh24:mi:ss'))             
     filter("parts"."date">=TO_DATE('2013-01-01 00:00:00', 
             'syyyy-mm-dd hh24:mi:ss')    
       AND ("parts"."concessionCode"='G9' 
        OR "parts"."concessionCode"='ZD') 
       AND "parts"."date"<=TO_DATE(' 2013-04-30 23:59:59', 
              'syyyy-mm-dd hh24:mi:ss'))    

Dla czterech wartości:

---------------------------------------------------------------------------------------------------- 
| Id | Operation       | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
---------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT     |    |  1 | 186 | 7412 (2)| 00:01:29 | 
| 1 | SORT ORDER BY     |    |  1 | 186 | 7412 (2)| 00:01:29 | 
| 2 | HASH GROUP BY     |    |  1 | 186 | 7412 (2)| 00:01:29 | 
| 3 | NESTED LOOPS     |    |  1 | 186 | 7410 (2)| 00:01:29 | 
|* 4 |  HASH JOIN      |    | 17 | 1683 | 7393 (2)| 00:01:29 | 
|* 5 |  HASH JOIN     |    | 136 | 8432 | 21 (5)| 00:00:01 | 
| 7 |  TABLE ACCESS BY INDEX ROWID| tb_concesions |  4 | 108 |  2 (0)| 00:00:01 | 
|* 8 |   INDEX UNIQUE SCAN   | pk_concession |  4 |  |  1 (0)| 00:00:01 | 
|* 9 |  TABLE ACCESS FULL   | tb_activities | 644 | 22540 | 18 (0)| 00:00:01 | 
|* 10 |  TABLE ACCESS FULL   | tb_parts  | 4310 | 155K| 7372 (2)| 00:01:29 | 
| 11 |  TABLE ACCESS BY INDEX ROWID | tb_strings  |  1 | 87 |  1 (0)| 00:00:01 | 
|* 12 |  INDEX UNIQUE SCAN   | pk_string  |  1 |  |  0 (0)| 00:00:01 | 
---------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id):             

PLAN_TABLE_OUTPUT                     
---------------------------------------------------------------------------------------------------- 
---------------------------------------------------             

    4 - access("parts"."activityCode"="activities"."activityCode" 
       AND "parts"."concessionCode"="activities"."concessionCode")        

    5 - access("activities"."concessionCode"="concessions"."concessionCode")       

    8 - access("concessions"."concessionCode"='G9' 
       OR "concessions"."concessionCode"='JS0' 
       OR "concessions"."concessionCode"='TR' 
       OR "concessions"."concessionCode"='ZD')   

    9 - filter("activities"."concessionCode"='G9' 
       OR "activities"."concessionCode"='JS0' 
       OR "activities"."concessionCode"='TR' 
       OR "activities"."concessionCode"='ZD')    
    10 - filter("parts"."date">=TO_DATE(' 2013-01-01 00:00:00', 
             'syyyy-mm-dd hh24:mi:ss')    
       AND "parts"."value"='E' 
       AND ("parts"."type"='M' OR "parts"."type"='U') 
       AND ("parts"."concessionCode"='G9' 
        OR "parts"."concessionCode"='JS0' 
        OR "parts"."concessionCode"='TR' 
        OR "parts"."concessionCode"='ZD') 
       AND "parts"."date"<=TO_DATE(' 2013-04-30 23:59:59',  
              'syyyy-mm-dd hh24:mi:ss'))                

    12 - access("activities"."concessionCode"="strings"."concessionCode" 
       AND "activities"."activityCode"="strings"."activityCode" 
       AND "strings"."language"='ENG') 
     filter("strings"."concessionCode"='G9' 
       OR "strings"."concessionCode"='JS0' 
       OR "strings"."concessionCode"='TR' 
       OR "strings"."concessionCode"='ZD')     

I wreszcie sześć wartości:

---------------------------------------------------------------------------------------------------- 
| Id | Operation       | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
---------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT     |    |  1 | 186 | 4525 (1)| 00:00:55 | 
| 1 | SORT ORDER BY     |    |  1 | 186 | 4525 (1)| 00:00:55 | 
| 2 | HASH GROUP BY     |    |  1 | 186 | 4525 (1)| 00:00:55 | 
| 3 | NESTED LOOPS     |    |  1 | 186 | 4523 (1)| 00:00:55 | 
|* 4 |  HASH JOIN      |    |  9 | 891 | 4514 (1)| 00:00:55 | 
|* 5 |  HASH JOIN     |    | 136 | 8432 | 21 (5)| 00:00:01 | 
| 6 |  INLIST ITERATOR    |    |  |  |   |   | 
| 7 |  TABLE ACCESS BY INDEX ROWID| tb_concesions |  4 | 108 |  2 (0)| 00:00:01 | 
|* 8 |   INDEX UNIQUE SCAN   | pk_concession |  4 |  |  1 (0)| 00:00:01 | 
|* 9 |  TABLE ACCESS FULL   | tb_activities | 644 | 22540 | 18 (0)| 00:00:01 | 
| 10 |  INLIST ITERATOR    |    |  |  |   |   | 
|* 11 |  TABLE ACCESS BY INDEX ROWID | tb_parts  | 2155 | 79735 | 4493 (1)| 00:00:54 | 
|* 12 |  INDEX RANGE SCAN   | in_parts  | 8620 |  | 1277 (1)| 00:00:16 | 
| 13 |  TABLE ACCESS BY INDEX ROWID | tb_strings  |  1 | 87 |  1 (0)| 00:00:01 | 
|* 14 |  INDEX UNIQUE SCAN   | pk_string  |  1 |  |  0 (0)| 00:00:01 | 
---------------------------------------------------------------------------------------------------- 

PLAN_TABLE_OUTPUT                     
---------------------------------------------------------------------------------------------------- 

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

    4 - access("parts"."activityCode"="activities"."activityCode" 
    AND "parts"."concessionCode"="activities"."concessionCode") 

    5 - access("activities"."concessionCode"="concessions"."concessionCode")       

    8 - access("concessions"."concessionCode"='G9' 
       OR "concessions"."concessionCode"='JS0' 
       OR "concessions"."concessionCode"='TR' 
       OR "concessions"."concessionCode"='ZD')   

    9 - filter("activities"."concessionCode"='G9' 
       OR "activities"."concessionCode"='JS0' 
       OR "activities"."concessionCode"='TR' 
       OR "activities"."concessionCode"='ZD')    

    11 - filter("parts"."value"='E' 
       AND ("parts"."type"='M' OR "parts"."type"='U'))     

    12 - access(("parts"."concessionCode"='G9' 
       OR "parts"."concessionCode"='ZD') 
       AND "parts"."date">=TO_DATE(' 2013-01-01 00:00:00', 
              'syyyy-mm-dd hh24:mi:ss') 
       AND "parts"."date"<=TO_DATE(' 2013-04-30 23:59:59', 
              'syyyy-mm-dd hh24:mi:ss'))    
     filter("parts"."date">=TO_DATE(' 2013-01-01 00:00:00', 
             'syyyy-mm-dd hh24:mi:ss')    
       AND "parts"."date"<=TO_DATE(' 2013-04-30 23:59:59', 
              'syyyy-mm-dd hh24:mi:ss'))   

    14 - access("activities"."concessionCode"="strings"."concessionCode" 
       AND "activities"."activityCode"="strings"."activityCode" 
       AND "strings"."language"='ENG') 
     filter("strings"."concessionCode"='G9' 
       OR "strings"."concessionCode"='JS0' 
       OR "strings"."concessionCode"='TR' 
       OR "strings"."concessionCode"='ZD') 

Ponieważ jest to moje pierwsze spotkanie z planu wykonania, mogę się tylko domyślać, co jest przyczyną opóźnienia. Pomiędzy 4 a 6 wartościami domyślam się, że jest to zmiana z PEŁNEGO DOSTĘPU DO DOSTĘPU PRZEZ INDEKS. Ponadto podczas uzyskiwania dostępu do tabeli filtr dla czterech wartości (id10) zawiera wszystkie cztery wartości koncesji; podczas gdy dla sześciu wartości dwie wartości koncesji są w części dostępowej, a filtr zawiera tylko datę, typ i wartość.

+2

Co mówi plan wykonania? –

+2

Pierwszą rzeczą do sprawdzenia jest porównanie planów wykonywania zapytań. Najprawdopodobniej z dwiema wartościami plan zacznie się od jakiegoś indeksu na koncesji, który działa źle dla określonych wartości. Z czterema, idzie w inny sposób i kończy się lepiej. – RichardTheKiwi

+2

Czy statystyki są aktualne (w szczególności w tabeli "części")? Jakie są relatywne populacje dla różnych wartości parametru parts.concesionCode? Jeśli możesz opublikować dwa plany zapytań - powinniśmy się spodziewać, że będą różniły się, prawdopodobnie z powodu wprowadzających w błąd statystyk. –

Odpowiedz

0

Ogólnie powodem takich anomalii jest to, że optymalizator zapytań nie może dokładnie przewidzieć kosztów. Jedynym sposobem na dokładne poznanie kosztu byłoby kilkukrotne wykonanie polecenia z różnymi planami wykonania. Zamiast tego używa statystyk do oszacowania szacunkowej wartości, a czasem szacunek jest błędny.

Porównując plany wyłączeń "z dwiema wartościami" z "czterema wartościami", można zauważyć, że te ostatnie generują wyższe koszty, a plan jest zupełnie inny. Optymalizator miał wybór między tymi dwoma planami wykonania i musiał pomyśleć, że pierwsze jest lepsze z dwiema wartościami, a drugie jest lepsze z czterema wartościami. Jednak w rzeczywistości drugi był lepszy w obu przypadkach.

Jeśli przeanalizujesz takie anomalie ostrożnie, często kończysz na spostrzeżeniach, takich jak pewna kombinacja wartości, jest nadreprezentowana lub niedostatecznie reprezentowana w twoich danych. Korzystanie z histogramów w statystykach daje optymalizatorowi więcej wskazówek i lepiej radzi sobie z "wypaczonymi danymi", ale jego możliwości prognostyczne będą nadal ograniczone.

W praktyce rozwiązaniem jest to, co zrobiłeś: przepisaj kod SQL, aż uzyskasz akceptowalną wydajność. Często "Wskazówki" (w Oracle) mogą również dać optymalizatorowi więcej wskazówek.

+0

Ładnie podsumowałem i dzięki za informację o podpowiedziach (tak jak mówiłem, nowicjusz SQL tutaj :)) – Archduke