Domanda

La situazione

Ho qualche problema con il mio piano di esecuzione della query per una query di medie dimensioni su una grande quantità di dati in Oracle 11.2.0.2.0. Per velocizzare le cose, ho introdotto un filtro di intervallo che fa più o meno qualcosa del genere:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((org_from IS NULL) OR (org_from <= org.no))
   AND ((org_to   IS NULL) OR (org_to   >= org.no)))
  -- [...]

Come puoi vedere, desidero limitare il JOIN di organisations utilizzando un intervallo facoltativo di numeri di organizzazione. Il codice client può chiamare DO_STUFF con (dovrebbe essere veloce) o senza (molto lento) la restrizione.

Il problema

Il problema è che PL / SQL creerà variabili di binding per i parametri org_from e org_to sopra, che è quello che mi aspetterei nella maggior parte dei casi:

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((:B1 IS NULL) OR (:B1 <= org.no))
   AND ((:B2 IS NULL) OR (:B2 >= org.no)))
  -- [...]

La soluzione alternativa

Solo in questo caso, ho misurato il piano di esecuzione della query per essere molto migliore quando ho semplicemente incorporato i valori, cioè quando la query eseguita da Oracle è effettivamente qualcosa di simile

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))
  -- [...]

Con "molto" intendo 5-10 volte più veloce. Si noti che la query viene eseguita molto raramente, ovvero una volta al mese. Quindi non ho bisogno di memorizzare nella cache il piano di esecuzione.

Le mie domande

  • Come posso incorporare i valori in PL / SQL? Conosco ESEGUI IMMEDIATA , ma preferirei avere PL / SQL compila la mia query e non concatenazione di stringhe.

  • Ho appena misurato qualcosa che è accaduto per coincidenza o posso presumere che le variabili in linea siano effettivamente migliori (in questo caso)? Il motivo per cui lo chiedo è perché penso che le variabili di associazione costringano Oracle a escogitare un piano di esecuzione generale , mentre i valori inline consentirebbero di analizzare statistiche di colonne e indici molto specifiche . Quindi posso immaginare che questa non sia solo una coincidenza.

  • Mi sto perdendo qualcosa? Forse esiste un modo completamente diverso per ottenere un miglioramento del piano di esecuzione delle query, diverso dall'inlining delle variabili (nota che ho provato anche alcuni suggerimenti ma non sono un esperto in questo campo)?

È stato utile?

Soluzione

In uno dei tuoi commenti hai detto:

"Inoltre ho controllato vari valori di bind. Con le variabili bind ne ottengo alcune FULL TABLE SCANS, mentre con hard-coded valori, il piano sembra molto migliore. "

Esistono due percorsi. Se si passa NULL per i parametri, si selezionano tutti i record. In queste circostanze, una scansione completa della tabella è il modo più efficiente per recuperare i dati. Se si trasmettono valori, le letture indicizzate potrebbero essere più efficienti, poiché si seleziona solo un piccolo sottoinsieme delle informazioni.

Quando si formula la query utilizzando le variabili bind, l'ottimizzatore deve prendere una decisione: dovrebbe presumere che la maggior parte delle volte passerai valori o che passerai valori nulli? Difficile. Quindi guardala in un altro modo: è più inefficiente eseguire una scansione completa della tabella quando è necessario selezionare solo un sottoinsieme di record o eseguire letture indicizzate quando è necessario selezionare tutti i record?

Sembra che l'ottimizzatore abbia scelto le scansioni complete della tabella come l'operazione meno inefficiente per coprire tutte le eventualità.

Considerando che quando si codificano i valori, l'ottimizzatore sa immediatamente che 10 IS NULL restituisce FALSE, e quindi può valutare i vantaggi dell'utilizzo di letture indicizzate per trovare i record del sottoinsieme desiderato.


Allora, cosa fare? Come dici tu, questa query viene eseguita solo una volta al mese, penso che richiederebbe solo una piccola modifica ai processi aziendali per avere query separate: una per tutte le organizzazioni e una per un sottoinsieme di organizzazioni.


"A proposito, rimuovendo la clausola: R1 IS NULL non cambia il piano di esecuzione molto, il che mi lascia con l'altro lato della condizione OR,: R1 <= org.no dove NULL non avrebbe senso comunque, poiché org.no NON è NULL "

Ok, quindi il fatto è che hai una coppia di variabili di bind che specificano un intervallo . A seconda della distribuzione dei valori, intervalli diversi potrebbero adattarsi a diversi piani di esecuzione. Cioè, questo intervallo (probabilmente) si adatterebbe a una scansione di intervallo indicizzato ...

WHERE org.id BETWEEN 10 AND 11

... mentre è probabile che questo sia più adatto a una scansione completa della tabella ...

WHERE org.id BETWEEN 10 AND 1199999

È qui che entra in gioco Bind Variable Peeking.

(a seconda della distribuzione dei valori, ovviamente).

Altri suggerimenti

Poiché i piani di query sono in realtà costantemente diversi, ciò implica che le stime di cardinalità dell'ottimizzatore sono disattivate per qualche motivo. Potete confermare dai piani di query che l'ottimizzatore si aspetta che le condizioni non siano sufficientemente selettive quando vengono utilizzate le variabili di associazione? Dato che utilizzi 11.2, Oracle dovrebbe utilizzare condivisione del cursore adattiva , quindi non dovrebbe essere un problema di sbirciamento della variabile bind (supponendo che tu stia chiamando la versione con variabili bind molte volte con valori NO diversi durante il test.

Le stime di cardinalità sul buon piano sono effettivamente corrette? So che hai detto che le statistiche sulla colonna NO sono accurate, ma sarei sospettoso di un istogramma vagante che potrebbe non essere aggiornato dal normale processo di raccolta delle statistiche, ad esempio.

Puoi sempre usare un suggerimento nella query per forzare l'uso di un particolare indice (anche se usando un struttura memorizzata o stabilità del piano di ottimizzazione sarebbe preferibile da una prospettiva di manutenzione a lungo termine). Qualsiasi di queste opzioni sarebbe preferibile al ricorso a SQL dinamico.

Un ulteriore test da provare, tuttavia, sarebbe sostituire la sintassi di join SQL 99 con la vecchia sintassi di Oracle, ovvero

SELECT <<something>>
  FROM <<some other table>> cust,
       organization org
 WHERE cust.org_id = org.id
   AND (    ((org_from IS NULL) OR (org_from <= org.no)) 
        AND ((org_to   IS NULL) OR (org_to   >= org.no)))

Questo ovviamente non dovrebbe cambiare nulla, ma ci sono stati problemi di parser con la sintassi SQL 99, quindi è qualcosa da controllare.

Puzza di Bind Peeking , ma sono solo su Oracle 10, quindi non posso affermare che lo stesso problema esista in 11.

Questo assomiglia molto alla necessità di Adaptive Cursor Sharing, combinata con la stabilità di SQLPlan. Penso che quello che sta succedendo sia che il capture_sql_plan_baselines parameter is true. E lo stesso per use_sql_plan_baselines. Se questo è vero, sta accadendo quanto segue:

  1. La prima volta che una query viene avviata, viene analizzata, ottiene un nuovo piano.
  2. La seconda volta, questo piano viene archiviato in sql_plan_baselines come piano accettato.
  3. Tutte le esecuzioni successive di questa query utilizzano questo piano, indipendentemente dalle variabili di bind.

Se la Condivisione cursore adattiva è già attiva, l'ottimizzatore genererà un piano nuovo / migliore, lo memorizzerà in sql_plan_baselines ma non è in grado di utilizzarlo, finché qualcuno non accetta questo nuovo piano come un piano alternativo accettabile. Controlla dba_sql_plan_baselines e verifica se la tua query contiene voci con accepted = 'NO' and verified = null Puoi utilizzare dbms_spm.evolve per far evolvere il nuovo piano e farlo accettare automaticamente se le prestazioni del piano sono almeno 1,5 volte migliori rispetto a senza il nuovo piano.

Spero che questo aiuti.

L'ho aggiunto come commento, ma lo offrirò anche qui.Spero che questo non sia eccessivamente semplicistico e guardando le risposte dettagliate potrei fraintendere il problema esatto, ma comunque ...

Sembra che la tabella della tua organizzazione abbia la colonna n. (org.no) definita come un numero.Nel tuo esempio hardcoded, utilizzi i numeri per fare i confronti.

JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))

Nella tua procedura, stai passando varchar2 :

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

Quindi, per confrontare varchar2 con number, Oracle dovrà eseguire le conversioni , quindi ciò potrebbe causare le scansioni complete.

Soluzione: modifica proc per trasmettere numeri

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top