Como melhorar o desempenho no Oracle usando SELECT DISTINCT
-
20-08-2019 - |
Pergunta
Atualmente estou trabalhando na implantação de um ERP com base OFBiz O banco de dados a ser utilizado é o Oracle 10g Enterprise
Um dos maiores problemas é alguns problemas de desempenho oráculo, analisando os logs OFBiz, a consulta a seguir:
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
é muito lento. Nós testamos executar a consulta sem a DISTINCT e leva cerca de 30 segundos. Há 4.000.000+ registros na tabela. Há índice para o campo PK orderId e quase todos os outros campos
O EXPLAIN PLAN com DISTINCT é:
SELECT STATEMENT () (null)
SORT (ORDER BY) (null)
HASH (UNIQUE) (null)
TABLE ACCESS (FULL) ORDER_HEADER
e sem a DISTINCT é:
SELECT STATEMENT () (null)
SORT (ORDER BY) (null)
TABLE ACCESS (FULL) ORDER_HEADER
quaisquer ideias sobre tuning Oracle para melhorar o desempenho deste tipo de consultas? É muito difícil para reescrever a consulta porque é gerado automaticamente pelo OFBiz então eu acho que a solução é de cerca de tuning Oracle
Agradecemos antecipadamente
EDIT: Eu analisei a consulta usando tkprof, como sugerido por Rob van Wijk e haffax, eo resultado é o seguinte
********************************************************************************
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
********************************************************************************
Assim, parece que o problema é o 'db arquivo espalhadas ler', todas as idéias para como sintonizar a Oracle, a fim de reduzir o tempo de espera neste evento?
Seguir-se com o novo resultado tkprof, desta vez fechando a sessão:
********************************************************************************
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
********************************************************************************
Solução
Se a diferença entre as duas consultas é substancial, que seria surpreendente. Você menciona que a consulta sem DISTINCT leva cerca de 30 segundos. Quanto tempo faz a consulta com o take distintos?
Você pode mostrar a saída tkprof da consulta com o DISTINCT, depois que você traçou a sessão com uma "sessão set eventos alter '10046 traço contexto nome para sempre, nível 8'", e desligar após a consulta terminar? Desta forma, podemos ver onde o tempo realmente está sendo gasto e se ele estava esperando por alguma coisa ( "caminho direto ler temp" talvez?)
Saudações, Rob.
Acompanhamento, após o arquivo tkprof foi postada:
Eu vejo que você conseguiu chegar a saída tkprof, mas infelizmente você não desconectar a sessão antes de criar o arquivo tkprof. Agora, o cursor foi deixado aberto e ele não conseguiu escrever STAT # linhas para o seu arquivo de rastreamento. É por isso que você não tem uma operação de fonte de plano / linha em seu arquivo tkprof. Seria bom se você pode repetir o processo, se a sugestão abaixo acaba por ser lixo.
Um pouco especulação do meu lado: Eu acho que o DISTINCT é quase um não-op porque você está selecionando tantas colunas. Se isso for verdade, então o seu predicado "ONDE STATUS_ID = 'ORDER_COMPLETED'" é muito seletivo e você vai se beneficiar de ter um índice nessa coluna. Depois de criar o índice, certifique-se de analisá-lo corretamente, talvez até mesmo com um histograma sobre se os valores dados estão tortos. O resultado final será um plano diferente para esta consulta, começando com um índice de intervalo SCAN, seguido por um acesso à tabela POR ROWID, levando a uma consulta muito rápida.
Depois de ter criado um índice, você pode ter a mesa re-analisados, incluindo histogramas usando esta declaração:
dbms_stats.gather_table_stats exec ([proprietário], [table_name], cascade => true, METHOD_OPT => 'PARA TODOS INDEXED COLUNAS TAMANHO')
Saudações, Rob.
Outras dicas
Desde que você está resultados de ordenação de acordo com order_date
é importante que você tem um índice descendente nesse campo.
Informe também o oráculo que você deseja usar este índice. Coloque o campo order_date
primeiro na consulta e usar uma sugestão como
SELECT /*+ index(HEADERS IDX_ORDER_DATE_DESC) */ ...
FROM ERP.ORDER_HEADER HEADERS
WHERE ...
ORDER BY ORDER_DATE DESC
Não é tanto sobre tendo índices, mas sim em contar oráculo para usá-los. A Oracle é muito exigente sobre índices. Você obtém os melhores resultados quando você escolhe índices de acordo com as suas consultas mais importantes. Em caso de dúvida, traçar um consulta. Desta forma, você pode ver em que parte do oráculo consulta gasta mais tempo e se seus índices são realmente pegou ou não. Rastreamento é inestimável quando lutando problemas de desempenho.
é ORDER_ID declarado como o PK utilizando uma restrição de chave primária? Porque se for eu esperaria que o otimizador de reconhecer que o DISTINCT é supérfluo nesta consulta e otimizá-lo para fora. Sem a restrição, não saberá que é supérfluo e assim vai despender um esforço desnecessário e considerável "de-enganar" os resultados.
Tente desativar a agregação de hash:
select /*+ no_use_hash_aggregation*/ distinct ...
http://oracle-randolf.blogspot.com/2011/ 01 / hash aggregation.html
Ao solucionar aplicações onde eu não tenho controle do SQL acho que o pacote dbms_sqltune poupa muito tempo. Consulte http://download.oracle.com/docs /cd/B28359_01/appdev.111/b28419/d_sqltun.htm , e sim, infelizmente, você deve ser licenciado para usá-lo.
Existem procedimentos neste pacote para executar uma análise de ajuste contra um sql_id específico na piscina comum ou o repositório AWR. A análise irá conter recomendações de indexação se existem melhorias a serem feitas com índices adicionais. Mais importante, o analisador pode descobrir um caminho de acesso melhorado que ele pode implementar com o que a Oracle chama de perfil SQL - este é um conjunto de sugestões que serão armazenados e utilizados sempre que isso sql_id é executado. Isto acontece sem a necessidade das dicas para ser codificados na instrução SQL, e há também uma opção para fazer o que você pode pensar como correspondência difusa se a aplicação gera valores literais na demonstração em vez de variáveis ??de ligação.
É claro que esta ferramenta não deve ser um substituto para a compreensão da aplicação e é estruturas de dados, mas a leitura através da saída que detalha o caminho de execução de um plano melhor (se encontrado) pode ser educativo.
A Oracle está a aceder a toda a tabela cada vez que você executar a consulta (QUADRO DE ACESSO (FULL)). Criando um índice nas colunas STATUS_ID e ORDER_TYPE_ID
CREATE INDEX ERP.ORDER_HEADER_I1 ON ERP.ORDER_HEADER ( STATUS_ID, ORDER_TYPE_ID );
vai ajudar muito, especialmente se houver vários valores diferentes de STATUS_ID e ORDER_TYPE_ID na tabela ORDER_HEADER.