Как повысить производительность в Oracle с помощью SELECT DISTINCT
-
20-08-2019 - |
Вопрос
В настоящее время я работаю над развертыванием ERP на основе OFBIZ. Используемая база данных является Oracle 10G Enterprise
Одной из самых больших проблем являются некоторые проблемы с производительностью Oracle, при анализе журналов ofbiz, следующий запрос:
SELECT DISTINCT ORDER_ID, ORDER_TYPE_ID, ORDER_NAME, EXTERNAL_ID,
SALES_CHANNEL_ENUM_ID, ORDER_DATE, ENTRY_DATE, VISIT_ID, STATUS_ID, CREATED_BY,
FIRST_ATTEMPT_ORDER_ID, CURRENCY_UOM, SYNC_STATUS_ID, BILLING_ACCOUNT_ID,
ORIGIN_FACILITY_ID, WEB_SITE_ID, PRODUCT_STORE_ID, TERMINAL_ID, TRANSACTION_ID,
AUTO_ORDER_SHOPPING_LIST_ID, NEEDS_INVENTORY_ISSUANCE, IS_RUSH_ORDER, INTERNAL_CODE,
REMAINING_SUB_TOTAL, GRAND_TOTAL, LAST_UPDATED_STAMP, LAST_UPDATED_TX_STAMP, CREATED_STAMP,
CREATED_TX_STAMP, RECIBIR_BODEGAL, RECEPCIONADA_BODEGAL, FECHA_RECEPCION_BODEGAL FROM
ERP.ORDER_HEADER WHERE ((STATUS_ID = :v0 OR STATUS_ID = :v1 OR STATUS_ID = :v2) AND
(ORDER_TYPE_ID = :v3)) ORDER BY ORDER_DATE DESC
очень медленно.Мы протестировали выполнение запроса без DISTINCT, и это занимает около 30 секунд.В таблице 4.000.000+ регистров.Есть индекс для поля OrderId PK и почти для каждого другого поля.
EXPLAIN PLAN с DISTINCT:
SELECT STATEMENT () (null)
SORT (ORDER BY) (null)
HASH (UNIQUE) (null)
TABLE ACCESS (FULL) ORDER_HEADER
и без DISTINCT:
SELECT STATEMENT () (null)
SORT (ORDER BY) (null)
TABLE ACCESS (FULL) ORDER_HEADER
есть идеи по настройке Oracle для повышения производительности запросов такого типа?Очень сложно переписать запрос, потому что он автоматически генерируется OfBiz, поэтому я думаю, что решение о настройке Oracle
заранее спасибо
РЕДАКТИРОВАТЬ:Я проанализировал запрос с помощью tkprof, как предложили Роб ван Вейк и haffax, и результат следующий:
********************************************************************************
SELECT DISTINCT ORDER_ID, ORDER_TYPE_ID, ORDER_NAME, EXTERNAL_ID,
SALES_CHANNEL_ENUM_ID, ORDER_DATE, ENTRY_DATE, VISIT_ID, STATUS_ID, CREATED_BY,
FIRST_ATTEMPT_ORDER_ID, CURRENCY_UOM, SYNC_STATUS_ID, BILLING_ACCOUNT_ID,
ORIGIN_FACILITY_ID, WEB_SITE_ID, PRODUCT_STORE_ID, TERMINAL_ID, TRANSACTION_ID,
AUTO_ORDER_SHOPPING_LIST_ID, NEEDS_INVENTORY_ISSUANCE, IS_RUSH_ORDER, INTERNAL_CODE,
REMAINING_SUB_TOTAL, GRAND_TOTAL, LAST_UPDATED_STAMP, LAST_UPDATED_TX_STAMP, CREATED_STAMP,
CREATED_TX_STAMP, RECIBIR_BODEGAL, RECEPCIONADA_BODEGAL, FECHA_RECEPCION_BODEGAL FROM
ERP.ORDER_HEADER WHERE STATUS_ID = 'ORDER_COMPLETED' ORDER BY ORDER_DATE DESC
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 9.10 160.81 66729 65203 37 50
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 9.14 160.83 66729 65203 37 50
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 58
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 1 0.00 0.00
db file scattered read 8178 0.28 146.55
direct path write temp 2200 0.04 4.22
direct path read temp 36 0.14 2.01
SQL*Net more data to client 3 0.00 0.00
SQL*Net message from client 1 3.36 3.36
********************************************************************************
Кажется, проблема в «рассеянном чтении файла БД», есть идеи, как настроить oracle, чтобы уменьшить время ожидания в этом событии?
Проследите за новым результатом tkprof, на этот раз закрывая сеанс:
********************************************************************************
SELECT DISTINCT ORDER_ID, ORDER_TYPE_ID, ORDER_NAME, EXTERNAL_ID,
SALES_CHANNEL_ENUM_ID, ORDER_DATE, ENTRY_DATE, VISIT_ID, STATUS_ID, CREATED_BY,
FIRST_ATTEMPT_ORDER_ID, CURRENCY_UOM, SYNC_STATUS_ID, BILLING_ACCOUNT_ID,
ORIGIN_FACILITY_ID, WEB_SITE_ID, PRODUCT_STORE_ID, TERMINAL_ID, TRANSACTION_ID,
AUTO_ORDER_SHOPPING_LIST_ID, NEEDS_INVENTORY_ISSUANCE, IS_RUSH_ORDER, INTERNAL_CODE,
REMAINING_SUB_TOTAL, GRAND_TOTAL, LAST_UPDATED_STAMP, LAST_UPDATED_TX_STAMP, CREATED_STAMP,
CREATED_TX_STAMP, RECIBIR_BODEGAL, RECEPCIONADA_BODEGAL, FECHA_RECEPCION_BODEGAL FROM
ERP.ORDER_HEADER WHERE STATUS_ID = 'ORDER_COMPLETED' ORDER BY ORDER_DATE DESC
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.01 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 1 8.23 47.66 66576 65203 31 50
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 8.26 47.68 66576 65203 31 50
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 58
Rows Row Source Operation
------- ---------------------------------------------------
50 SORT ORDER BY (cr=65203 pr=66576 pw=75025 time=47666679 us)
3456659 TABLE ACCESS FULL ORDER_HEADER (cr=65203 pr=65188 pw=0 time=20757300 us)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 1 0.00 0.00
db file scattered read 8179 0.14 34.96
direct path write temp 2230 0.00 3.91
direct path read temp 52 0.14 0.84
SQL*Net more data to client 3 0.00 0.00
SQL*Net message from client 1 1510.62 1510.62
********************************************************************************
Решение
Если разница между двумя запросами существенна, это было бы удивительно.Вы упомянули, что запрос без DISTINCT занимает около 30 секунд.Сколько времени занимает запрос с DISTINCT?
Можете ли вы показать выходные данные запроса tkprof с помощью DISTINCT после того, как вы отследили сеанс с помощью «изменить события набора сеансов '10046 контекст имени трассировки навсегда, уровень 8'» и отключиться после завершения запроса?Таким образом, мы можем увидеть, на что на самом деле тратится время и ожидает ли оно чего-то (может быть, температура чтения по прямому пути?)
С уважением, Роб.
Продолжение после публикации файла tkprof:
Я вижу, вам удалось получить выходные данные tkprof, но, к сожалению, вы не отключили сеанс перед созданием файла tkprof.Теперь курсор остался открытым, и ему не удалось записать строки STAT# в ваш файл трассировки.Вот почему в вашем файле tkprof нет операции источника плана/строки.Было бы неплохо, если бы вы могли повторить процесс, если приведенное ниже предложение окажется чушью.
Небольшое предположение с моей стороны:Я думаю, что DISTINCT практически бесполезен, потому что вы выбираете так много столбцов.Если это правда, то ваш предикат «WHERE STATUS_ID = 'ORDER_COMPLETED'» очень избирательен, и вам будет полезно иметь индекс по этому столбцу.После создания индекса убедитесь, что вы проанализировали его правильно, возможно, даже с включенной гистограммой, если значения данных искажены.Конечным результатом будет другой план для этого запроса, начиная со СКАНИРОВАНИЯ ИНДЕКСНОГО ДИАПАЗОНА, за которым следует ДОСТУП К ТАБЛИЦЕ ПО ROWID, что приводит к очень быстрому запросу.
После создания индекса вы можете повторно проанализировать таблицу, включая гистограммы, используя этот оператор:
exec dbms_stats.gather_table_stats([владелец],[table_name],cascade=>true,method_opt=>'ДЛЯ ВСЕХ ИНДЕКСИРОВАННЫХ СТОЛБЦИЙ РАЗМЕР')
С уважением, Роб.
Другие советы
Поскольку вы упорядочиваете результаты в соответствии order_date
важно, чтобы у вас был нисходящий индекс в этом поле.Также сообщите Oracle, что вы хотите использовать этот индекс.Поместите order_date
поле сначала в запросе и используйте подсказку, например
SELECT /*+ index(HEADERS IDX_ORDER_DATE_DESC) */ ...
FROM ERP.ORDER_HEADER HEADERS
WHERE ...
ORDER BY ORDER_DATE DESC
Речь идет не столько о наличии индексов, сколько о том, чтобы указать Oracle использовать их.Oracle очень требователен к индексам.Наилучшие результаты вы получите, если выберете индексы по наиболее важным для вас запросам.Если сомневаешься, отслеживать запрос.Таким образом, вы можете увидеть, на какую часть запроса оракул тратит больше всего времени и действительно ли ваши индексы подбираются или нет.Трассировка имеет неоценимое значение при борьбе с проблемами производительности.
Объявлен ли ORDER_ID как PK с использованием ограничения PRIMARY KEY?Потому что в этом случае я ожидаю, что оптимизатор распознает, что DISTINCT в этом запросе является излишним, и оптимизирует его.Без ограничения он не будет знать, что оно лишнее, и поэтому будет тратить ненужные и значительные усилия на «дедублирование» результатов.
Попробуйте отключить агрегацию хэшей:
select /*+ no_use_hash_aggregation*/ distinct ...
http://oracle-randolf.blogspot.com/2011/01/hash-aggregation.html
При устранении неполадок в приложениях, в которых у меня нет контроля над SQL, я обнаружил, что пакет dbms_sqltune экономит много времени.Видеть http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28419/d_sqltun.htm и да, к сожалению, у вас должна быть лицензия на его использование.
В этом пакете есть процедуры для запуска анализа настройки по определенному sql_id в общем пуле или репозитории AWR.Анализ будет содержать рекомендации по индексированию, если необходимо внести какие-либо улучшения с помощью дополнительных индексов.Что еще более важно, анализатор может обнаружить улучшенный путь доступа, который он может реализовать с помощью того, что Oracle называет профилем SQL — это набор подсказок, которые будут храниться и использоваться всякий раз, когда этот sql_id выполняется.Это происходит без необходимости кодирования подсказок в операторе SQL, а также есть возможность выполнить то, что вы можете назвать нечетким сопоставлением, если ваше приложение генерирует в операторе литеральные значения вместо переменных привязки.
Конечно, этот инструмент не должен заменять понимание приложения и его структур данных, но чтение результатов, подробно описывающих путь выполнения лучшего плана (если он найден), может быть познавательным.
Oracle обращается ко всей таблице каждый раз, когда вы запускаете запрос ( TABLE ACCESS (FULL) ).Создание INDEX для столбцов STATUS_ID и ORDER_TYPE_ID
CREATE INDEX ERP.ORDER_HEADER_I1 ON ERP.ORDER_HEADER ( STATUS_ID, ORDER_TYPE_ID );
очень поможет, особенно если в таблице ORDER_HEADER есть несколько разных значений STATUS_ID и ORDER_TYPE_ID.