2011-07-07 18 views
11

Estoy analizando los planes de ejecución de Oracle y encontré un hecho sorprendente. Mira esta consulta. La sugerencia es sólo para mostrar que tengo un índice y yo esperaría que Oracle pueda usarlo para escaneo de rangos:Diferencia de plan de ejecución no despreciable con Oracle al usar jdbc Timestamp o Fecha

// 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 < ?"); 

Estos dos fijaciones como resultado un comportamiento completamente diferente (para excluir temas Echar un vistazo variable de enlace, en realidad forzadas dos-análisis sintácticos duras):

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

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

1) con marcas de tiempo, consigo un INDEX FULL SCAN y por lo tanto un filtro predicado

-------------------------------------------------------------- 
| 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) Con fechas, tengo la gran INDEX RANGE SCAN mejor acceso y un predicado

-------------------------------------------------------------- 
| 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) 

Ahora mi ejemplo es sólo un ejemplo. La consulta real es mucho más compleja, donde es esencial tener RANGE SCANS o UNIQUE SCANS (según el predicado) en lugar de FULL SCANS.

¿Hay algo que esté malentendiendo aquí? ¿Alguien puede indicarme la mejor solución/práctica? Porque en el mundo de Java, creo que java.sql.Timestamp es mucho más adecuado, pero la mayoría de nuestras columnas son del tipo DATE de Oracle. Estamos usando Java 6 y Oracle 11g

+0

Nota, me encontré con una pregunta similar aquí: http://stackoverflow.com/questions/1945603/why- is-oracle-so-slow-when-i-pass-a-java-sql-timestamp-for-a-date-column/6620643 –

Respuesta

14

El hecho es que las marcas de tiempo de Oracle y las fechas de Oracle son dos tipos de datos diferentes. Para comparar una marca de tiempo con una fecha Oracle tiene que ejecutar una conversión, esa INTERNAL_FUNCTION(). La decisión de diseño interesante es que Oracle convierte la columna de la tabla en lugar del valor pasado, lo que significa que la consulta ya no utiliza el índice.

He podido reproducir su escenario en SQL * Plus, por lo que no es un problema con el uso de java.sql.Timestamp. Fundir las marcas de tiempo pasados ​​a fechas no resuelve el problema ...

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> 

Pero no creo que ayuda a cualquiera: sería más fácil simplemente pasar las fechas en su lugar.


Curiosamente, crear un índice basado en funciones para asignar las columnas de fecha a las indicaciones de tiempo no ayuda. La llamada INTERNAL_FUNCTION() no se reconoce como CAST() y el índice se ignora. Intentar crear un índice usando INTERNAL_FUNCTION() arroja un ORA-00904.

+1

La conversión sería posible, porque utilizamos una capa de abstracción de base de datos para todas las consultas, similar a http://www.jooq.org. Pero me temo que el lanzamiento podría introducir nuevos problemas con los índices basados ​​en funciones. Consulte esta pregunta aquí: http://stackoverflow.com/questions/5340738/oracle-execution-plans-when-using-the-like-operator-with-a-terministic-function. Así que sí, probablemente, teniendo en cuenta que solo tenemos columnas 'DATE' en la base de datos, probablemente deberíamos transformar las fechas" last-minute "de' java.sql.Timestamp' a 'java.sql.Date' antes de enlazar en la abstracción de la base de datos layer ... –

+1

Usando 'java.sql.Timestamp' en Java, pero el enlace' java.sql.Date' hace el truco. Los planes de ejecución ahora muestran el uso del índice ... Esperemos que esto no haya roto otras cosas ... :) –

+1

Desafortunadamente, en algunos casos, Oracle (JDBC, probablemente) trunca la parte de tiempo del 'java.sql.Date '. Especialmente con las sentencias 'INSERT' y' UPDATE', esto puede ser bastante incorrecto ...: -/ –

0

Mientras APC's answer ya se explica suficientemente por qué ocurre esto, las siguientes entradas del blog son interesantes en el caso que estamos tratando de resolver este problema con JPA e Hibernate:

O con JDBC o jOOQ:

En particular, una posible solución es simplemente pasar oracle.sql.DATE en lugar de cualquier tipo java.sql a la PreparedStatement

Cuestiones relacionadas