A questão:
Eu tenho uma tabela espacial (road lines), armazenada usando o SDE.ST_GEOMETRY
tipo de dados definido pelo usuário da ESRI em um geodatabase Oracle 12c . Quero listar os vértices da linha para que eu possa acessar e atualizar suas coordenadas. Se eu estivesse usando SDO_GEOMETRY / Oracle Locator, usaria a
SDO_UTIL.GETVERTICES
função Mas não estou usando SDO_GEOMETRY / Oracle Locator e não há função equivalente no SDE.ST_GEOMETRY
. As únicas SDE.ST_GEOMETRY
funções que posso encontrar que pertencem aos vértices são ST_PointN
e ST_NumPoints
.
Eu vim com uma consulta que faz tudo isso com êxito - obtém os vértices da linha como linhas (inspiradas nesta página ):
1 SELECT a.ROAD_ID
2 ,b.NUMBERS VERTEX_INDEX
3 ,a.SDE.ST_X(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS X
4 ,a.SDE.ST_Y(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS Y
5 FROM ENG.ROADS a
6 CROSS JOIN ENG.NUMBERS b
7 WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
8 --removed to do explain plan: ORDER BY ROAD_ID, b.NUMBERS
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 1 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 2 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 3 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 4 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 3 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
Ele CROSS JOINS
alinha as linhas ROADS
de uma NUMBERS
tabela (e limita os resultados ao número de vértices em cada linha).
Estatísticas: (atualizado)
- Cada linha tem no máximo 30 vértices (média de 4,38 vértices por linha)
- ROADS possui 3.997 linhas
- NUMBERS possui 30 linhas (números sequenciais iniciando em 1)
- O conjunto de resultados possui 17.536 linhas
No entanto, o desempenho é ruim (40 segundos) e não consigo deixar de pensar - existe uma maneira mais elegante de fazer isso? Para mim, usar uma tabela de números e uma junção cruzada parece uma abordagem desleixada. Existe uma maneira melhor?
Os termos do leigo seriam apreciados; Eu sou um cara de obras públicas, não um DBA.
Atualização # 1:
Se eu remover as linhas 3 e 4 (sequência de funções relacionadas a X e Y) da consulta, ela será executada instantaneamente. Mas é claro, não posso simplesmente remover essas linhas, preciso das colunas X e Y. Portanto, isso me leva a acreditar que o desempenho lento tem algo a ver com as funções X e Y.
No entanto, se eu exportar os pontos para uma tabela estática e executar as funções X e Y nela, isso também será executado instantaneamente.
Então, isso significa que o desempenho lento é causado pelas funções X e Y, exceto, bem, não, não é? Estou confuso.
Atualização # 2:
Se eu retirar os X e Y da consulta, colocá-los em uma consulta externa e adicionar ROWNUM à consulta interna, será muito mais rápido (16 segundos - atualizado):
SELECT
ROWNUM
,ROAD_ID
,VERTEX_INDEX
,SDE.ST_X(ST_POINT) AS X
,SDE.ST_Y(ST_POINT) AS Y
FROM
(
SELECT
ROWNUM
,a.ROAD_ID
,b.NUMBERS VERTEX_INDEX
,SDE.ST_PointN(a.SHAPE, b.NUMBERS) AS ST_POINT
FROM ENG.ROAD a
CROSS JOIN ENG.NUMBERS b
WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
)
--removed to do explain plan: ORDER BY ROAD_ID, VERTEX_INDEX
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 1 | COUNT | | | | | | |
| 2 | VIEW | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 3 | COUNT | | | | | | |
| 4 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 5 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 7 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 6 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
Justin Cave explica por que o ROWNUM ajuda o desempenho aqui: Por que adicionar o ROWNUM a uma consulta melhora o desempenho?
Embora essa melhoria de desempenho seja boa, ainda não é suficiente. E não posso deixar de pensar que ainda não entendo completamente como a consulta funciona ou por que é tão lenta quanto é.
A questão ainda permanece: existe uma maneira melhor?
fonte
Respostas:
Sei um pouco sobre o desempenho do Oracle e praticamente nada sobre tipos de dados personalizados, mas tentarei dar um plano para melhorar o desempenho.
1) Verifique se você não pode obter um plano de explicação.
É possível obter planos de explicação mesmo se você não tiver um sofisticado software de banco de dados. O que acontece se você executar
set autotrace on explain
?Você também pode tentar DBMS_XPLAN . Primeiro salve o plano envolvendo sua consulta com algumas palavras-chave extras:
Em seguida, execute o seguinte:
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());
É possível que nenhum deles funcione e você realmente não pode obter um plano de explicação. Eu só queria verificar isso porque, com um plano de explicação, será muito mais fácil para a comunidade ajudá-lo.
2) Considere os requisitos.
Você disse que 20 segundos não é bom o suficiente. Você ou alguém definiu exatamente o que é bom o suficiente? Existe algum espaço para negociação? Sua consulta precisa ser exatamente uma consulta SELECT? Você poderia preencher uma tabela temporária global em uma etapa e selecionar os resultados desejados na próxima? Você poderia criar um procedimento armazenado que retorne um conjunto de resultados e chame isso?
3) Estabeleça um limite inferior para o tempo necessário para concluir a consulta.
Sugiro executar uma consulta simples que "engana" para descobrir como seria uma consulta otimizada. Por exemplo, quanto tempo essa consulta que obtém apenas os primeiros vértices leva?
Eu suspeito que isso lhe dará 4000 linhas. Se você multiplicar o tempo de resposta dessa consulta por 17,5 / 4, isso poderá fornecer um limite inferior bom para o tempo total de execução.
Se o limite inferior para o tempo total de execução for maior do que o estabelecido na etapa 2, você precisará ser criativo com seu modelo de dados calculando os resultados com antecedência e armazenando-os em tabelas ou renegociar o tempo de resposta necessário.
4) Benchmark para descobrir quais funções estão contribuindo mais para o seu tempo de execução.
Você estava no caminho certo com a atualização nº 1, mas precisa tentar controlar a quantidade de trabalho que está sendo realizado. Por exemplo, é possível escrever um grupo de consultas relativamente simples que executam cada função exatamente 10000 vezes? Como os tempos de resposta se comparam?
5) Vá trabalhar.
Dependendo dos requisitos estabelecidos na etapa 2 e do que você encontrou na etapa 4, tente qualquer truque para reduzir o tempo de execução da consulta. Você é capaz de pré-calcular resultados e salvá-los? Se o problema estiver relacionado ao número de vezes que as funções são executadas, a dica de materialização não documentada pode ser útil. Isso força a Oracle a criar uma tabela temporária oculta nos bastidores para armazenar os resultados. Não sei se é compatível com os tipos de dados especiais que você está usando.
Por exemplo, talvez algo assim tenha um desempenho melhor? Desculpas se não compilar, mas não tenho como testar.
Se você ainda estiver parado depois de tudo isso, suspeito que ao menos lhe fornecerá informações adicionais que você pode editar na pergunta. Boa sorte!
fonte
Tentei usar CONNECT BY (e DUAL) para ver se seria mais rápido, mas não é (é quase o mesmo).
Eu tive a ideia deste post: Como calcular intervalos no Oracle?
fonte
Resultados e resposta à resposta de Joe Obbish :
Nota: Daqui em diante, vou me referir à consulta na Atualização 2 como 'a consulta'; Não vou me referir à consulta na pergunta original.
1) Verifique se você não pode obter um plano de explicação.
Não consigo executar
set autotrace on explain
. Eu recebo este erro:ORA-00922: missing or invalid option (#922)
Mas eu sou capaz de executar
DBMS_XPLAN
. Eu tinha assumido que seria incapaz de fazer isso. Felizmente, eu estava errado. Agora estou executando planos de explicação.2) Considere os requisitos.
Sua consulta precisa ser exatamente uma consulta SELECT?
Eu acho que a consulta faz precisa ser exatamente uma consulta. O software que estou usando é muito limitado e não permite várias instruções de seleção.
Você definiu exatamente quais são seus requisitos?
3) Estabeleça um limite inferior para o tempo necessário para concluir a consulta.
A consulta do primeiro vértice é executada em 3,75 segundos (retorna 3805 linhas, conforme o esperado).
3.75 sec * (16495 total / 3805 lines) = 16.25 sec
O resultado: o limite inferior para o tempo total de execução é maior do que o estabelecido na etapa 2 (5 segundos). Portanto, acho que a solução é '... ser criativo com meu modelo de dados, calculando os resultados antecipadamente e armazenando-os em uma tabela' (o tempo de resposta necessário não é negociável). Em outras palavras, faça uma visualização materializada.
Além disso, o limite inferior de 16,25 segundos corresponde ao tempo total de execução da consulta na Atualização 2 (16 segundos). Acho que isso prova que minha consulta está totalmente otimizada, dadas as funções e os dados com os quais tenho que trabalhar.
4) Benchmark para descobrir quais funções estão contribuindo mais para o seu tempo de execução.
Eu criei duas tabelas (ambas contêm 10.000 linhas):
ROADS_BM
eROADS_STARTPOINT_BM
. Eu executei consultas simples nas tabelas usando cada uma das funções envolvidas. Aqui estão os resultados:Documentação de função: ST_X , ST_Y , ST_NumPoints , ST_PointN
O resultado?
ST_PointN
é o problema. Seu tempo de resposta de 9,5 segundos é péssimo em comparação com as outras funções. Suponho que isso faça algum sentido .ST_PointN
retorna umST_POINT
tipo de dados de geometria, que deve ser bastante complexo em comparação com as outras funções que retornam um número simples.5) Vá trabalhar.
Com base nas etapas 2, 3 e 4, aqui estão minhas descobertas:
ST_PointN
função. Está lento. Acho que não há muito o que fazer sobre isso. Além de tentar reprogramar / recriar completamente a função, na esperança de que eu pudesse me sair melhor do que os especialistas que a criaram. Não é exatamente prático.ST_PointN
é o culpado por isso também. A consulta CTE não foi um desperdício; Eu aprendi muito apenas usando isso.6. Conclusão.
Estou satisfeito por otimizar a consulta o máximo possível. Vou configurar o pré-cálculo e passar para a próxima. Um grande obrigado a Joe Obbish; Eu aprendi muito com as etapas que ele forneceu.
fonte