Незначительное различие плана выполнения с Oracle при использовании временной отметки jdbc или даты
Я анализирую планы исполнения Oracle и нашел удивительный факт. Проверьте этот запрос. Подсказка только для того, чтобы показать, что у меня есть индекс, и я ожидаю, что Oracle будет использовать его для сканирования диапазона:
// 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 < ?");
Эти два привязки приводят к совершенно другому поведению (чтобы исключить проблемы с подстановкой переменных привязки, я фактически применял два жестких анализа):
// 1. with timestamps
stmt.setTimestamp(1, start);
stmt.setTimestamp(2, end);
// 2. with dates
stmt.setDate(1, start);
stmt.setDate(2, end);
1) С отметками времени я получаю INDEX FULL SCAN
и, следовательно, предикат фильтра
--------------------------------------------------------------
| 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) С датами я получаю намного лучше INDEX RANGE SCAN
и предикат доступа
--------------------------------------------------------------
| 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)
Теперь мой пример - всего лишь пример. Реальный запрос намного сложнее, где важно иметь RANGE SCANS
или UNIQUE SCANS
(в зависимости от предиката), а не FULL SCANS
.
Я что-то недопонимаю здесь? Может ли кто-нибудь указать мне на лучшее решение/практику? Потому что в Java-мире я думаю, что java.sql.Timestamp
гораздо более подходит, но большинство наших столбцов имеют тип Oracle DATE
. Мы используем Java 6 и Oracle 11g
Ответы
Ответ 1
Итак, так, Oracle timestamps и даты Oracle представляют собой два разных типа данных. Чтобы сравнить временную метку с датой, Oracle должна выполнить преобразование - это INTERNAL_FUNCTION(). Интересным конструктивным решением является то, что Oracle преобразует столбец таблицы, а не переданное значение, что означает, что запрос больше не использует индекс.
Я смог воспроизвести ваш сценарий в SQL * Plus, так что это не проблема с использованием java.sql.Timestamp
. Приведение прошедших временных меток к датам устраняет проблему...
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>
Но я не думаю, что это поможет вам: вместо этого было бы проще просто провести даты.
Интересно, что создание индекса на основе функций, заставляющего столбцы даты использовать временные метки, не помогает. Вызов INTERNAL_FUNCTION()
не распознается как CAST()
, и индекс игнорируется. Попытка создать индекс с помощью INTERNAL_FUNCTION()
hurls ORA-00904.
Ответ 2
В то время как ответ APC уже достаточно объясняет, почему это происходит, следующие сообщения в блоге интересны, если вы пытаетесь решить эту проблему с JPA и Hibernate:
Или с помощью JDBC или jOOQ:
В частности, возможным решением является просто передать oracle.sql.DATE
вместо любого типа java.sql
в PreparedStatement