Domanda

Nel nostro prodotto abbiamo un motore di ricerca generico e stiamo cercando di ottimizzare le prestazioni di ricerca. Molte delle tabelle utilizzate nelle query consentono valori null. Dovremmo ridisegnare la nostra tabella per non consentire valori nulli per l'ottimizzazione o no?

Il nostro prodotto funziona sia su Oracle che MS SQL Server .

È stato utile?

Soluzione

In Oracle , i valori NULL non sono indicizzati, i. e. questa query:

SELECT  *
FROM    table
WHERE   column IS NULL

utilizzerà sempre la scansione della tabella completa poiché l'indice non copre i valori necessari.

Oltre a ciò, questa query:

SELECT  column
FROM    table
ORDER BY
        column

utilizzerà anche la scansione della tabella completa e l'ordinamento per lo stesso motivo.

Se i tuoi valori non consentono intrinsecamente NULL , quindi contrassegnare la colonna come NOT NULL .

Altri suggerimenti

Una risposta in più per attirare un po 'di attenzione in più sul commento di David Aldridge sulla risposta accettata di Quassnoi.

La dichiarazione:

  

questa query:

     

SELEZIONA * DA tabella DOVE colonna   È NULL

     

utilizzerà sempre la scansione della tabella completa

non è vero. Ecco l'esempio del contatore che utilizza un indice con un valore letterale:

SQL> create table mytable (mycolumn)
  2  as
  3   select nullif(level,10000)
  4     from dual
  5  connect by level <= 10000
  6  /

Table created.

SQL> create index i1 on mytable(mycolumn,1)
  2  /

Index created.

SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)

PL/SQL procedure successfully completed.

SQL> set serveroutput off
SQL> select /*+ gather_plan_statistics */ *
  2    from mytable
  3   where mycolumn is null
  4  /

  MYCOLUMN
----------


1 row selected.

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
  2  /

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID  daxdqjwaww1gr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ *   from mytable  where mycolumn
is null

Plan hash value: 1816312439

-----------------------------------------------------------------------------------
| Id  | Operation        | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |      1 |        |      1 |00:00:00.01 |       2 |
|*  1 |  INDEX RANGE SCAN| I1   |      1 |      1 |      1 |00:00:00.01 |       2 |
-----------------------------------------------------------------------------------

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

   1 - access("MYCOLUMN" IS NULL)


19 rows selected.

Come puoi vedere, l'indice è in uso.

Saluti, Rob.

Risposta breve: sì, a condizione!

Il problema principale con valori e prestazioni nulli riguarda le ricerche in avanti.

Se si inserisce una riga in una tabella, con valori null, viene posizionata nella pagina naturale a cui appartiene. Qualsiasi query alla ricerca di quel record lo troverà nel posto appropriato. Facile finora ....

... ma diciamo che la pagina si riempie, e ora quella riga è rannicchiata tra le altre righe. Va ancora bene ...

... fino a quando la riga non viene aggiornata e il valore null ora contiene qualcosa. Le dimensioni della riga sono aumentate oltre lo spazio disponibile, quindi il motore DB deve fare qualcosa al riguardo.

La cosa più veloce da fare per il server è spostare la riga off in quella pagina in un'altra e sostituire la voce della riga con un puntatore in avanti. Sfortunatamente, ciò richiede una ricerca aggiuntiva quando viene eseguita una query: una per trovare la posizione naturale della riga e una per trovare la posizione corrente.

Quindi, la risposta breve alla tua domanda è sì, rendendo quei campi non annullabili aiuterà le prestazioni di ricerca. Ciò è particolarmente vero se capita spesso che i campi null nei record che cerchi siano aggiornati a non null.

Naturalmente, ci sono altre penalità (in particolare I / O, anche se in minima parte l'indice di profondità) associate a set di dati più grandi, e quindi hai problemi di applicazione con la proibizione di null in campi che concettualmente li richiedono, ma hey, questo è un altro problema :)

Se la tua colonna non contiene NULL, è meglio dichiarare questa colonna NOT NULL , l'ottimizzatore potrebbe essere in grado di intraprendere un percorso più efficiente.

Tuttavia, se nella colonna sono presenti NULL, non si ha molta scelta (un valore predefinito non nullo può creare più problemi di quanti ne risolva).

Come accennato da Quassnoi, i NULL non sono indicizzati in Oracle o, per essere più precisi, una riga non verrà indicizzata se tutte le colonne indicizzate sono NULL, ciò significa:

  • che i NULL possono potenzialmente velocizzare la tua ricerca perché l'indice avrà meno righe
  • puoi comunque indicizzare le righe NULL se aggiungi un'altra colonna NOT NULL all'indice o anche una costante.

Il seguente script mostra un modo per indicizzare i valori NULL:

CREATE TABLE TEST AS 
SELECT CASE
          WHEN MOD(ROWNUM, 100) != 0 THEN
           object_id
          ELSE
           NULL
       END object_id
  FROM all_objects;

CREATE INDEX idx_null ON test(object_id, 1);

SET AUTOTRACE ON EXPLAIN

SELECT COUNT(*) FROM TEST WHERE object_id IS NULL;

Direi che è richiesto un test, ma è bello conoscere le esperienze di altre persone. Nella mia esperienza sul server ms sql, i valori null possono causare gravi problemi di prestazioni (differenze). In un test molto semplice ora ho visto un ritorno di query in 45 secondi quando non è stato impostato null sui campi correlati nella tabella create statement e in 25 minuti in cui non è stato impostato (ho rinunciato ad aspettare e ho preso un picco a il piano di query stimato).

I dati di test sono 1 milione di righe x 20 colonne che sono costruite da 62 caratteri alfa minuscoli casuali su un HD normale i5-3320 e 8 GB di RAM (SQL Server che utilizza 2 GB) / SQL Server 2012 Enterprise Edition su Windows 8.1. È importante utilizzare dati casuali / irregolari per rendere il test realistico "peggio" Astuccio. In entrambi i casi la tabella è stata ricreata e ricaricata con dati casuali che hanno impiegato circa 30 secondi su file di database che avevano già una quantità adeguata di spazio libero.

select count(field0) from myTable where field0 
                     not in (select field1 from myTable) 1000000

CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) , ...

 vs

CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) not null,

per motivi di prestazioni entrambi avevano l'opzione table data_compression = page set e tutto il resto era predefinito. Nessun indice.

alter table myTable rebuild partition = all with (data_compression = page);

Non avere null è un requisito nelle tabelle ottimizzate per la memoria per le quali non sto usando specificamente, tuttavia il server sql farà ovviamente ciò che è più veloce, che in questo caso specifico sembra essere ampiamente a favore di non avere null nei dati e usare no null sulla tabella create.

Qualsiasi query successiva dello stesso modulo su questa tabella ritorna in due secondi, quindi suppongo che le statistiche standard predefinite e possibilmente che la tabella (1.3GB) si adatti alla memoria funzionino bene. cioè.

select count(field19) from myTable where field19 
                       not in (select field18 from myTable) 1000000

A parte il fatto di non avere valori nulli e di non avere a che fare con casi nulli rende le query molto più semplici, più brevi, meno soggette a errori e molto più veloci. Se possibile, meglio evitare i null generalmente sul server ms sql almeno se non sono esplicitamente richiesti e non possono ragionevolmente essere risolti dalla soluzione.

Iniziare con una nuova tabella e dimensionare questa query fino a 10m righe / 13GB richiede 12 minuti, il che è molto rispettabile considerando l'hardware e nessun indice in uso. Per informazioni, la query era completamente legata all'IO con un IO compreso tra 20 MB / sa 60 MB / s. Una ripetizione della stessa query ha richiesto 9 minuti.

I campi nullable possono avere un grande impatto sulle prestazioni quando si esegue " NOT IN " interrogazioni. Poiché le righe con tutti i campi indicizzati impostati su null non sono indicizzate in indici B-Tree, Oracle deve eseguire una scansione completa della tabella per verificare la presenza di valori null, anche quando esiste un indice.

Ad esempio:

create table t1 as select rownum rn from all_objects;

create table t2 as select rownum rn from all_objects;

create unique index t1_idx on t1(rn);

create unique index t2_idx on t2(rn);

delete from t2 where rn = 3;

explain plan for
select *
  from t1
 where rn not in ( select rn
                     from t2 );

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      | 50173 |   636K|  3162   (1)| 00:00:38 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| T1   | 50205 |   637K|    24   (5)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| T2   | 45404 |   576K|     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

La query deve verificare la presenza di valori null quindi deve eseguire una scansione completa della tabella di t2 per ogni riga in t1.

Ora, se rendiamo i campi non annullabili, può usare l'indice.

alter table t1 modify rn not null;

alter table t2 modify rn not null;

explain plan for
select *
  from t1
 where rn not in ( select rn
                     from t2 );

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |  2412 | 62712 |    24   (9)| 00:00:01 |
|   1 |  NESTED LOOPS ANTI |        |  2412 | 62712 |    24   (9)| 00:00:01 |
|   2 |   INDEX FULL SCAN  | T1_IDX | 50205 |   637K|    21   (0)| 00:00:01 |
|*  3 |   INDEX UNIQUE SCAN| T2_IDX | 45498 |   577K|     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Il problema di utilizzare Null perché influiscono sulle prestazioni è uno di quegli atti di bilanciamento nella progettazione di database. Devi bilanciare le esigenze aziendali con le prestazioni.

I null dovrebbero essere usati se sono necessari. Ad esempio, potresti avere una data di inizio e una data di fine in una tabella. Spesso non si conosce la data di fine al momento della creazione del record. Pertanto, è necessario consentire i valori null indipendentemente dal fatto che influiscano o meno sulle prestazioni poiché i dati semplicemente non sono lì per essere inseriti. Tuttavia, se i dati devono, in base alle regole aziendali, essere presenti al momento della creazione del record, non è necessario consentire null. Ciò migliorerebbe le prestazioni, renderebbe la codifica un po 'più semplice e garantire l'integrità dei dati.

Se disponi di dati esistenti che desideri modificare per non consentire più valori null, devi considerare l'impatto di tale modifica. Innanzitutto, sai quale valore devi inserire nei record che sono attualmente nulli? In secondo luogo, hai un sacco di codice che utilizza isnull o coalesce che devi aggiornare (queste cose rallentano le prestazioni, quindi se non hai più bisogno di controllarle) , dovresti cambiare il codice)? Hai bisogno di un valore predefinito? Puoi davvero assegnarne uno? Altrimenti parte del codice insert o update si interromperà se non si considera che il campo non può più essere nullo. A volte le persone inseriranno informazioni errate per consentire loro di eliminare i null. Quindi ora il campo del prezzo deve contenere valori decimali e cose come "sconosciuto" e quindi non può essere correttamente un tipo di dati decimale e quindi devi fare tutti i tipi di lunghezze per fare calcoli. Questo spesso crea problemi di prestazioni peggiori o peggiori del valore nullo creato. Perché hai bisogno di esaminare tutto il tuo codice e ovunque tu abbia usato un riferimento al fatto che è archiviato come nullo o non nullo, devi riscrivere per escludere o includere in base ai possibili valori errati che qualcuno inserirà perché i dati non sono consentiti essere nullo.

Eseguo molte importazioni di dati dai dati client e ogni volta che otteniamo un file in cui non esiste un campo che dovrebbe consentire valori null, otteniamo dati inutili che devono essere ripuliti prima di importarli nel nostro sistema. L'email è una di queste. Spesso i dati vengono immessi non conoscendo questo valore ed è generalmente un tipo di dati stringa, quindi l'utente può digitare qualsiasi cosa qui. Andiamo a importare e-mail e troviamo cose "non lo so". Difficile provare a inviare effettivamente un'email a " Non lo so " ;. Se il sistema richiede un indirizzo e-mail valido e verifica la presenza di qualcosa come l'esistenza di un segno @, otterremmo 'I@dont.know" In che modo i dati spazzatura come questo sono utili per gli utenti dei dati?

Alcuni dei problemi di prestazioni con valori null sono il risultato della scrittura di query non espandibili. A volte solo riorganizzare la clausola where anziché eliminare un null necessario può migliorare le prestazioni.

Nella mia esperienza NULL è un valore valido e di solito significa "non so". Se non lo sai, è davvero inutile recuperare un valore predefinito per la colonna o provare a imporre un vincolo NOT NULL. NULL è solo un caso specifico.

La vera sfida per i NULL è complicare un po 'il recupero. Ad esempio, non puoi dire DOVE nome_colonna IN (NULL, 'valore1', 'valore2').

Personalmente, se trovi molte delle tue colonne o determinate colonne contengono molti NULL, penso che potresti voler rivisitare il tuo modello di dati. Forse quelle colonne null possono essere inserite in una tabella figlio? Ad esempio: una tabella con numeri di telefono in cui è il nome, il telefono di casa, il cellulare, il numero di fax, il numero di lavoro, il numero di emergenza ecc ... Puoi popolare solo uno o due di questi e sarebbe meglio normalizzarlo.

Quello che devi fare è tornare indietro e vedere come si accederà ai dati. È una colonna che dovrebbe avere un valore? È una colonna che ha un valore solo per alcuni casi? È una colonna che verrà interrogata molto?

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