Oracle non utilizza un indice univoco per una chiave lunga


16

Ho una tabella con 250.000 righe nel mio database di test. (Ci sono alcune centinaia di milioni in produzione, possiamo osservare lo stesso problema lì.) La tabella ha un identificatore di stringa nvarchar2 (50), non nullo, con un indice univoco su di esso (non è il PK).

Gli identificatori sono costituiti da una prima parte che ha 8 valori diversi nel mio database di test (e circa un migliaio in produzione), quindi un segno @ e infine un numero, compreso tra 1 e 6 cifre. Ad esempio, potrebbero esserci 50 mila righe che iniziano con 'ABCD_BGX1741F_2006_13_20110808.xml @', seguite da 50 mila numeri diversi.

Quando eseguo una query per una singola riga in base al suo identificatore, la cardinalità è stimata come 1, il costo è molto basso, funziona benissimo. Quando eseguo una query per più di una riga con più identificatori in un'espressione IN o un'espressione OR, le stime per l'indice sono completamente errate, quindi viene utilizzata una scansione completa della tabella. Se forzo l'indice con un suggerimento, è molto veloce, la scansione della tabella completa viene effettivamente eseguita con un ordine di grandezza più lento (e molto più lento in produzione). Quindi è un problema di ottimizzazione.

Come test, ho duplicato la tabella (nello stesso schema + tablespace) con lo stesso DDL esatto e lo stesso contenuto esatto. Ho ricreato l'indice univoco sulla prima tabella per buona misura e ho creato lo stesso indice esatto sulla tabella dei cloni. Ho fatto un DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. Puoi persino vedere che i nomi degli indici sono consecutivi. Quindi ora l'unica differenza tra le due tabelle è che la prima è stata caricata in ordine casuale per un lungo periodo di tempo, con blocchi sparsi sul disco (in un tablespace insieme a molte altre tabelle di grandi dimensioni), la seconda è stata caricata come una in batch INSERT-SELECT. A parte questo, non riesco a immaginare alcuna differenza. (La tabella originale è stata ridotta dall'ultima grande eliminazione e dopo non è stata eseguita una sola eliminazione.)

Ecco i piani di query per il malato e la tabella dei cloni (le stringhe sotto il pennello nero sono le stesse in tutta l'immagine e anche sotto il pennello grigio.):

piani di query

(In questo esempio, ci sono 1867 righe che iniziano con l'identificatore con pennello nero. Una query a 2 righe produce una cardinalità di 1867 * 2, una query a 3 righe produce una cardinalità di 1867 * 3, ecc. Impossibile essere una coincidenza, Oracle sembra non curarsi della fine degli identificatori.)

Cosa potrebbe causare questo comportamento? Ovviamente sarebbe piuttosto costoso ricreare la tabella in produzione.

USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Ho modificato solo lo schema e il nome del tablespace. Puoi vedere che i nomi di tabella e indice sono gli stessi dello screenshot del piano di query.

Risposte:


7

(Questo risponde all'altra domanda sul perché gli istogrammi sono diversi.)

Gli istogrammi vengono creati per impostazione predefinita in base all'inclinazione della colonna e se la colonna è stata utilizzata in un predicato rilevante. La copia del DDL e dei dati non è sufficiente, anche le informazioni sul carico di lavoro sono importanti.

Secondo la Performance Tuning Guide :

Quando si elimina una tabella, le informazioni sul carico di lavoro utilizzate dalla funzione di raccolta dell'istogramma automatico e la cronologia delle statistiche salvate utilizzate dalle procedure RESTORE _ * _ STATS vengono perse. Senza questi dati, queste funzionalità non funzionano correttamente.

Ad esempio, ecco una tabella con dati distorti ma senza istogramma:

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
NONE

L'esecuzione della stessa cosa, ma con una query prima della raccolta delle statistiche, genererà un istogramma.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
FREQUENCY

2
Esempio brillantemente semplice. Hai idea del perché il CBO stava usando gli istogrammi per le stime di cardinalità su una scansione unica piuttosto che assumerne solo 1?
Jack Douglas,

Grazie! Ho fatto una replica completa con il mio tipo di dati e domande sul mio blog: joco.name/2014/01/05/…
fejesjoco

@Jack Penso che sia pigrizia. Gli ingegneri Oracle devono aver capito che le statistiche di un indice univoco avranno lo stesso numero di valori distinti delle righe, quindi l'assunzione della 1 cardinalità non è cablata, ma semplicemente usata dalle statistiche, come in ogni altro caso. Inoltre, come caso generale, gli istogrammi vincono statistiche semplici. Il mio caso sembra essere molto speciale a causa delle sole chiavi lunghe, ma credo che altrimenti funzioni piuttosto bene.
fejesjoco,

@fejesjoco Penso che la spiegazione di JL sia più probabile, dato che gli istogrammi avrebbero anche battuto le statistiche generali nel caso di una singola ricerca (senza in), no? Penso che il CBO faccia l'ipotesi della cardinalità 1, ma solo nel caso più semplice. Suppongo che potresti aggirare tutto usando un grande UNION ALLma ci potrebbero essere altri motivi per non farlo e JL menziona altre possibili soluzioni nel post del blog collegato.
Jack Douglas,

1
Un altro piccolo mistero da considerare: come è stato creato questo istogramma? Oracle sembra considerare una colonna distorta solo se ha duplicati, che ovviamente la tua colonna unica non può avere. Qualcuno ha costruito intenzionalmente questo istogramma (improbabile) o ha raccolto statistiche con il non raccomandato method_opt=>'for all indexed columns'?
Jon Heller,

8

Ho trovato la soluzione! È così bello e in realtà ho imparato MOLTO su Oracle.

In una parola: istogrammi.

Ho iniziato a leggere molto su come funziona il CBO di Oracle e mi sono imbattuto negli istogrammi. Non ho capito bene quindi ho dato un'occhiata al tavolo USER_HISTOGRAMS e voilá. C'erano diverse file per il tavolo malato e praticamente nulla per il tavolo clonato. Per la tabella malata, c'era una riga per ciascuna delle 8 diverse parti di partenza dell'identificatore. E questa è la chiave: sono stati tagliati a 32 caratteri, prima del segno @. Come ho detto, la prima parte delle chiavi è altamente ripetitiva, diventano diverse dopo il segno @.

Sembra che gli istogrammi possano essere più potenti del semplice fatto che un indice univoco abbia sempre una cardinalità di 0 o 1 per un dato valore. Quando stavo eseguendo una query per più di 2 righe, Oracle ha esaminato l'istogramma, ha pensato che potrebbero esserci decine di migliaia di valori per quella parte iniziale dell'identificatore e ha gettato il CBO fuori rotta.

Ho eliminato gli istogrammi per quella colonna nella vecchia tabella e il problema è scomparso!

Altre letture: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating


2
Phil

Non l'ho visto :). Quindi l'unica cosa strana è il motivo per cui c'erano degli istogrammi nella prima tabella e non nel clone, pensavo che gather_schema_stats aggiornasse tutto, apparentemente no.
fejesjoco,

6

Ho mandato un'email a Jonathan Lewis a riguardo e ho ottenuto una risposta molto utile:

La stranezza nel calcolo è una conseguenza dei limiti degli istogrammi basati sui caratteri, vedi in particolare:

http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/

Guardando l'esempio, la query è per un elenco IN, non per una singola riga, quindi la mia ipotesi iniziale sarebbe che l'ottimizzatore ha usato una strategia generica per calcolare la selettività multi-riga piuttosto che avere un pezzo speciale di codice per un Elenco IN su una chiave primaria. Immagino che non sarebbe troppo difficile per loro riconoscere questo caso, ma probabilmente gli sviluppatori non hanno ritenuto che valesse la pena.

Consiglio vivamente di leggere i post sul blog che collega, descrivono in dettaglio la limitazione degli istogrammi in cui stai correndo, ad esempio:

Conclusione : se hai stringhe abbastanza lunghe e simili in una colonna che è un buon candidato per un istogramma di frequenza (ad esempio una colonna di stato molto descrittiva), allora hai un problema se un valore molto raro sembra identico a un molto popolare valore fino ai primi 32 caratteri. Potresti scoprire che l'unica soluzione è quella di cambiare l'elenco dei valori legali (anche se varie strategie che coinvolgono colonne virtuali o indici basati su funzioni possono aggirare il problema).


Purtroppo gli istogrammi sembrano essere una caratteristica poco conosciuta, immagino sia perché è troppo profonda per uno sviluppatore SQL e il più delle volte funzionano e basta, ma è bello sapere che ci sono molte risorse al riguardo, non stavo cercando posti giusti :). È piuttosto brutto che Oracle tagli a 32 byte e prenda decisioni disastrose basate su quello. Fortunatamente, non ho bisogno di alcun aggiustamento, far cadere gli istogrammi è una soluzione perfetta. I valori chiave sono unici, cerco sempre 20 valori alla volta, funziona bene solo con un indice ed è deterministico. Ma la prossima volta non userò i tasti lunghi, questo è certo.
fejesjoco,

Gli istogrammi sono piuttosto noti tra i DBA;) Adoro il fatto che sembri desideroso di imparare cose più profonde e penso davvero che dovresti leggere il libro di JL che è molto buono. Il CBO in genere fa un ottimo lavoro: ci saranno sempre casi limite che devono essere esaminati, ma vale la pena ricordare che anche senza il taglio, le stime sono sempre solo stime.
Jack Douglas,

1
Se esegui un normale lavoro statistico (come quello che Oracle esegue di default su un'installazione pulita), potresti trovare degli istogrammi riapparire, potresti dover cercare un modo per impedirlo (come forse LOCK_TABLE_STATS )
Jack Douglas

Ho citato un post sul blog nella mia risposta, ci sono istruzioni su come prevenire gli istogrammi per una colonna.
fejesjoco,

1
@Jack Douglas, grazie per aver coinvolto J. Lewis e aver riferito!
Dimitre Radoulov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.