Oracle anti-join Esecuzione domanda piano
-
20-09-2019 - |
Domanda
Abbiamo due tabelle in questo modo:
Event
id
type
... a bunch of other columns
ProcessedEvent
event_id
process
Ci sono indici definiti per
- Event (id) (PK)
- ProcessedEvent (event_id, di processo)
Il primo rappresenta gli eventi in un'applicazione.
Il secondo rappresenta il fatto che un certo evento schiera processi da un determinato processo. Ci sono molti processi che hanno bisogno di elaborare un determinato evento, quindi non ci sono più voci nella seconda tabella per ogni voce nel primo.
Al fine di trovare tutti gli eventi che hanno bisogno di elaborazione eseguiamo la seguente query:
select * // of course we do name the columns in the production code
from Event
where type in ( 'typeA', 'typeB', 'typeC')
and id not in (
select event_id
from ProcessedEvent
where process = :1
)
Le statistiche sono aggiornate
Dal momento che la maggior parte degli eventi vengono elaborati, penso che il miglior piano di esecuzione dovrebbe essere simile a questo
- indice di scansione completa sull'Indice ProcessedEvent
- indice di scansione completa sull'Indice evento
- contro uniscono tra i due
- accesso tavola con il resto
- Filtro
Invece Oracle fa il seguente
- indice di scansione completa sull'Indice ProcessedEvent
- scansione completa della tabella sul tavolo evento
- filtrare la tabella eventi
- contro uniscono tra i due set
Con un hint di indice ottengo Oracle per effettuare le seguenti operazioni:
- indice di scansione completa sull'Indice ProcessedEvent
- indice di scansione completa sull'Indice evento
- accesso tavolo sul tavolo evento
- filtrare la tabella eventi
- contro uniscono tra i due set
che è veramente stupido IMHO.
Quindi la mia domanda è: quale potrebbe essere il motivo per Oracle per insistere sulla presto accesso alle tabelle
Aggiunta: La performance è male. Stiamo correggendo il problema di prestazioni selezionando solo le Event.IDs e poi recupero le righe necessarie '' manualmente'. Ma, naturalmente, che è solo un lavoro in giro.
Soluzione
Il tuo indice FULL SCAN sarà probabilmente più veloce di una scansione completa della tabella in quanto l'indice è probabile "più sottile" rispetto alla tabella. Ancora, la piena INDEX SCAN è una lettura completa del segmento e sarà circa lo stesso costo come scansione completa della tabella.
Tuttavia, si sta anche aggiungendo un ACCESSO TABELLA ROWID passo. Si tratta di un passo costoso: una logica IO per riga per l'accesso ROWID, mentre si ottiene uno IO logica per ogni Multi blocchi (a seconda del vostro db_file_multiblock_read_count parameter
) per la scansione completa della tabella.
In conclusione, l'ottimizzatore calcola che:
cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)
Aggiorna : La TABELLA FULL SCAN consente anche il filtro del tipo prima di quanto il percorso completo INDICE DI SCANSIONE (poiché l'indice non conosce il tipo di evento), riducendo pertanto la dimensione il set che sarà anti-incollati (ancora un altro vantaggio della scansione completa della tabella).
Altri suggerimenti
L'ottimizzatore fa molte cose che non hanno senso in un primo momento, ma ha è ragioni. Essi non possono sempre essere destro , ma sono comprensibili.
La tabella eventi può essere più facile da full-scan anziché per l'accesso identificativo causa delle sue dimensioni. Può essere che ci sono significativamente meno operazioni di IO coinvolti per leggere l'intera sequenza tabella che leggere pezzi.
è la performance male, o stai solo chiedendo il motivo per cui l'ottimizzatore ha fatto?
Non riesco a spiegare il comportamento del ottimizzatore, ma la mia esperienza è stata quella di evitare di "NOT IN" a tutti i costi, sostituendolo invece con MINUS, in questo modo:
select * from Event
where id in (
select id from Event where type in ( 'typeA', 'typeB', 'typeC')
minus
select id from ProcessedEvent
)
Ho visto ordini di grandezza in termini di prestazioni di query con le trasformazioni simili.
Qualcosa di simile:
WITH
PROCEEDED AS
(
SELECT
event_id
FROM
ProcessedEvent
WHERE
PROCESS = :1
)
SELECT
* // of course we do name the columns in the production code
FROM
EVENT
LEFT JOIN PROCEEDED P
ON
p.event_id = EVENT.event_id
WHERE
type IN ( 'typeA', 'typeB', 'typeC')
AND p.event_id IS NULL; -- exclude already proceeded
potrebbe funzionare abbastanza veloce (almeno molto più veloce rispetto NOT IN
).