Незначительная разница в плане выполнения с Oracle при использовании jdbc Timestamp или Date - PullRequest
12 голосов
/ 07 июля 2011

Я анализирую планы выполнения 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

Ответы [ 2 ]

15 голосов
/ 07 июля 2011

Итак, отметки времени Oracle и даты 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() сбрасывает ORA-00904.

2 голосов
/ 29 декабря 2014

Хотя ответ APC уже достаточно объясняет, почему это происходит, следующие посты в блоге интересны, если вы пытаетесь решить эту проблему с помощью JPA и Hibernate:

Или с JDBC или jOOQ:

В частности, возможное решение состоит в том, чтобы просто передать oracle.sql.DATE вместо любого типа java.sql в PreparedStatement

...