2012-06-27 9 views
10

Ho una tabella con 1,5 milioni di righe. Eseguo una query che recupera i record con valori non ripetuti in una colonna. Sto osservando un comportamento in cui, dopo aver creato gli indici, le prestazioni della query si riducono. Ho anche utilizzato dbms_stats con una percentuale di stima del 100% (modalità di calcolo) per raccogliere le statistiche in modo che Oracle 11g CBO prenda una decisione più informata per il piano di query, ma non migliori il tempo di esecuzione della query.La query viene eseguita più lentamente dopo la creazione degli indici e viene utilizzato il calcolo dbms_stats

SQL> desc tab3; 
Name     Null? Type 
---------------------------------------------- 
COL1       NUMBER(38) 
COL2       VARCHAR2(100) 
COL3       VARCHAR2(36) 
COL4       VARCHAR2(36) 
COL5       VARCHAR2(4000) 
COL6       VARCHAR2(4000) 
MEASURE_0      VARCHAR2(4000) 
MEASURE_1      VARCHAR2(4000) 
MEASURE_2      VARCHAR2(4000) 
MEASURE_3      VARCHAR2(4000) 
MEASURE_4      VARCHAR2(4000) 
MEASURE_5      VARCHAR2(4000) 
MEASURE_6      VARCHAR2(4000) 
MEASURE_7      VARCHAR2(4000) 
MEASURE_8      VARCHAR2(4000) 
MEASURE_9      VARCHAR2(4000) 

La colonna measure_0 ha 0,4 milioni di valori univoci.

SQL> select count(*) from (select measure_0 from tab3 group by measure_0 having count(*) = 1) abc; 

    COUNT(*) 
---------- 
    403664 

Il seguente è la query con il piano di esecuzione, si prega di notare non ci sono gli indici della tabella.

SQL> set autotrace traceonly; 

SQL> SELECT * FROM (
    2  SELECT 
    3    (ROWNUM -1) AS COL1, 
    4    ft.COL1   AS OLD_COL1, 
    5    ft.COL2, 
    6    ft.COL3, 
    7    ft.COL4, 
    8    ft.COL5, 
    9    ft.COL6, 
10    ft.MEASURE_0, 
11    ft.MEASURE_1, 
12    ft.MEASURE_2, 
13    ft.MEASURE_3, 
14    ft.MEASURE_4, 
15    ft.MEASURE_5, 
16    ft.MEASURE_6, 
17    ft.MEASURE_7, 
18    ft.MEASURE_8, 
19    ft.MEASURE_9 
20  FROM tab3 ft 
21  WHERE MEASURE_0 IN 
22  (
23    SELECT MEASURE_0 
24    FROM tab3 
25    GROUP BY MEASURE_0 
26    HAVING COUNT(*) = 1 
27  ) 
28 ) ABC WHERE COL1 >= 0 AND COL1 <=449; 

450 rows selected. 

Elapsed: 00:00:01.90 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 3115757351 

------------------------------------------------------------------------------------ 
| Id | Operation    | Name  | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------ 
| 0 | SELECT STATEMENT  |   | 1243 | 28M| 717K (1)| 02:23:29 | 
|* 1 | VIEW     |   | 1243 | 28M| 717K (1)| 02:23:29 | 
| 2 | COUNT     |   |  |  |   |   | 
|* 3 | HASH JOIN   |   | 1243 | 30M| 717K (1)| 02:23:29 | 
| 4 |  VIEW    | VW_NSO_1 | 1686K| 3219M| 6274 (2)| 00:01:16 | 
|* 5 |  FILTER    |   |  |  |   |   | 
| 6 |  HASH GROUP BY  |   |  1 | 3219M| 6274 (2)| 00:01:16 | 
| 7 |  TABLE ACCESS FULL| TAB3  | 1686K| 3219M| 6196 (1)| 00:01:15 | 
| 8 |  TABLE ACCESS FULL | TAB3  | 1686K| 37G| 6211 (1)| 00:01:15 | 
------------------------------------------------------------------------------------ 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    3 - access("MEASURE_0"="MEASURE_0") 
    5 - filter(COUNT(*)=1) 

Note 
----- 
    - dynamic sampling used for this statement (level=2) 


Statistics 
---------------------------------------------------------- 
     354 recursive calls 
      0 db block gets 
     46518 consistent gets 
     45122 physical reads 
      0 redo size 
     43972 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

L'interrogazione riprende 1,90 secondi. Se eseguo nuovamente la query, sono necessari 1,66 secondi. Perché ci vuole più tempo alla prima esecuzione?

Per accelerarlo ho creato indici sulle due colonne utilizzate nella query.

SQL> create index ind_tab3_orgid on tab3(COL1); 

Index created. 

Elapsed: 00:00:01.68 
SQL> create index ind_tab3_msr_0 on tab3(measure_0); 

Index created. 

Elapsed: 00:00:01.83 

Quando ho licenziato la query dopo questo per la prima volta ci sono voluti una convulsa secondo a tornare. Mentre le corse successive lo hanno acquistato a 2.9 secondi. Perché oracle impiega così tanto tempo alla prima esecuzione, si sta scaldando o qualcosa del genere ... mi confonde!

Questo è il piano quando prende 2.9 secondi-

450 rows selected. 

Elapsed: 00:00:02.92 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 240271480 

------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |    | 1243 | 28M| 711K (1)| 02:22:15 | 
|* 1 | VIEW       |    | 1243 | 28M| 711K (1)| 02:22:15 | 
| 2 | COUNT      |    |  |  |   |   | 
| 3 | NESTED LOOPS    |    |  |  |   |   | 
| 4 |  NESTED LOOPS    |    | 1243 | 30M| 711K (1)| 02:22:15 | 
| 5 |  VIEW      | VW_NSO_1  | 1686K| 3219M| 6274 (2)| 00:01:16 | 
|* 6 |  FILTER     |    |  |  |   |   | 
| 7 |  HASH GROUP BY   |    |  1 | 3219M| 6274 (2)| 00:01:16 | 
| 8 |   TABLE ACCESS FULL  | TAB3   | 1686K| 3219M| 6196 (1)| 00:01:15 | 
|* 9 |  INDEX RANGE SCAN   | IND_TAB3_MSR_0 | 1243 |  |  2 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS BY INDEX ROWID| TAB3   | 1243 | 28M| 44 (0)| 00:00:01 | 
------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    6 - filter(COUNT(*)=1) 
    9 - access("MEASURE_0"="MEASURE_0") 

Note 
----- 
    - dynamic sampling used for this statement (level=2) 


Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
    660054 consistent gets 
     22561 physical reads 
      0 redo size 
     44358 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

mi aspettavo il momento di essere inferiore rispetto a quando il tavolo era non indicizzata. Perché la versione indicizzata della tabella richiede più tempo per recuperare i risultati rispetto alla versione non indicizzata? Se non sbaglio è il TABLE ACCESS BY INDEX ROWID che sta prendendo tempo. Posso far rispettare oracle per utilizzare TABLE ACCESS FULL?

Ho quindi raccolto le statistiche sul tavolo in modo che CBO migliori il piano con l'opzione di calcolo. Quindi ora le statistiche sarebbero accurate.

SQL> EXECUTE dbms_stats.gather_table_stats (ownname=>'EQUBE67DP', tabname=>'TAB3',estimate_percent=>null,cascade=>true); 

PL/SQL procedure successfully completed. 

Elapsed: 00:01:02.47 
SQL> set autotrace off; 
SQL> select COLUMN_NAME,NUM_DISTINCT,SAMPLE_SIZE,HISTOGRAM,LAST_ANALYZED from dba_tab_cols where table_name = 'TAB3' ; 

COLUMN_NAME     NUM_DISTINCT SAMPLE_SIZE HISTOGRAM  LAST_ANALYZED 
------------------------------ ------------ ----------- --------------- --------- 
COL1        1502257  1502257 NONE   27-JUN-12 
COL2          0    NONE   27-JUN-12 
COL3          1  1502257 NONE   27-JUN-12 
COL4          0    NONE   27-JUN-12 
COL5        1502257  1502257 NONE   27-JUN-12 
COL6        1502257  1502257 NONE   27-JUN-12 
MEASURE_0       405609  1502257 HEIGHT BALANCED 27-JUN-12 
MEASURE_1       128570  1502257 NONE   27-JUN-12 
MEASURE_2       1502257  1502257 NONE   27-JUN-12 
MEASURE_3       185657  1502257 NONE   27-JUN-12 
MEASURE_4        901  1502257 NONE   27-JUN-12 
MEASURE_5        17  1502257 NONE   27-JUN-12 
MEASURE_6        2202  1502257 NONE   27-JUN-12 
MEASURE_7        2193  1502257 NONE   27-JUN-12 
MEASURE_8        21  1502257 NONE   27-JUN-12 
MEASURE_9        27263  1502257 NONE   27-JUN-12 

ho di nuovo corse la query

450 rows selected. 

Elapsed: 00:00:02.95 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 240271480 

------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |    | 31M| 718G| 8046 (2)| 00:01:37 | 
|* 1 | VIEW       |    | 31M| 718G| 8046 (2)| 00:01:37 | 
| 2 | COUNT      |    |  |  |   |   | 
| 3 | NESTED LOOPS    |    |  |  |   |   | 
| 4 |  NESTED LOOPS    |    | 31M| 62G| 8046 (2)| 00:01:37 | 
| 5 |  VIEW      | VW_NSO_1  | 4057 | 7931K| 6263 (2)| 00:01:16 | 
|* 6 |  FILTER     |    |  |  |   |   | 
| 7 |  HASH GROUP BY   |    |  1 | 20285 | 6263 (2)| 00:01:16 | 
| 8 |   TABLE ACCESS FULL  | TAB3   | 1502K| 7335K| 6193 (1)| 00:01:15 | 
|* 9 |  INDEX RANGE SCAN   | IND_TAB3_MSR_0 |  4 |  |  2 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS BY INDEX ROWID| TAB3   | 779K| 75M|  3 (0)| 00:00:01 | 
------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    6 - filter(COUNT(*)=1) 
    9 - access("MEASURE_0"="MEASURE_0") 


Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
    660054 consistent gets 
     22561 physical reads 
      0 redo size 
     44358 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

Questa volta la query è tornato in 2.9 secondi (a volte ci sono voluti 3.9 secondo troppo).

Il mio obiettivo è ridurre al minimo il tempo di esecuzione della query il più possibile. Ma dopo aver aggiunto gli indici o dopo aver calcolato le statistiche, il tempo di interrogazione ha continuato ad aumentare. Perché sta succedendo questo e come posso migliorare mantenendo gli indici?

+0

Qual è il piano di esecuzione ** senza l'indice? –

+2

Perché la query richiede più tempo alla prima esecuzione? Per prima cosa, sono le 254 chiamate SQL ricorsive segnalate da autotrace. Tutto il lavoro che deve essere fatto per analizzare la query per la sintassi, per la semantica (esistono nomi e nomi di oggetti di riferimento e hai i privilegi su di essi), e poi il lavoro per preparare il piano di esecuzione (quali indici sono disponibili, costi stimati di vari piani possibili). Ci sono molti esercizi pesanti in quella prima esecuzione. – spencer7593

+2

Sulla seconda query, che richiede i 29 secondi della prima esecuzione, è probabile che vengano letti dati fisici ... Oracle sta recuperando blocchi dal disco e riempiendo la cache del buffer. (l'autotrace mostrerebbe un riassunto di ciò, un evento 10046 traccia avrebbe i dettagli per tutte le attese.) – spencer7593

risposta

11

Prima di tutto, lasciatemi citare Tom Kyte:

solo continuare a dire a te stesso più e più

"scansioni complete non sono il male, gli indici non sono buone"
"scansioni complete sono non male, gli indici non sono buone "
'scansioni complete non sono il male, gli indici non sono buone'
'scansioni complete non sono il male, gli indici non sono buone'
" scansioni complete e non sono vil, indici non sono buone"
'scansioni complete non sono il male, gli indici non sono buone'

indici saranno non sempre migliorare le prestazioni, non sono la pallottola d'argento magica (come se una cosa del genere mai esistito :)

Ora stai chiedendo perché ci vuole più tempo con il tuo indice. La risposta è piuttosto semplice:

  • con la scansione completa della tabella: coerente ottiene
  • con l'indice: 660.054 coerente ottiene

In altre parole: Oracle eseguire operazioni più di lettura con il tuo indice piuttosto che con la scansione completa della tabella. Questo accade perché:

  1. completa della tabella SCAN legge le operazioni di massa sono (molti blocchi alla volta) e sono quindi un modo efficiente per leggere un sacco di dati
  2. a volte quando si legge da un indice si finisce per leggere il esatto stesso blocco di dati più di una volta.

Quanto al motivo per l'ottimizzatore ha scelto di utilizzare questo indice, ovviamente, non efficiente, questo è perché anche con esimate_percent=100 e istogrammi completi (che avete raccolto sulla colonna MEASURE_0), un po 'di distribuzione dei dati ancora non possono essere espressi in modo affidabile dalla semplice analisi dell'ottimizzatore. In particolare, la dipendenza da cross-column e cross-table non è ben compresa dall'analizzatore. Ciò porta a stime sbagliate, che portano a una scelta di piano scarsa.

Modifica: sembra che l'ipotesi di lavoro del CBO non funzioni affatto per questo self-join (l'ultima query prevede 31 milioni di righe mentre solo 450 sono selezionate!). Questo è piuttosto sconcertante dal momento che la tabella ha solo 1,5 M righe. Quale versione di Oracle stai usando?

penso troverete che è possibile rimuovere il self-join e quindi migliorare le prestazioni delle query con strumenti di analisi:

SELECT * FROM (
    SELECT (ROWNUM -1) AS COL1, ABC.* 
    FROM (
     SELECT 
       ft.COL1 AS OLD_COL1, 
       [...], 
       COUNT(*) OVER (PARTITION BY MEASURE_O) nb_0 
     FROM tab3 ft 
    ) ABC 
    WHERE nb_0 = 1 
     AND ROWNUM - 1 <= 449 
    ) v 
WHERE COL1 >= 0; 

Lei è stato anche chiedendo perché la prima volta che si esegue una query richiede più tempo in generale. Questo perché ci sono delle cache al lavoro. A livello di database c'è l'SGA in cui tutti i blocchi vengono prima copiati dal disco e quindi possono essere letti più volte (la prima volta che viene interrogato un blocco è sempre una lettura fisica).Quindi alcuni sistemi dispongono anche di una cache di sistema indipendente che restituirà i dati più rapidamente se sono stati letti di recente.

Per approfondimenti:

+0

Grazie, terrei sicuramente a mente il pensiero di Tom Kyte :). Sto usando Oracle 11g. Ho letto su profili SQL, sfortunatamente non penso che i profili SQL possano risolvere il mio problema. I profili SQL si applicano alle query il cui predicato non cambia, la query che ho menzionato qui cambierebbe (l'ultimo filtro 'COL1> = 0 AND COL1 <= 449' può essere qualcosa come' COL1> = 400000 AND COL1 <= 400449'). Ho provato ad usare il suggerimento di campionamento dinamico ma non sono sicuro di farlo bene. Ho inserito un suggerimento di livello di campionamento dinamico 3 nella seconda selezione su 'tab3', ma senza alcun effetto. – rirhs

+0

Ho provato a eseguire la query che hai scritto, ci vogliono circa 14 secondi per tornare indietro. – rirhs

+0

(1) Potresti usare variabili bind :) e (2) hai provato la query con analytics? Ad ogni modo, non credo che l'uso dell'indice sarebbe utile in questo caso: stai praticamente impaginando l'intera tabella con un filtro su 'MEASURE_0' che selezionerà quasi la metà delle righe. L'indice è molto buono quando c'è una forte selettività, non tanto quando il filtro è debole. –

3

Come funziona questo codice?

SELECT ROWNUM - 1  AS col1 
,  ft.col1   AS old_col1 
,  ft.col2 
,  ft.col3 
,  ft.col4 
,  ft.col5 
,  ft.col6 
,  ft.measure_0 
,  ft.measure_1 
,  ft.measure_2 
,  ft.measure_3 
,  ft.measure_4 
,  ft.measure_5 
,  ft.measure_6 
,  ft.measure_7 
,  ft.measure_8 
,  ft.measure_9 
FROM tab3 ft 
WHERE NOT EXISTS (SELECT NULL 
        FROM tab3 ft_prime 
        WHERE ft_prime.measure_0 = ft.measure_0 
        AND ft_prime.ROWID <> ft.ROWID) 
AND ROWNUM <= 450; 
+0

Questa query funziona molto bene, è tornata in 0.04 secondi, ma la query che ho menzionato qui cambierà (l'ultimo filtro 'COL1> = 0 E COL1 <= 449' può essere qualcosa come 'COL1> = 400000 E COL1 <= 400449'). Così ho circondato la tua query con una selezione che specificava questo predicato, e ho ottenuto risultati in 1,6 secondi, che è sempre meglio di quanto sta accadendo con la query precedente. Grazie! – rirhs

+0

@shri: Si può lasciare che il filtro dell'intervallo superiore 'ROWNUM <= 450' nella query interna, questo consentirà a Oracle di sapere che sono necessarie al massimo 450 righe. Il filtro aggiuntivo 'COL1> = 0' deve essere nella query esterna, naturalmente. Vedi questo [discussione sull'ottimizzazione dell'impaginazione] (http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:127412348064). –

Problemi correlati