Perché SELECT * è considerato dannoso?


256

Perché è una SELECT *cattiva pratica? Non significherebbe meno codice da modificare se si aggiungesse una nuova colonna desiderata?

Capisco che si SELECT COUNT(*)tratta di un problema di prestazioni su alcuni DB, ma se volessi davvero ogni colonna?


30
SELECT COUNT(*)essere cattivo è incredibilmente vecchio e obsoleto . Per informazioni su SELECT *- vedi: stackoverflow.com/questions/1960036/…
OMG Ponies

8
SELECT COUNT(*)fornisce una risposta diversa a SELECT COUNT(SomeColumn)meno che la colonna non sia una colonna NOT NULL. E l'ottimizzatore può offrire SELECT COUNT(*)un trattamento speciale - e di solito lo fa. Si noti inoltre che WHERE EXISTS(SELECT * FROM SomeTable WHERE ...)viene dato un trattamento caso speciale.
Jonathan Leffler,

3
@Michael Mrozek, in realtà è l'inverso della domanda. Chiedo se sia mai stato dannoso, non se non sia mai stato dannoso.
Theodore R. Smith,

1
@Bytecode Ninja: nello specifico, MySQL con motore MyISAM ha un'ottimizzazione per COUNT (*): mysqlperformanceblog.com/2007/04/10/count-vs-countcol
Piskvor ha lasciato l'edificio il

Risposte:


312

Ci sono davvero tre ragioni principali:

  • Inefficienza nel trasferimento dei dati al consumatore. Quando SELEZIONA *, stai spesso recuperando dal database più colonne di quelle che l'applicazione deve realmente funzionare. Questo fa sì che più dati si spostino dal server di database al client, rallentando l'accesso e aumentando il carico sui computer, oltre a richiedere più tempo per viaggiare attraverso la rete. Ciò è particolarmente vero quando qualcuno aggiunge nuove colonne alle tabelle sottostanti che non esistevano e non erano necessarie quando i consumatori originali hanno codificato il loro accesso ai dati.

  • Problemi di indicizzazione. Prendi in considerazione uno scenario in cui desideri ottimizzare una query su un livello elevato di prestazioni. Se dovessi usare *, e restituisse più colonne di quelle effettivamente necessarie, il server dovrebbe spesso eseguire metodi più costosi per recuperare i tuoi dati di quanto altrimenti potrebbe fare. Ad esempio, non saresti in grado di creare un indice che copra semplicemente le colonne del tuo elenco SELECT e, anche se lo facessi (comprese tutte le colonne [ brivido ]), il ragazzo successivo che è venuto e ha aggiunto una colonna al sottostante tabella farebbe in modo che l'ottimizzatore ignorasse il tuo indice di copertura ottimizzato e probabilmente ti accorgeresti che le prestazioni della tua query calerebbero sostanzialmente senza alcun motivo evidente.

  • Problemi vincolanti. Quando selezioni *, è possibile recuperare due colonne con lo stesso nome da due tabelle diverse. Questo può spesso causare l'arresto anomalo del consumatore di dati. Immagina una query che unisce due tabelle, entrambe contenenti una colonna denominata "ID". Come farebbe un consumatore a sapere quale fosse quale? SELECT * può anche confondere le viste (almeno in alcune versioni di SQL Server) quando cambiano le strutture delle tabelle sottostanti: la vista non viene ricostruita e i dati restituiti possono essere senza senso . E la parte peggiore è che puoi prenderti cura di nominare le tue colonne come vuoi, ma il prossimo ragazzo che arriva potrebbe non avere modo di sapere che deve preoccuparsi di aggiungere una colonna che si scontrerà con il tuo già sviluppato nomi.

Ma non è affatto male per SELECT *. Lo uso liberamente per questi casi d'uso:

  • Query ad hoc. Quando provo a eseguire il debug di qualcosa, specialmente su un tavolo stretto con cui potrei non avere familiarità, SELECT * è spesso il mio migliore amico. Mi aiuta a vedere cosa sta succedendo senza dover fare un carico di ricerche per sapere quali sono i nomi delle colonne sottostanti. Questo diventa un "plus" più grande quanto più lunghi diventano i nomi delle colonne.

  • Quando * significa "una riga". Nei seguenti casi d'uso, SELECT * va bene, e le voci secondo cui è un killer delle prestazioni sono solo leggende urbane che potrebbero aver avuto un po 'di validità molti anni fa, ma non ora:

    SELECT COUNT(*) FROM table;

    in questo caso, * significa "conta le righe". Se dovessi usare un nome di colonna invece di *, conterebbe le righe in cui il valore di quella colonna non era nullo . COUNT (*), per me, porta davvero a casa il concetto che stai contando le righe ed eviti strani casi limite causati dall'eliminazione dei NULL dai tuoi aggregati.

    Lo stesso vale con questo tipo di query:

    SELECT a.ID FROM TableA a
    WHERE EXISTS (
        SELECT *
        FROM TableB b
        WHERE b.ID = a.B_ID);

    in qualsiasi database degno di nota, * significa semplicemente "una riga". Non importa cosa hai inserito nella sottoquery. Alcune persone usano l'ID b nell'elenco SELECT o useranno il numero 1, ma IMO quelle convenzioni sono praticamente insensate. Quello che vuoi dire è "contare la riga", ed è quello che * indica. La maggior parte degli ottimizzatori di query disponibili è abbastanza intelligente da saperlo. (Anche se ad essere sincero, so solo che questo è vero con SQL Server e Oracle.)


17
L'uso di "SELECT id, name" ha la stessa probabilità di "SELECT *" per selezionare due colonne con lo stesso nome da due tabelle diverse quando si utilizzano i join. Il prefisso con il nome della tabella risolve il problema in entrambi i casi.
Michał Tatarynowicz,

1
So che questo è più vecchio, ma è quello che è stato tirato su mentre cercavo su Google, quindi sto chiedendo. "Quando * significa" una riga ". Nei seguenti casi d'uso, SELECT * va bene e le voci secondo cui è un killer delle prestazioni sono solo leggende urbane ..." hai qualche riferimento qui? Questa affermazione è dovuta al fatto che l'hardware è più potente (in tal caso non significa che non sia inefficiente solo perché è meno probabile che tu lo noti). Non sto provando a indovinare di per sé, mi sto solo chiedendo da dove provenga questa affermazione.
Jared,

6
Per quanto riguarda i riferimenti, puoi esaminare i piani di query: sono identici nei casi in cui hai un "*" nella sottoquery rispetto a quando selezioni una colonna. Sono identici perché l'ottimizzatore basato sui costi "riconosce" che semanticamente, stai parlando di qualsiasi riga che soddisfa i criteri - non è una questione di hardware o velocità.
Dave Markle,

4
Un altro vantaggio dell'utilizzo *è che in alcune situazioni può sfruttare meglio i sistemi cache di MySQL. Se si sta eseguendo un gran numero di simili selectquery che richiedono diversi nomi di colonna ( select A where X, select B where X, ...) utilizzando un select * where Xpermetterà la cache di gestire un maggior numero di query che può risultare in un aumento sostanziale delle prestazioni. È uno scenario specifico dell'applicazione, ma vale la pena ricordare.
Ben D,

2
Più di 8 anni dopo, ma voglio aggiungere un punto sull'ambiguità che non è stato menzionato. Lavorare con oltre 200 tabelle in un database e avere una combinazione di convenzioni di denominazione. Durante la revisione del codice che interagisce con i risultati della query, SELECT *impone agli sviluppatori di esaminare gli schemi di tabella interessati, per determinare le colonne interessate / disponibili, ad esempio all'interno di un foreacho serialize. Il compito di esaminare ripetutamente gli schemi per rintracciare ciò che sta accadendo, aumenterà inevitabilmente il tempo totale necessario sia per il debug che per lo sviluppo del codice correlato.
Fyrye,

91

Il carattere asterisco, "*", nell'istruzione SELECT è una scorciatoia per tutte le colonne nelle tabelle coinvolte nella query.

Prestazione

La *scorciatoia può essere più lenta perché:

  • Non tutti i campi sono indicizzati, forzando una scansione completa della tabella - meno efficiente
  • Ciò che si salva per l'invio SELECT *via cavo rischia una scansione completa della tabella
  • Restituzione di più dati del necessario
  • La restituzione di colonne finali utilizzando un tipo di dati a lunghezza variabile può comportare un sovraccarico di ricerca

Manutenzione

Quando si utilizza SELECT *:

  • Qualcuno che non ha familiarità con la base di codice sarebbe costretto a consultare la documentazione per sapere quali colonne vengono restituite prima di poter apportare modifiche competenti. Rendere il codice più leggibile, ridurre al minimo l'ambiguità e il lavoro necessario per le persone che non hanno familiarità con il codice consente di risparmiare più tempo e sforzi a lungo termine.
  • Se il codice dipende dall'ordine delle colonne, SELECT *nasconderà un errore in attesa che si verifichi se a una tabella è stato modificato l'ordine delle colonne.
  • Anche se hai bisogno di ogni colonna al momento della scrittura della query, ciò potrebbe non essere il caso in futuro
  • l'utilizzo complica la profilazione

Design

SELECT *è un anti-pattern :

  • Lo scopo della query è meno ovvio; le colonne utilizzate dall'applicazione sono opache
  • Infrange la regola della modularità sull'uso della tipizzazione rigorosa ogni volta che è possibile. Explicit è quasi universalmente migliore.

Quando utilizzare "SELECT *"?

È accettabile SELECT *quando è esplicita la necessità di ogni colonna nelle tabelle coinvolte, al contrario di ogni colonna esistente al momento della scrittura della query. Il database espanderà internamente * nell'elenco completo delle colonne - non ci sono differenze di prestazioni.

Altrimenti, elenca esplicitamente ogni colonna da utilizzare nella query, preferibilmente durante l'utilizzo di un alias di tabella.


20

Anche se volessi selezionare ogni colonna ora, potresti non voler selezionare ogni colonna dopo che qualcuno ha aggiunto una o più nuove colonne. Se scrivi la query con SELECT *te stai correndo il rischio che a un certo punto qualcuno possa aggiungere una colonna di testo che fa funzionare la tua query più lentamente anche se in realtà non hai bisogno di quella colonna.

Non significherebbe meno codice da modificare se si aggiungesse una nuova colonna desiderata?

È probabile che se desideri effettivamente utilizzare la nuova colonna, dovrai comunque apportare molte altre modifiche al tuo codice. Stai solo salvando , new_column- solo pochi caratteri di battitura.


21
Soprattutto se quella nuova colonna è un BLOB da tre megabyte
Matti Virkkunen,

2
@Matti - Ma speriamo che ci pensino più di "Ehi, lascia cadere un'enorme colonna BLOB su questo tavolo!" . (Sì, gli sciocchi sperano di saperlo ma un ragazzo non può sognare?)
ChaosPandion,

5
Le prestazioni sono un aspetto, ma spesso c'è anche un aspetto di correttezza: la forma del risultato proiettato *può cambiare inaspettatamente e questo può provocare il caos nell'applicazione stessa: colonne a cui fa riferimento ordinale (es. Sqldatareader.getstring (2)) improvvisamente recuperano una colonna diversa , nessuna INSERT ... SELECT *si spezzerà e così via e così via.
Remus Rusanu,

2
@chaos: mettere i blob sui tavoli non farà molto male alle tue prestazioni ... A meno che tu non usi SELECT * ... ;-)
Dave Markle

2
Non dovresti preoccuparti delle prestazioni finché non causano problemi reali. Inoltre, SELECT *non si tratta di salvare pochi personaggi. Si tratta di risparmiare ore di tempo di debug perché è facile dimenticare di specificare nuove colonne aggiunte.
Lewis,

4

Se si denominano le colonne in un'istruzione SELECT, verranno restituite nell'ordine specificato e pertanto potrebbero essere referenziate in modo sicuro dall'indice numerico. Se si utilizza "SELEZIONA *", si potrebbe finire per ricevere le colonne in sequenza arbitraria e quindi utilizzare le colonne in modo sicuro solo per nome. A meno che non si sappia in anticipo cosa si vorrà fare con qualsiasi nuova colonna che viene aggiunta al database, l'azione corretta più probabile è ignorarla. Se stai per ignorare eventuali nuove colonne che vengono aggiunte al database, non c'è alcun vantaggio nel recuperarle.


"può quindi tranquillamente fare riferimento con indice numerico", ma chi sarebbe così stupido da sempre cercare di fare riferimento a una colonna indice numerico al posto del suo nome !? È un anti-pattern molto peggiore rispetto all'utilizzo di select * in una vista.
MGOwen,

@MGOwen: Usare select *e quindi usare le colonne per indice sarebbe orribile, ma usare select X, Y, Zo select A,B,Ce quindi passare il lettore di dati risultante al codice che si aspetta di fare qualcosa con i dati nelle colonne 0, 1 e 2 sembrerebbe un modo perfettamente ragionevole per consentire allo stesso codice di agire su X, Y, Z o A, B, C. Si noti che gli indici delle colonne dipenderebbero dalla loro posizione all'interno dell'istruzione SELECT, piuttosto che dal loro ordine nel database.
Supercat,

3

In molte situazioni, SELECT * causerà errori in fase di esecuzione nell'applicazione, anziché in fase di progettazione. Nasconde la conoscenza delle modifiche alle colonne o dei riferimenti errati nelle applicazioni.


1
In che modo aiuta a nominare le colonne? In SQL Server, le query esistenti, incorporate nel codice o negli SP, non si lamentano fino a quando non vengono eseguite, anche se hai denominato le colonne. I nuovi falliranno quando li testerai, ma per un sacco di tempo dovrai cercare SP interessati dalle modifiche alla tabella. A che tipo di situazioni ti riferisci che verrebbero colti in fase di progettazione?
ChrisA

3

Se vuoi davvero ogni colonna, non ho visto una differenza di prestazioni tra select (*) e la denominazione delle colonne. Il driver per nominare le colonne potrebbe essere semplicemente per essere esplicito su quali colonne ti aspetti di vedere nel tuo codice.

Spesso, tuttavia, non si desidera ogni colonna e la selezione (*) può comportare un lavoro non necessario per il server di database e la trasmissione di informazioni non necessarie sulla rete. È improbabile che causi un problema evidente a meno che il sistema non sia fortemente utilizzato o la connettività di rete sia lenta.


3

Pensalo come una riduzione dell'accoppiamento tra l'app e il database.

Riassumendo l'aspetto "odore di codice":
SELECT *crea una dipendenza dinamica tra l'app e lo schema. Limitare il suo utilizzo è un modo per rendere la dipendenza più definita, altrimenti una modifica al database ha una maggiore probabilità di crash dell'applicazione.


3

Se aggiungi campi alla tabella, verranno automaticamente inclusi in tutte le tue query in cui utilizzi select *. Questo può sembrare conveniente, ma renderà l'applicazione più lenta in quanto stai recuperando più dati del necessario e, a un certo punto, arresterà effettivamente l'applicazione.

Esiste un limite per la quantità di dati che è possibile recuperare in ogni riga di un risultato. Se si aggiungono campi alle tabelle in modo che un risultato superi tale limite, viene visualizzato un messaggio di errore quando si tenta di eseguire la query.

Questo è il tipo di errori che sono difficili da trovare. Apporti una modifica in un posto e esplode in un altro posto che non utilizza affatto i nuovi dati. Potrebbe anche essere una query utilizzata meno frequentemente, quindi ci vuole un po 'di tempo prima che qualcuno la usi, il che rende ancora più difficile connettere l'errore alla modifica.

Se si specificano i campi desiderati nel risultato, si è al sicuro da questo tipo di overflow ambientale.



2

Riferimento tratto da questo articolo.

Non andare mai con "SELEZIONA *",

Ho trovato solo un motivo per utilizzare "SELEZIONA *"

Se hai requisiti speciali e hai creato un ambiente dinamico quando aggiungi o elimina colonna gestisci automaticamente per codice dell'applicazione. In questo caso speciale non è necessario modificare il codice dell'applicazione e del database e ciò influirà automaticamente sull'ambiente di produzione. In questo caso è possibile utilizzare "SELEZIONA *".


1

Generalmente devi adattare i risultati del tuo SELECT * ... strutture di dati di vario tipo. Senza specificare in quale ordine stanno arrivando i risultati, può essere difficile allineare tutto correttamente (e più campi oscuri sono molto più facili da perdere).

In questo modo è possibile aggiungere campi alle tabelle (anche al centro di esse) per vari motivi senza interrompere il codice di accesso sql in tutta l'applicazione.


1

Usare SELECT *quando hai bisogno solo di un paio di colonne significa che molti più dati trasferiti di quelli di cui hai bisogno. Ciò aggiunge l'elaborazione sul database e aumenta la latenza nel trasferimento dei dati al client. Aggiungete a ciò che userà più memoria quando caricata, in alcuni casi significativamente più, come file BLOB di grandi dimensioni, riguarda principalmente l'efficienza.

Oltre a questo, tuttavia, è più facile vedere quando si guarda la query quali colonne vengono caricate, senza dover cercare cosa c'è nella tabella.

Sì, se aggiungi una colonna aggiuntiva, sarebbe più veloce, ma nella maggior parte dei casi, vorresti / devi modificare il codice utilizzando la query per accettare le nuove colonne in ogni caso, e c'è il potenziale che ottenere quelle che non indossi " non voglio / aspettarti può causare problemi. Ad esempio, se prendi tutte le colonne, quindi fai affidamento sull'ordine in un ciclo per assegnare variabili, quindi aggiungendone una o se cambiano gli ordini di colonna (visto che succede quando si ripristina da un backup) può buttare via tutto.

Questo è anche lo stesso tipo di ragionamento per cui se lo stai facendo INSERTdovresti sempre specificare le colonne.


1

Non penso che ci possa essere davvero una regola generale per questo. In molti casi, ho evitato SELECT *, ma ho anche lavorato con framework di dati in cui SELECT * è stato molto utile.

Come per tutte le cose, ci sono vantaggi e costi. Penso che parte dell'equazione vantaggio / costo sia proprio quanto controllo hai sulle strutture dati. Nei casi in cui SELECT * funzionava bene, le strutture dei dati erano strettamente controllate (era un software di vendita al dettaglio), quindi non c'erano molti rischi che qualcuno stesse per inserire un enorme campo BLOB in una tabella.


1

La selezione con il nome della colonna aumenta la probabilità che il motore di database possa accedere ai dati dagli indici anziché interrogare i dati della tabella.

SELEZIONA * espone il tuo sistema a cambiamenti imprevisti di prestazioni e funzionalità nel caso in cui lo schema del database cambi, perché otterrai nuove colonne aggiunte alla tabella, anche se il tuo codice non è pronto per usare o presentare quei nuovi dati.


1

C'è anche una ragione più pragmatica: il denaro. Quando si utilizza il database cloud e si devono pagare i dati elaborati, non vi è alcuna spiegazione per leggere i dati che verranno immediatamente eliminati.

Ad esempio: BigQuery :

Prezzi delle query

Il prezzo della query si riferisce al costo di esecuzione dei comandi SQL e delle funzioni definite dall'utente. BigQuery addebita le query utilizzando una metrica: il numero di byte elaborati.

e controlla la proiezione - Evita SELEZIONA * :

Procedura consigliata: controllare la proiezione: interrogare solo le colonne necessarie.

La proiezione si riferisce al numero di colonne lette dalla query. La proiezione di colonne in eccesso comporta ulteriori I / O (sprecati) e materializzazione (scrittura dei risultati).

L'uso di SELECT * è il modo più costoso per eseguire query sui dati. Quando usi SELECT *, BigQuery esegue una scansione completa di ogni colonna della tabella.


0

Comprendi i tuoi requisiti prima di progettare lo schema (se possibile).

Informazioni sui dati, 1) indicizzazione 2) tipo di memoria utilizzata, 3) motore o funzionalità del fornitore; ovvero ... memorizzazione nella cache, capacità in memoria 4) tipi di dati 5) dimensione della tabella 6) frequenza della query 7) carichi di lavoro correlati se la risorsa è condivisa 8) Test

A) I requisiti possono variare. Se l'hardware non può supportare il carico di lavoro previsto, è necessario rivalutare come fornire i requisiti nel carico di lavoro. Per quanto riguarda la colonna aggiunta alla tabella. Se il database supporta le viste, è possibile creare una vista indicizzata (?) Dei dati specifici con le colonne con nome specifico (anziché selezionare '*'). Rivedi periodicamente i tuoi dati e il tuo schema per assicurarti di non imbatterti mai nella sindrome "Garbage-in" -> "Garbage-out".

Supponendo che non ci sia altra soluzione; puoi prendere in considerazione quanto segue. Esistono sempre più soluzioni a un problema.

1) Indicizzazione: la selezione * eseguirà una scansione della tabella. A seconda di vari fattori, ciò può comportare una ricerca del disco e / o contesa con altre query. Se la tabella è multiuso, assicurati che tutte le query siano performanti ed eseguite al di sotto dei tempi previsti. Se è presente una grande quantità di dati e la tua rete o altra risorsa non è ottimizzata; devi tenerne conto. Il database è un ambiente condiviso.

2) tipo di archiviazione. Vale a dire: se stai usando SSD, disco o memoria. I tempi di I / O e il carico sul sistema / CPU varieranno.

3) Il DBA può ottimizzare il database / le tabelle per prestazioni più elevate? Supponendo per qualsiasi motivo, i team hanno deciso che selezionare '*' è la migliore soluzione al problema; il DB o la tabella possono essere caricati in memoria? (O altro metodo ... forse la risposta è stata progettata per rispondere con un ritardo di 2-3 secondi? --- mentre un annuncio pubblicitario suona per guadagnare le entrate dell'azienda ...)

4) Inizia dalla linea di base. Comprendi i tuoi tipi di dati e come verranno presentati i risultati. Tipi di dati più piccoli, il numero di campi riduce la quantità di dati restituiti nel set di risultati. Questo lascia risorse disponibili per altre esigenze di sistema. Le risorse di sistema hanno generalmente un limite; 'sempre' lavorare al di sotto di questi limiti per garantire stabilità e comportamento prevedibile.

5) dimensione della tabella / dati. selezionare '*' è comune con tabelle minuscole. In genere si adattano alla memoria e i tempi di risposta sono rapidi. Ancora una volta .... rivedi le tue esigenze. Pianificare lo scorrimento delle funzioni; pianificare sempre le esigenze attuali e possibili future.

6) Frequenza di query / query. Essere consapevoli di altri carichi di lavoro sul sistema. Se questa query si attiva ogni secondo e la tabella è minuscola. Il set di risultati può essere progettato per rimanere nella cache / memoria. Tuttavia, se la query è un processo batch frequente con Gigabyte / Terabyte di dati ... potrebbe essere meglio dedicare risorse aggiuntive per garantire che altri carichi di lavoro non siano interessati.

7) Carichi di lavoro correlati. Comprendi come vengono utilizzate le risorse. La rete / sistema / database / tabella / applicazione è dedicata o condivisa? Chi sono gli stakeholder? È per produzione, sviluppo o QA? È una "soluzione rapida" temporanea? Hai provato lo scenario? Rimarrai sorpreso da quanti problemi possono esserci sull'hardware attuale oggi. (Sì, le prestazioni sono veloci ... ma il design / le prestazioni sono ancora degradate.) Il sistema deve eseguire 10K query al secondo contro 5-10 query al secondo. Il server di database è dedicato o esegue altre applicazioni, il monitoraggio viene eseguito sulla risorsa condivisa. Alcune applicazioni / lingue; Gli O / S consumeranno il 100% della memoria causando vari sintomi / problemi.

8) Test: prova le tue teorie e comprendi il più possibile. Il tuo problema "*" selezionato potrebbe essere un grosso problema, o potrebbe essere qualcosa di cui non devi nemmeno preoccuparti.

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.