2011-07-07 11 views
11

Analizuję plany wykonania Oracle i odkryłem zadziwiający fakt. Sprawdź to zapytanie. Podpowiedź jest tylko do wyświetlania, że ​​mam indeksu i będę oczekiwać Oracle wykorzystać go do skanowania zakres:Nieistotna różnica w planie wykonania z Oracle przy użyciu znacznika czasu jdbc lub daty

// execute_at is of type DATE. 
PreparedStatement stmt = connection.prepareStatement(
    "SELECT /*+ index(my_table my_index) */ * " + 
    "FROM my_table " + 
    "WHERE execute_at > ? AND execute_at < ?"); 

Te dwa wiązania skutkują zupełnie innego zachowania (w celu wykluczenia powiązać zmiennej problemy zerkanie, faktycznie egzekwowane dwa twardych Analizuje):

// 1. with timestamps 
stmt.setTimestamp(1, start); 
stmt.setTimestamp(2, end); 

// 2. with dates 
stmt.setDate(1, start); 
stmt.setDate(2, end); 

1) za pomocą znaczników czasu, dostaję INDEX FULL SCAN a zatem orzeczenie filtr

-------------------------------------------------------------- 
| Id | Operation     | Name     | 
-------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |      | 
|* 1 | FILTER      |      | 
| 2 | TABLE ACCESS BY INDEX ROWID| my_table    | 
|* 3 | INDEX FULL SCAN   | my_index    | 
-------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
    1 - filter(:1<:2)" 
    3 - filter((INTERNAL_FUNCTION(""EXECUTE_AT"")>:1 AND 
       INTERNAL_FUNCTION(""EXECUTE_AT"")<:2)) 

2) z datami, otrzymuję dużo lepiej INDEX RANGE SCAN oraz dostęp orzecznik

-------------------------------------------------------------- 
| Id | Operation     | Name     | 
-------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |      | 
|* 1 | FILTER      |      | 
| 2 | TABLE ACCESS BY INDEX ROWID| my_table    | 
|* 3 | INDEX RANGE SCAN   | my_index    | 
-------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
    1 - filter(:1<:2)" 
    3 - access(""EXECUTE_AT"">:1 AND ""EXECUTE_AT""<:2) 

Teraz mój przykład jest tylko przykładem. Prawdziwe zapytanie jest o wiele bardziej skomplikowane, gdzie niezbędne jest posiadanie RANGE SCANS lub UNIQUE SCANS (w zależności od predykatu) zamiast FULL SCANS.

Czy jest tu coś, czego nie rozumiem? Czy ktoś może wskazać mi najlepsze rozwiązanie/praktykę? Ponieważ w świecie Java uważam, że java.sql.Timestamp jest o wiele bardziej odpowiedni, ale większość naszych kolumn ma typ Oracle: DATE. Używamy Java 6 i Oracle 11g

+0

Uwaga, znalazłem podobne pytanie tutaj: http://stackoverflow.com/questions/1945603/why- is-oracle-so-slow-when-i-pass-a-java-sql-timestamp-for-date-column/6620643 –

Odpowiedz

14

Chodzi o to, że znaczniki czasu Oracle i daty Oracle są dwoma różnymi typami danych. Aby porównać znacznik czasu z datą, w której Oracle musi przeprowadzić konwersję - INTERNAL_FUNCTION(). Interesująca decyzja projektowa jest taka, że ​​Oracle konwertuje kolumnę tabeli, a nie przekazaną wartość, co oznacza, że ​​zapytanie nie używa już indeksu.

Byłem w stanie odtworzyć scenariusz w SQL * Plus, więc nie jest to problem z używaniem java.sql.Timestamp. Odlewania przekazywane znaczniki czasu dat nie rozwiązuje problemu ...

SQL> explain plan for 
    2  select * from test1 
    3  where d1 > cast(to_timestamp('01-MAY-2011 00:00:00.000', 'DD-MON-YYYY Hh24:MI:SS.FF') as date) 
    4  and d2 > cast(to_timestamp('01-JUN-2011 23:59:59.999', 'DD-MON-YYYY Hh24:MI:SS.FF') as date) 
    5/

Explained. 

SQL> select * from table(dbms_xplan.display) 
    2/

PLAN_TABLE_OUTPUT 
----------------------------------------------------------- 
Plan hash value: 1531258174 

------------------------------------------------------------------------------------- 
| Id | Operation     | Name | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT   |  | 25 | 500 |  3 (0)| 00:00:01 | 
| 1 | TABLE ACCESS BY INDEX ROWID| TEST1 | 25 | 500 |  3 (0)| 00:00:01 | 
|* 2 | INDEX RANGE SCAN   | T1_I |  1 |  |  2 (0)| 00:00:01 | 
------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 

PLAN_TABLE_OUTPUT 
-----------------------------------------------------------------------------------  
    2 - access("D1">CAST(TO_TIMESTAMP('01-MAY-2011 00:00:00.000','DD-MON-YYYY 
       Hh24:MI:SS.FF') AS date) AND "D2">CAST(TO_TIMESTAMP('01-JUN-2011 
       23:59:59.999','DD-MON-YYYY Hh24:MI:SS.FF') AS date) AND "D1" IS NOT NULL) 
     filter("D2">CAST(TO_TIMESTAMP('01-JUN-2011 23:59:59.999','DD-MON-YYYY 
       Hh24:MI:SS.FF') AS date)) 

18 rows selected. 

SQL> 

Ale nie sądzę, że pomaga dowolny: łatwiej byłoby po prostu przejść zamiast dat.


Co ciekawe, budowanie opartego na funkcjach indeksu polegającego na przesyłaniu kolumn dat do znaczników czasu nie pomaga. Wywołanie INTERNAL_FUNCTION() nie jest rozpoznawane jako CAST(), a indeks jest ignorowany. Próba zbudowania indeksu przy użyciu INTERNAL_FUNCTION() ciska ORA-00904.

+1

Przesyłanie jest możliwe, ponieważ używamy warstwy abstrakcji bazy danych dla wszystkich zapytań, podobnie jak http://www.jooq.org. Obawiam się jednak, że casting może wprowadzić nowe problemy z indeksami funkcjonalnymi. Zobacz to pytanie tutaj: http://stackoverflow.com/questions/5340738/oracle-execution-plans-when-using-the-operator--z-zaznaczeniem-deterministycznym. Więc tak, prawdopodobnie biorąc pod uwagę, że mamy tylko kolumny 'DATE' w DB, prawdopodobnie powinniśmy przekształcić daty" last-minute "z' java.sql.Timestamp' na 'java.sql.Date' przed powiązaniem w abstrakcji bazy danych layer ... –

+1

Użycie 'java.sql.Timestamp' w Javie, ale powiązanie' java.sql.Date' robi lewę. Plany wykonania pokazują teraz użycie indeksu ... Miejmy nadzieję, że to nie zniszczyło innych rzeczy ... :) –

+1

Niestety, w niektórych przypadkach Oracle (prawdopodobnie JDBC) obcina część czasu 'java.sql.Date '. Zwłaszcza w przypadku instrukcji 'INSERT' i' UPDATE' może to być całkiem błędne ...: -/ –

0

Podczas APC's answer już dostatecznie wyjaśnia, dlaczego tak się dzieje, następujące blogi są interesujące w przypadku starasz się rozwiązać ten problem z JPA i Hibernate:

lub przy JDBC lub jOOQ:

W szczególności, możliwym rozwiązaniem jest po prostu przekazać oracle.sql.DATE zamiast dowolnego typu java.sql do PreparedStatement

Powiązane problemy