2009-06-01 7 views
6

ho questa query:Faster 'selezionare thing_id distinta, thing_name da table1' in Oracle

select distinct id,name from table1 

Per un dato ID, il nome sarà sempre lo stesso. Entrambi i campi sono indicizzati. Non esiste una tabella separata che associa l'id al nome. La tabella è molto grande (10 milioni di righe), quindi la query potrebbe richiedere del tempo.

Questa query è molto veloce, dal momento che è indicizzato:

select distinct name from table1 

Allo stesso modo per questa query:

select distinct id from table1 

Supponendo che non posso ottenere la struttura del database cambiato (un assunto molto sicuro) che cosa è un modo migliore per strutturare la prima query per le prestazioni?

Modifica per aggiungere un disc sterilizzate della tabella:

 

Name       Null  Type 
------------------------------ -------- ---------------------------- 
KEY       NOT NULL NUMBER 
COL1       NOT NULL NUMBER 
COL2       NOT NULL VARCHAR2(4000 CHAR) 
COL3       VARCHAR2(1000 CHAR) 
COL4       VARCHAR2(4000 CHAR) 
COL5       VARCHAR2(60 CHAR) 
COL6       VARCHAR2(150 CHAR) 
COL7       VARCHAR2(50 CHAR) 
COL8       VARCHAR2(3 CHAR) 
COL9       VARCHAR2(3 CHAR) 
COLA       VARCHAR2(50 CHAR) 
COLB       NOT NULL DATE 
COLC       NOT NULL DATE 
COLD       NOT NULL VARCHAR2(1 CHAR) 
COLE       NOT NULL NUMBER 
COLF       NOT NULL NUMBER 
COLG       VARCHAR2(600 CHAR) 
ID        NUMBER 
NAME       VARCHAR2(50 CHAR) 
COLH       VARCHAR2(3 CHAR) 

20 rows selected 
+0

È possibile aggiungere una clausola WHERE per limitare rapidamente il set di risultati? Non sono sicuro che lo stiate già facendo anche se gli esempi mostrati non hanno una logica di predicato. :) – tom

+0

Quante righe vengono restituite da DISTINCT ID e DISTINCT NAME? – Quassnoi

+0

33 righe per ciascuno. –

risposta

12

[EDIT ULTIME]

mio RISPOSTA ORIGINALE quanto riguarda la creazione dell'indice appropriato (nome, id) per sostituire l'indice (nome) è sotto. (Questa non era una risposta alla domanda originale, che non consentiva modifiche al database.)

Ecco le dichiarazioni che ho non ancora testato. Probabilmente c'è qualche ovvia ragione per cui questi non funzioneranno. Non avevo mai realmente suggerisco dichiarazioni di scrittura come questo (con il rischio di essere inculcato a fondo per tale suggerimento ridicolo.)

Se queste query anche restituire set di risultati, il set ressult sarà simile solo il risultato impostato dal OP query, quasi per errore, sfruttando una garanzia originale sui dati che Don ci ha fornito. Questa dichiarazione NON è equivalente all'SQL originale, queste istruzioni sono progettate per il caso speciale come descritto da Don.

select m1.id 
     , m2.name 
    from (select min(t1.rowid) as min_rowid 
       , t1.id 
      from table1 t1 
      where t1.id is not null 
      group by t1.id 
     ) m1 
     , (select min(t2.rowid) as min_rowid 
      , t2.name from table1 t2 
     where t2.name is not null 
     group by t2.name 
     ) m2 
    where m1.min_rowid = m2.min_rowid 
    order 
    by m1.id 

Diamo disfare che:

  • m1 è una vista in linea che ci ottiene un elenco di valori id distinti.
  • m2 è una vista in linea che ottiene un elenco di valori nome distinti.
  • materializzano i punti di vista m1 e m2
  • partita l'identificativo da m1 e m2 da abbinare id con name

Qualcun altro ha suggerito l'idea di un merge indice. In precedenza avevo respinto quell'idea, un piano di ottimizzazione per abbinare 10 di milioni di rowid senza eliminarne nessuno.

Con sufficientemente bassa cardinalità per id e il nome, e con il piano di ottimizzazione destra:

select m1.id 
     , (select m2.name 
      from table1 m2 
      where m2.id = m1.id 
      and rownum = 1 
     ) as name 
    from (select t1.id 
      from table1 t1 
      where t1.id is not null 
      group by t1.id 
     ) m1 
    order 
    by m1.id 

Diamo disfare che

  • m1 è una vista in linea che ci ottiene un elenco di distinta valori di identificazione.
  • materializzare la vista m1
  • per ogni riga m1, interrogare table1 per ottenere il valore del nome da una singola fila (stopkey)

NOTA IMPORTANTE

Queste dichiarazioni sono FONDAMENTALMENTE diversi dalla query OP. Sono progettati per restituire un set di risultati DIFFERENT rispetto alla query OP. Il si verifica per restituire il set di risultati desiderato a causa di una garanzia non convenzionale sui dati. Don ci ha detto che uno name è determinato da id. (È vero il contrario? È determinato da name? Abbiamo una GARANZIA STATA, non necessariamente applicata dal database, ma una garanzia che possiamo sfruttare?) Per qualsiasi valore ID, ogni riga con quel valore ID avrà lo stesso valore NAME. (E ci sono anche garantiti il ​​contrario è vero, che per ogni valore NAME, ogni riga con quel valore NAME avrà lo stesso valore di ID?)

Se è così, forse possiamo fare uso di tali informazioni. Se ID e NAME vengono visualizzati in coppie distinte, è sufficiente trovare una riga specifica. La "coppia" avrà un ROWID di corrispondenza, che per convenienza risulta essere disponibile da ciascuno degli indici esistenti. Cosa succede se otteniamo il ROWID minimo per ogni ID e ottieni il ROWID minimo per ogni NAME. Non è quindi possibile abbinare lo ID allo NAME in base al ROWID che contiene la coppia? Penso che potrebbe funzionare, data una cardinalità abbastanza bassa. (Cioè, se abbiamo a che fare solo con centinaia di ROWIDs piuttosto che 10s di milioni.)

[/ ULTIME EDIT]

[EDIT]

La domanda è ora aggiornato con le informazioni relative alla tabella, mostra che la colonna ID e la colonna NAME consentono entrambi i valori NULL. Se Don può vivere senza alcun valore NULL restituito nel set di risultati, quindi l'aggiunta del predicato IS NOT NULL su entrambe le colonne potrebbe consentire l'utilizzo di un indice. (NOTA: in un indice Oracle (B-Tree), i valori NULL NON appaiono nell'indice.)

[/ EDIT]

ORIGINALE RISPOSTA:

creare un indice appropriato

create index table1_ix3 on table_1 (name,id) ... ; 

Okay, questo è non la risposta alla domanda che hai posto, ma è la risposta giusta per risolvere il problema delle prestazioni. (Non hai specificato modifiche al database, ma in questo caso la modifica del database è la risposta giusta.)

Nota che se hai un indice definito su (name,id), allora (molto probabilmente) non è necessario un indice su (name), l'ottimizzatore considererà la colonna principale name nell'altro indice.

(UPDATE: come qualcuno più astuto di quanto ho fatto notare, non avevo nemmeno preso in considerazione la possibilità che gli indici esistenti erano indici bitmap e non indici B-tree ...)


Re- valutare la necessità del set di risultati ... è necessario restituire id oppure restituire name essere sufficiente.

select distinct name from table1 order by name; 

Per un particolare nome, si potrebbe presentare una seconda interrogazione per ottenere l'associato id, se e quando lo desideravi ...

select id from table1 where name = :b1 and rownum = 1; 

Se si ha realmente bisogno il set di risultati specificato, puoi provare alcune alternative per vedere se le prestazioni sono migliori. Non nutro molte speranze per uno di questi:

select /*+ FIRST_ROWS */ DISTINCT id, name from table1 order by id; 

o

select /*+ FIRST_ROWS */ id, name from table1 group by id, name order by name; 

o

select /*+ INDEX(table1) */ id, min(name) from table1 group by id order by id; 

UPDATE: come altri hanno acutamente sottolineato, con questo approccio noi' per testare e confrontare le prestazioni di query alternative, che è una sorta di approccio "hit & miss". (Non sono d'accordo sul fatto che sia casuale, ma concordo sul fatto che sia casuale.)

AGGIORNAMENTO: tom suggerisce il suggerimento ALL_ROWS. Non l'avevo considerato, perché ero davvero concentrato sull'ottenere un piano di query usando un INDICE. Sospetto che la query OP stia eseguendo una scansione completa della tabella e probabilmente non è la scansione che sta impiegando del tempo, è l'operazione di ordinamento univoco (< 10g) o un'operazione di hash (10gR2 +) che richiede tempo. (Assente statistiche temporizzate e traccia dell'evento 10046, sto solo indovinando qui.) Ma poi di nuovo, forse è la scansione, chissà, l'alta marea sul tavolo potrebbe essere uscita in una vasta distesa di blocchi vuoti.

Inutile dire che le statistiche sulla tabella devono essere aggiornate e dovremmo utilizzare SQL * Plus AUTOTRACE o almeno ESPLINARE PIANO per esaminare i piani di query.

Ma nessuna delle query alternative suggerite risolve il problema delle prestazioni.

È possibile che gli hint influenzino l'ottimizzatore per selezionare un piano diverso, sostanzialmente soddisfacendo l'ORDER BY da un indice, ma non mi aspetto molte speranze. (Non credo che l'hint FIRST_ROWS funzioni con GROUP BY, potrebbe essere il suggerimento INDEX.) Posso vedere il potenziale per un simile approccio in uno scenario in cui ci sono tracce di blocchi di dati vuoti e scarsamente popolati e che accedono ai dati blocca attraverso un indice, potrebbe effettivamente essere significativamente meno blocchi di dati tirati in memoria ... ma quello scenario sarebbe l'eccezione piuttosto che la norma.


UPDATE: Come Rob van Wijk sottolinea, facendo uso della funzione di traccia Oracle è l'approccio più efficace per identificare e risolvere problemi di prestazioni.

Senza l'output di un output di EXPLAIN PLAN o SQL * Plus AUTOTRACE, sto solo indovinando qui.

Sospetto che il problema di prestazioni in questo momento sia che i blocchi di dati della tabella devono essere referenziati per ottenere il set di risultati specificato.

Non c'è niente da fare intorno ad esso, la query non può essere soddisfatto da solo un indice, dal momento che non v'è un indice che contiene sia le NAME e ID colonne, sia con la colonna ID o NAME come la colonna principale. Le altre due query OP "veloci" possono essere soddisfatte dall'indice senza bisogno di fare riferimento alla riga (blocchi di dati).

Anche se il piano di ottimizzazione per la query utilizzava uno degli indici, deve ancora recuperare la riga associata dal blocco di dati, per ottenere il valore per l'altra colonna. E senza predicato (nessuna clausola WHERE), l'ottimizzatore sta probabilmente optando per una scansione completa della tabella e probabilmente eseguendo un'operazione di ordinamento (< 10g). (Anche in questo caso, un PIANO DI SPIEGAZIONE mostrerebbe il piano di ottimizzazione, come sarebbe AUTOTRACE.)

Sto anche assumendo qui (grande ipotesi) che entrambe le colonne siano definite come NOT NULL.

Si potrebbe anche considerare di definire la tabella come una tabella organizzata indice (IOT), soprattutto se queste sono le uniche due colonne nella tabella. (Un IOT non è una panacea, si tratta con il proprio insieme di problemi di prestazioni.)


Si può provare a riscrivere la query (a meno che questo è un cambiamento database che è anche verboten) Nei nostri ambienti di database , consideriamo una query come parte del database come le tabelle e gli indici.)

Anche in questo caso, senza un predicato, l'ottimizzatore probabilmente non utilizzerà un indice. C'è una possibilità che si potrebbe ottenere il piano di query per utilizzare uno degli indici esistenti per ottenere le prime righe restituite in fretta, con l'aggiunta di un pizzico, provare una combinazione di:

select /*+ INDEX(table1) */ ... 
select /*+ FIRST_ROWS */ ... 
select /*+ ALL_ROWS */ ... 

    distinct id, name from table1; 
    distinct id, name from table1 order by id; 
    distinct id, name from table1 order by name; 
    id, name from table1 group by id, name order by id; 
    id, min(name) from table1 group by id order by id; 
    min(id), name from table1 group by name order by name; 

Con un suggerimento, si può essere in grado di influenza l'ottimizzatore per l'uso di un indice e questo può evitare l'operazione di ordinamento, ma nel complesso richiede più tempo per restituire l'intero set di risultati.

(AGGIORNAMENTO: qualcun altro ha sottolineato che l'ottimizzatore potrebbe scegliere di unire due indici in base a ROWID.Questa è una possibilità, ma senza un predicato per eliminare alcune righe, è probabile che sarà un approccio molto più costoso (corrispondente a 10 s di milioni ROWID) da due indici, specialmente quando nessuna delle righe verrà esclusa sulla base del match.)

Ma tutto ciò che teorizza non equivale a squat senza alcune statistiche sulle prestazioni.


Assente alterare qualsiasi altra cosa nel database, l'unica altra speranza (mi viene in mente) di voi accelerare la query è quello di assicurarsi l'operazione di ordinamento è sintonizzato in modo che il (richiesto) operazione di ordinamento può essere eseguito in memoria, piuttosto che su disco. Ma non è davvero la risposta giusta. L'ottimizzatore potrebbe non eseguire alcuna operazione di ordinamento, potrebbe invece eseguire un'operazione di hash (10gR2 +), nel qual caso, dovrebbe essere regolato. L'operazione di ordinamento è solo una supposizione da parte mia, sulla base dell'esperienza passata con Oracle 7.3, 8, 8i, 9i.)

Un DBA grave sta per avere più problema con voi futzing con la SORT_AREA_SIZE e/o HASH_AREA_SIZE parametri per la tua sessione (s) di quello che farà nella creazione degli indici corretti. (E quei parametri di sessione sono "old school" per versioni precedenti alla 10g gestione automatica della memoria magica.)

Mostra al tuo DBA le specifiche per il set di risultati, lascia che il DBA si sintonizzi.

+0

Sì, sono colonne, non campi. Si noti inoltre che "creare indice" costituisce una modifica del database. –

+0

@Don, sì, il 'creare indice' è una modifica del database. Hai definito gli indici sbagliati. L'indice su (nome) dovrebbe essere sostituito con un indice su (nome, id). Questa è la risposta giusta. – spencer7593

+0

'Supponendo che non riesca a ottenere la modifica della struttura del database' è un elemento chiave della domanda che ho posto.Tutte le informazioni sono buone, non è utile nel mio contesto. –

0

Si potrebbe provare questo:

select id, max(name) from table1 group by id 

Questa utilizza l'indice su id di sicuro, ma bisogna provare se funziona veloce.

+0

Ho appena provato questo. È ancora lento, però. –

0

Senza voler indulgere nella pratica di gettare roba al muro fino a quando qualcosa bastoni, provate questo:

select id, name from table1 group by id, name 

Ho vaghi ricordi di un GROUP BY essere inspiegabilmente più veloce di un DISTINCT.

+0

Siamo spiacenti, non si è bloccato. Grazie comunque, comunque, valeva la pena sparare. –

+0

È vero. La parola chiave DISTINCT non solo raggruppa ma ordina anche i risultati. – tom

+0

DISTINCT non sempre ORDINA da 10g in su – cagcowboy

0

Perché è necessario avere il "nome" nella clausola se il nome è sempre lo stesso per un determinato ID? (Nm ... si desidera che il nome che si sta verificando non solo per l'esistenza)

SELECT name, id FROM table WHERE id in (SELECT DISTINCT id FROM table)? 

Non so se questo aiuta ...

+0

Per mostrare il nome all'utente. –

+0

Sì, l'ho capito e l'ho aggiunto in paren ... – GreenieMeanie

0

è l'ID unico? In tal caso, è possibile eliminare DISTINCT dalla query. Se no, forse ha bisogno di un nuovo nome? Sì, lo so, non è possibile modificare lo schema ...

+0

Non è unico. Ci sarà un gran numero di righe che hanno tutti lo stesso id e nome, e sto cercando di ottenere la lista. Se c'è qualcosa di simile selezionare first.id, first.name, che farebbe il trucco, suppongo. –

+0

Vuoi dire, SELEZIONA id, nome FROM table WHERE rownum = 1? – tom

+0

bene, tranne che voglio uno di ogni id, non solo un totale –

0

Si potrebbe provare qualcosa di simile

Select Distinct t1.id, t2.name 
FROM (Select Distinct ID From Table) As T1 
INNER JOIN table t2 on t1.id=t2.id 

Select distinct t1.id, t2.name from table t1 
inner Join table t2 on t1.id=t2.id 

Non sono sicuro se questo funzionerà più o meno veloce di quella originale, come io non sono del tutto la comprensione come è impostato il tuo tavolo. Se ogni ID avrà sempre lo stesso nome e l'ID è unico, non vedo realmente il punto del distinto.

+0

Molte righe hanno lo stesso ID, ma un dato ID è sempre associato allo stesso nome. –

2

Una query non può essere ottimizzata osservandola, o suggerendo a caso alcune query equivalenti, indipendentemente da quanto siano significative.

Tu, noi o l'ottimizzatore abbiamo bisogno di conoscere le statistiche sui tuoi dati. E poi puoi misurare con strumenti come EXPLAIN PLAN o SQL Trace/tkprof o anche lo strumento di autotrace semplice da SQL Plus.

ci mostri l'uscita di questo:

set serveroutput off 
select /*+ gather_plan_statistics */ distinct id,name from table1; 
select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); 

E come fa il vostro intero aspetto table1 come? Si prega di mostrare un output descrittivo.

Cordiali saluti, Rob.

+0

+1 @Rob van Wijk: punti eccellenti.La funzione Oracle ci consente di comprendere i problemi di prestazioni, in particolare la traccia dell'evento 10046 per le attese e l'evento 10053 per il piano di ottimizzazione. Ma solo una nota, alcuni degli strumenti di assistenza tuning che ho usato effettivamente _do_ generano query alternative (ableti non casuali ma sistematicamente), ad esempio, sostituendo i predicati IN con i predicati EXISTS, sostituendo DISTINCT con la clausola GROUP BY, aggiungendo suggerimenti e quindi eseguendo le query alternative per confrontare le prestazioni. – spencer7593

+0

Ho eseguito il set di tre query utilizzando Oracle SQL Developer e non c'è output. Forse quello strumento sta intralciando. Per quanto riguarda il secondo, aggiungerò una versione sterilizzata. Ho solo bisogno di rinominare i campi. –

+0

Si prega di provare SQL * Plus (trovato nella cartella bin) –

0

"La tabella è molto grande (10 milioni di righe)" Se non è possibile modificare il database (aggiungere indice ecc.). Quindi la tua query non avrà altra scelta che leggere l'intera tabella. Quindi, in primo luogo, determinare quanto tempo ci vuole (ad esempio, l'ID SELECT, NAME FROM TABLE1). Non lo farai più veloce di così. Il secondo passo che deve fare è il DISTINCT. In 10g + dovrebbe usare un HASH GROUP BY. Prima di ciò è un'operazione SORT. Il primo è più veloce. Se il tuo database è 9i, potresti OTTENERE un miglioramento copiando le 10 milioni di righe in un database 10g e facendolo lì. In alternativa, allocare gocce di memoria (google ALTER SESSION SET SORT_AREA_SIZE). Ciò potrebbe danneggiare altri processi sul database, ma i tuoi DBA non ti offrono molte opzioni.

0

Cerca davvero di risolvere qualcosa con i DBA. Veramente. Tentare di comunicare i benefici e alleviare le loro paure di prestazioni degradate.

Hai un ambiente di sviluppo/database per testare questa roba?

Quanto devono essere tempestivi i dati?

Che ne dici di una copia della tabella già raggruppata per ID e nome con indicizzazione corretta? È possibile configurare un processo batch per aggiornare la nuova tabella una volta la notte.

Ma se questo non dovesse funzionare ...

Come sull'esportazione di tutte le coppie id e nome ad un database alternativo in cui è possibile raggruppare e l'indice a vostro vantaggio e lasciare i DBA con tutta la loro rigidità compiaciuta?

+0

sì, ho un ambiente di test, e sto provando cose che le persone hanno suggerito. Inoltre, non penso che i DBA siano compiaciuti e rigidi. Bene, forse lo sono, ma è legittimo fare attenzione prima di aggiungere ogni indice che ogni app vuole su un tavolo. Quindi, prima di spingere forte per quello, voglio avere le mie anatre di fila. –

+2

Buon affare. L'aggiunta di un indice aggiuntivo avrà probabilmente un impatto minimo sulle letture. Tuttavia, il nuovo indice è un altro oggetto che deve essere aggiornato a causa di inserimenti/aggiornamenti/eliminazioni. Mi scuso per sembrare un po 'offensivo. Non è personale. È solo che a volte mi sento come se ci concentrassimo così tanto su soluzioni tecniche che ci mancano le ovvie soluzioni non tecniche come semplicemente discutere dei nostri bisogni con gli altri nell'IT. – tom

0

Se per un dato id stesso name viene sempre restituito, è possibile eseguire il seguente:

SELECT (
     SELECT name 
     FROM table1 
     WHERE id = did 
       AND rownum = 1 
     ) 
FROM (
     SELECT DISTINCT id AS did 
     FROM table1 
     WHERE id IS NOT NULL 
     ) 

entrambe le query useranno l'indice id.

Se hai ancora bisogno dei valori NULL, eseguire questo:

SELECT (
     SELECT name 
     FROM table1 
     WHERE id = did 
       AND rownum = 1 
     ) 
FROM (
     SELECT DISTINCT id AS did 
     FROM table1 
     WHERE id IS NOT NULL 
     ) 
UNION ALL 
SELECT NULL, name 
FROM table1 
WHERE id IS NULL 
     AND rownum = 1 

questo sarà meno efficiente, dal momento che la seconda query non fa uso di indici, ma si fermerà al primo NULL che incontra: se è vicino all'inizio dei tavoli, quindi sei fortunato.

vedere la voce nel mio blog per i dettagli delle prestazioni:

0

Questo può funzionare meglio. Presume che, come hai detto tu, il nome sia sempre lo stesso per un dato id.

WITH id_list AS (SELECT DISTINCT id FROM table1) 
SELECT id_list.id, (SELECT name FROM table1 WHERE table1.id = id_list.id AND rownum = 1) 
    FROM id_list; 
Problemi correlati