Почему Oracle так медленно работает, когда я передаю java.sql.Timestamp для столбца DATE? - PullRequest
6 голосов
/ 22 декабря 2009

У меня есть таблица со столбцом DATE со временем (как обычно в Oracle, поскольку нет типа TIME). Когда я запрашиваю этот столбец из JDBC, у меня есть два варианта:

  • Вручную преобразовать значения с помощью Oracle to_date()
  • Используйте java.sql.Timestamp

Оба подхода работают и имеют исключительные области отвратительности. Моя проблема, когда я SELECT данные. Вот два примера запросов:

select *
from TABLE
where TS between {ts '2009-12-08 00:00:00.000'} and {ts '2009-12-09 00:00:00.000'}

select *
from TABLE
where TS between trunc({ts '2009-12-08 00:00:00.000'}) and trunc({ts '2009-12-09 00:00:00.000'})

Оба запроса работают, возвращают одинаковые результаты и выдают одинаковый результат в EXPLAIN PLAN. Это правильные индексы используются.

Только один запрос выполняется 15 минут, в то время как второй запрос занимает 0,031 с. Это почему? Есть ли какое-то центральное место, чтобы это исправить, или мне нужно проверить все мои запросы по этому столбцу и полностью убедиться, что там есть trunc()? Как я могу исправить эту проблему, когда мне нужно выбрать до определенной секунды?

[РЕДАКТИРОВАТЬ] Таблица разделена, и я нахожусь на Oracle 10.2.0.

Ответы [ 4 ]

4 голосов
/ 22 августа 2011

Это связано с тем, что тип данных TIMESTAMP является более точным, чем DATE, поэтому, когда вы передаете значение параметра TIMESTAMP в условие столбца DATE, Oracle должен преобразовать все значения DATE в TIMESTAMP, чтобы выполнить сравнение (это использованное выше использование INTERNAL_FUNCTION), и поэтому индекс имеет быть полностью отсканированным.

3 голосов
/ 08 июля 2011

У меня здесь похожая проблема:

Незначительная разница в плане выполнения с Oracle при использовании jdbc Timestamp или Date

В моем примере это сводится к тому факту, что при использовании метки времени JDBC, INTERNAL_FUNCTION применяется к столбцу фильтра, а не к переменной bind. Таким образом, индекс больше нельзя использовать для RANGE SCANS или UNIQUE SCANS:

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

Решение этой проблемы внутри сторонних API

Кстати, эту проблему также можно решить в сторонних API, например, в Hibernate:

или в jOOQ:

3 голосов
/ 22 декабря 2009

Я не понимаю, что на самом деле означает {ts '2009-12-08 00: 00: 00.000'}, поскольку, насколько я знаю, это не Oracle SQL. Можете ли вы точно показать, какой запрос вы используете?

Одной из возможных проблем является то, что вы указываете свой диапазон в миллисекундах. Тип DATE в Oracle сводится к секундам. (Используйте тип TIMESTAMP, если вам нужно хранить доли секунд). Но может случиться так, что в первом запросе Oracle преобразует каждое значение DATE в TIMESTAMP, чтобы выполнить сравнение с указанным вами TIMESTAMP. Во втором случае он знает, что TRUNC () будет эффективно округлять ваше значение до чего-то, что может быть выражено как DATE, поэтому преобразование не требуется.

Если вы хотите избежать таких неявных преобразований, убедитесь, что вы всегда сравниваете like с like. например,

select * 
from my_table t
where t.ts between to_date('2009-12-08','YYYY-MM-DD') and to_date('2009-12-09','YYYY-MM-DD')
2 голосов
/ 27 декабря 2009

У меня была такая проблема в проекте, и установка свойства соединения oracle.jdbc.V8Compatible = true устранила проблему.

Ссылка Дагмана говорит вам, как его установить:

Вы устанавливаете свойство соединения с помощью добавив его в java.util.Properties объект передан DriverManager.getConnection или для OracleDataSource.setConnectionProperties. Вы устанавливаете системное свойство с помощью включая опцию -D в вашей Java командная строка.

java -Doracle.jdbc.V8Compatible = "true" MyApp

Примечание для 11g, и это свойство, очевидно, не используется.

С http://forums.oracle.com/forums/thread.jspa?messageID=1659839:

Еще одна заметка для тех, кто используя 11gR1 (и на) JDBC тонкий драйвер: совместимое соединение V8 собственности больше не существует, поэтому вы не можете положитесь на это, чтобы отправить java.sql. Timestamp в качестве SQLDATE. Какие Вы можете сделать, однако, это позвонить:

setObject(i, aTimestamp, java.sql.Types.DATE) sends data as SQLDATE
setObject(i, aDate) sends data as SQLDATE
setDate(i, aDate) sends data as SQLDATE
setDATE(i, aDATE) (non standard) sends data as SQLDATE

setObject(i, aTimestamp) sends data as SQLTIMESTAMP
setTimestamp(i, aTimestamp) sends data as SQLTIMESTAMP
setObject(i, aTimestamp) sends data as SQLTIMESTAMP
setTIMESTAMP(i, aTIMESTAMP) (non standard) sends data as SQLTIMESTAMP
...