Perché "Seleziona * dalla tabella" è considerato una cattiva pratica


96

Ieri stavo discutendo con un programmatore "hobby" (io stesso sono un programmatore professionista). Ci siamo imbattuti in alcuni dei suoi lavori e ha detto che interroga sempre tutte le colonne del suo database (anche su / nel server / codice di produzione).

Ho provato a convincerlo a non farlo, ma non ho ancora avuto molto successo. A mio avviso, un programmatore dovrebbe solo interrogare ciò che è effettivamente necessario per motivi di "bellezza", efficienza e traffico. Sbaglio con il mio punto di vista?


1
Direi che cosa succede se il contenuto della tabella cambia? aggiungere / rimuovere colonne? stai ancora selezionando * .. così ti mancheranno cose o tirerai indietro più dati di quelli che ti servono.
JF,

2
@JFit Fa parte di questo, ma lontano dall'intera storia.
jwenting



@gnat una domanda può davvero essere considerata un duplicato di una domanda chiusa? (cioè perché quello chiuso non era davvero adatto in primo luogo)
gbjbaanb

Risposte:


67

Pensa a cosa stai tornando e a come associarli a variabili nel tuo codice.

Ora pensa cosa succede quando qualcuno aggiorna lo schema della tabella per aggiungere (o rimuovere) una colonna, anche quella che non stai utilizzando direttamente.

Usare select * quando si digitano le query manualmente va bene, non quando si scrivono query per il codice.


8
Prestazioni, carico di rete, ecc. Sono molto più importanti della comodità di ripristinare le colonne nell'ordine e con il nome desiderato.
jwenting

21
@jwenting davvero? le prestazioni contano più della correttezza? Ad ogni modo, non vedo che "select *" funzioni meglio della selezione delle sole colonne desiderate.
gbjbaanb,

9
@Bratch, negli ambienti di produzione della vita reale, potresti avere centinaia di applicazioni che utilizzano le stesse tabelle e non è possibile che tutte queste applicazioni possano essere gestite correttamente. Hai ragione nel sentiment, ma praticamente l'argomento fallisce solo a causa della realtà del lavoro nelle copmanie. Le modifiche dello schema alle tabelle attive avvengono sempre.
user1068,

18
Non capisco il punto in questa risposta. Se aggiungi una colonna a una tabella, sia SELECT * che SELECT [Columns] funzioneranno, l'unica differenza è che se il codice deve essere associato alla nuova colonna, SELECT [Columns] dovrà essere modificato mentre SELEZIONA * no. Se una colonna viene rimossa da una tabella, SELECT * si interromperà nel punto di associazione, mentre SELECT [Colonne] si interromperà quando viene eseguita la query. Mi sembra che SELEZIONA * sia l'opzione più flessibile in quanto eventuali modifiche alla tabella richiederebbero solo modifiche all'associazione. Mi sto perdendo qualcosa?
TallGuy

11
@gbjbaanb quindi accedi alle colonne per nome. Qualsiasi altra cosa sarebbe ovviamente stupida a meno che tu non abbia specificato l'ordine delle colonne nella query.
immibis,

179

Modifiche allo schema

  • Recupera per ordine --- Se il codice sta recuperando la colonna # come modo per ottenere i dati, una modifica dello schema comporterà la regolazione dei numeri di colonna. Ciò incasinerà l'applicazione e accadranno cose brutte.
  • Recupera per nome --- Se il codice sta recuperando una colonna per nome come foo, e un'altra tabella nella query aggiunge una colonna foo, il modo in cui questa viene gestita può causare problemi quando si cerca di ottenere la colonna giusta foo .

In entrambi i casi, una modifica dello schema può causare problemi con l'estrazione dei dati.

Inoltre, considerare se una colonna in uso viene rimossa dalla tabella. Il select * from ...funziona ancora, ma gli errori fuori quando si cerca di estrarre i dati dal set di risultati. Se la colonna è specificata nella query, la query si spegnerà invece fornendo un'indicazione chiara su cosa e dove si trova il problema.

Sovraccarico di dati

Alcune colonne possono avere una quantità significativa di dati associati ad essi. Selezionando indietro *verranno estratti tutti i dati. Sì, ecco che è varchar(4096)su 1000 righe che hai selezionato indietro dandoti altri 4 megabyte di dati che non ti servono, ma vengono comunque inviati attraverso il filo.

In relazione alla modifica dello schema, quel varchar potrebbe non esistere lì quando hai creato la tabella per la prima volta, ma ora è lì.

Mancata trasmissione dell'intento

Quando selezioni indietro *e ottieni 20 colonne ma ne hai bisogno solo 2, non stai comunicando l'intento del codice. Quando si guarda la query che fa select *uno non si sa quali sono le parti importanti di esso. Posso modificare la query per utilizzare questo altro piano invece di renderlo più veloce non includendo queste colonne? Non lo so perché l'intento di ciò che la query restituisce non è chiaro.


Vediamo alcuni violini SQL che esplorano un po 'di più quelle modifiche allo schema .

Innanzitutto, il database iniziale: http://sqlfiddle.com/#!2/a67dd/1

DDL:

create table one (oneid int, data int, twoid int);
create table two (twoid int, other int);

insert into one values (1, 42, 2);
insert into two values (2, 43);

SQL:

select * from one join two on (one.twoid = two.twoid);

E le colonne che si ottiene indietro sono oneid=1, data=42, twoid=2, e other=43.

Ora, cosa succede se aggiungo una colonna alla tabella uno? http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

E i miei risultati dalla stessa query di prima sono oneid=1, data=42, twoid=2, e other=foo.

Un cambiamento in una delle tabelle interrompe i valori di a select *e all'improvviso il tuo legame di "altro" con un int genererà un errore e non sai perché.

Se invece fosse la tua istruzione SQL

select 
    one.oneid, one.data, two.twoid, two.other
from one join two on (one.twoid = two.twoid);

La modifica alla tabella uno non avrebbe interrotto i tuoi dati. Quella query viene eseguita allo stesso modo prima della modifica e dopo la modifica.


indicizzazione

Quando lo fai, select * fromstai tirando tutte le righe da tutte le tabelle che soddisfano le condizioni. Anche i tavoli di cui non ti importa davvero. Mentre questo significa che vengono trasferiti più dati, c'è un altro problema di prestazioni in agguato nello stack.

Indici. (correlato a SO: come utilizzare l'indice nell'istruzione select? )

Se si stanno ritirando molte colonne, l'ottimizzatore del piano di database potrebbe ignorare l'utilizzo di un indice poiché sarà comunque necessario recuperare tutte quelle colonne e occorrerebbe più tempo per utilizzare l'indice e quindi recuperare tutte le colonne nella query di quello sarebbe solo fare una scansione completa della tabella.

Se stai solo selezionando il, per esempio, il cognome di un utente (che fai molto e quindi hai un indice su di esso), il database può fare solo una scansione dell'indice ( scansione solo dell'indice wiki postgres , scansione della tabella completa mysql vs full scansione indice , scansione solo indice: evitare l'accesso alla tabella ).

Esistono parecchie ottimizzazioni sulla lettura solo dagli indici, se possibile. Le informazioni possono essere estratte più rapidamente su ogni pagina dell'indice perché ne stai estraendo anche di meno - non stai inserendo tutte quelle altre colonne per il select *. È possibile che solo una scansione dell'indice restituisca risultati dell'ordine di 100 volte più veloce (fonte: selezionare * è errato ).

Ciò non significa che una scansione completa dell'indice sia eccezionale, è comunque una scansione completa, ma è meglio di una scansione completa della tabella. Una volta che inizi a inseguire tutti i modi in cui ciò select *danneggia la performance, continui a trovarne di nuovi.

Lettura correlata


2
@Tonny sarei d'accordo - ma quando ho risposto (prima) non avrei mai pensato che questa domanda avrebbe generato molte discussioni e commenti! È ovvio interrogare solo per le colonne con nome, non è vero ?!
gbjbaanb,

3
Rompere tutto aggiungendo una colonna è anche una buona ragione per cui il codice dovrebbe sempre accedere alle colonne in un datareader per nome, non per ordinale hardcoded ...
Julia Hayward,

1
@gbjbaanb Lo è per me. Ma molte persone vengono a scrivere query SQL senza un background / formazione formale. Per loro potrebbe non essere ovvio.
Tonny

1
@Aaronaught L'ho aggiornato con il bit aggiuntivo sui problemi di indicizzazione. Ci sono altri punti che dovrei sollevare per l'errore di select *?

3
Caspita, la risposta accettata è stata così scadente nel spiegare qualsiasi cosa che ho votato in negativo. Stupito che questa non sia la risposta accettata. +1.
Ben Lee,

38

Un'altra preoccupazione: se si tratta di una JOINquery e si stanno recuperando i risultati della query in un array associativo (come potrebbe essere il caso in PHP), è soggetto a bug.

Il fatto è quello

  1. se la tabella fooha colonne idename
  2. se la tabella barha colonne ide address,
  3. e nel tuo codice che stai utilizzando SELECT * FROM foo JOIN bar ON foo.id = bar.id

indovina cosa succede quando qualcuno aggiunge una colonna namealla bartabella.

Il codice smetterà improvvisamente di funzionare correttamente, perché ora la namecolonna appare nei risultati due volte e se si memorizzano i risultati in un array, i dati di second name( bar.name) sovrascriveranno il primo name( foo.name)!

È un bug piuttosto brutto perché è molto ovvio. Può volerci un po 'di tempo per capire, e non c'è modo in cui la persona che aggiunge un'altra colonna alla tabella possa aver previsto un effetto indesiderato indesiderato.

(Storia vera).

Quindi, non usare *, controlla le colonne che stai recuperando e usa gli alias dove appropriato.


va bene in questo caso (che considero un po 'raro) potrebbe essere un grosso problema. Ma potresti comunque evitarlo (e la maggior parte delle persone probabilmente lo farà) eseguendo una query con il carattere jolly e aggiungendo semplicemente un alias per i nomi di colonna identici.
il baconing

4
In teoria, ma se si utilizza un jolly per comodità, ci si affida a esso per fornire automaticamente tutte le colonne esistenti e non preoccuparsi mai di aggiornare la query man mano che le tabelle crescono. Se stai specificando ogni singola colonna, sei costretto ad andare alla query per aggiungerne un'altra alla tua SELECTclausola e questo è quando speri che il nome non sia univoco. A proposito, non penso che sia così raro nei sistemi con database di grandi dimensioni. Come ho detto, una volta ho trascorso un paio d'ore a caccia di questo bug in un grande mudball di codice PHP. E ho trovato un altro caso proprio ora: stackoverflow.com/q/17715049/168719
Konrad Morawski

3
Ho trascorso un'ora la scorsa settimana cercando di ottenere questo attraverso un capo consulenti. Dovrebbe essere un guru di SQL ... Sigh ...
Tonny,

22

Interrogare ogni colonna potrebbe essere perfettamente legittimo, in molti casi.

Interrogare sempre ogni colonna non lo è.

È più lavoro per il tuo motore di database, che deve spegnersi e rovistare nei suoi metadati interni per capire quali colonne deve affrontare prima che possa andare avanti con il vero business di ottenere effettivamente i dati e rispedirli a te. OK, non è il più grande sovraccarico del mondo, ma i cataloghi di sistema possono essere un collo di bottiglia apprezzabile.

È più lavoro per la tua rete, perché stai ritirando un numero qualsiasi di campi quando potresti volerne solo uno o due. Se qualcuno [qualcos'altro] va e aggiunge un paio di dozzine di campi extra, ognuno dei quali contiene grossi pezzi di testo, la tua produttività passa improvvisamente attraverso il pavimento - senza una ragione evidente. Ciò è peggiorato se la clausola "where" non è particolarmente buona e si stanno tirando indietro anche molte righe - questo è potenzialmente un sacco di dati che si fanno strada attraverso la rete (cioè sarà lento).

È più lavoro per la tua applicazione, dover ritirare e archiviare tutti questi dati extra che probabilmente non gli interessano.

Corri il rischio che le colonne cambino il loro ordine. OK, non dovresti preoccuparti di questo (e non lo farai se selezioni solo le colonne di cui hai bisogno) ma, se vai a prenderle tutte in una volta e qualcuno [else] decide di riorganizzare l'ordine delle colonne all'interno della tabella , quell'esportazione CSV accuratamente realizzata che dai ai conti in fondo al corridoio improvvisamente va tutto in pentola - di nuovo, senza una ragione prontamente apparente.

A proposito, ho detto "qualcun altro" un paio di volte, sopra. Ricorda che i database sono intrinsecamente multiutente; potresti non avere il controllo su di loro che pensi di fare.


3
Penso che interrogare sempre ogni colonna possa essere legittimo per cose come le strutture di visualizzazione delle tabelle indipendenti dallo schema. Non una situazione terribilmente comune, ma nel contesto di strumenti esclusivamente per uso interno tali cose possono essere utili.
supercat

1
@supercat Questo è solo l'UNICO caso d'uso valido per un "SELECT *" che mi viene in mente. E anche allora preferirei limitare la query a "SELEZIONA TOP 10 *" (in MS SQL) o aggiungere "LIMIT 10" (mySQL) o aggiungere "WHERE ROWNUM <= 10" (Oracle). Di solito in quel caso si tratta più di "quali colonne ci sono e alcuni dati di esempio" che del contenuto completo.
Tonny

@Tonny: SQL Server ha modificato gli script predefiniti per aggiungere la TOPlimitazione; Non sono sicuro di quanto sia importante se il codice legge quanti ne vuole visualizzare e quindi elimina la query. Penso che le risposte alle query vengano elaborate in modo un po 'pigramente, sebbene non conosca i dettagli. In ogni caso, penso che invece di dirlo "non è legittimo", sarebbe meglio dire "... è legittimo in molto meno"; in sostanza, riassumerei i casi legittimi come quelli in cui l'utente avrebbe un'idea migliore di ciò che è significativo del programmatore.
supercat

@supercat Sono d'accordo. E mi piace molto il modo in cui lo hai inserito nell'ultima frase. Devo ricordare quello.
Tonny,

11

La risposta breve è: dipende dal database che usano. I database relazionali sono ottimizzati per l'estrazione dei dati necessari in modo rapido, affidabile e atomico . Su insiemi di dati di grandi dimensioni e query complesse è molto più veloce e probabilmente più sicuro di SELEZIONARE * e fa l'equivalente dei join sul lato 'code'. I negozi di valori-chiave potrebbero non avere implementate tali funzionalità o potrebbero non essere sufficientemente maturi per essere utilizzati nella produzione.

Detto questo, puoi comunque popolare qualunque struttura di dati stai usando con SELECT * ed elaborare il resto nel codice ma troverai colli di bottiglia delle prestazioni se vuoi ridimensionare.

Il confronto più vicino è l'ordinamento dei dati: è possibile utilizzare quicksort o bubblesort e il risultato sarà corretto. Ma non sarà ottimizzato e sicuramente avrà problemi quando introduci la concorrenza e devi ordinare atomicamente.

Ovviamente, è più economico aggiungere RAM e CPU che investire in un programmatore che può fare query SQL e ha anche una vaga comprensione di cosa sia un JOIN.


Impara SQL! Non è così difficile. È la lingua "nativa" dei database in lungo e in largo. È potente. È elegante. Ha superato la prova del tempo. E non c'è modo di scrivere un join sul lato "codice" che sia più efficiente del join nel database, a meno che non si sia davvero inetti nel fare join SQL. Considera che per eseguire un "code join", devi estrarre tutti i dati da entrambe le tabelle anche in un semplice join a 2 tabelle. Oppure stai estraendo le statistiche dell'indice e le usi per decidere quali dati del tavolo estrarre prima di aderire? Non la pensavo così ... Impara a usare il database correttamente, gente.
Craig,

@Craig: SQL è comune nei database relazionali in lungo e in largo. Questo è lontano dall'unico tipo di DB, però ... e c'è una ragione per cui approcci di database più moderni sono spesso chiamati NoSQL. : P Nessuno che conosco direbbe SQL "elegante" senza una pesante dose di ironia. Fa solo schifo di molte delle alternative, per quanto riguarda i database relazionali.
cHao,

@cHao Sono stato molto consapevole dei vari altri tipi di database là fuori per decenni . Il database Pick "nosql" è in circolazione da sempre. "NoSQL" non è nemmeno lontanamente un nuovo concetto. Anche gli ORM sono in circolazione da sempre e sono sempre stati lenti. Lento! = Buono. Per quanto riguarda l'eleganza (LINQ?), Non puoi convincermi che sia ragionevole o elegante per una clausola where: Customer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();vedi Time to Take Offense a pagina 2.
Craig

@Craig: non farmi nemmeno iniziare su ORM. Quasi tutti i sistemi là fuori lo fanno in modo orribile e l'astrazione perde dappertutto. Questo perché i record DB relazionali non sono oggetti - nella migliore delle ipotesi, sono le viscere serializzabili di una parte di un oggetto. Ma per quanto riguarda LINQ, vuoi davvero andare lì? L'equivalente SQLish è qualcosa di simile var cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();.... e quindi procedi a creare un cliente da ogni riga. LINQ batte i pantaloni.
cHao,

@Craig: Certo, non è così elegante come potrebbe essere. Ma non sarà mai così elegante come vorrei fino a quando non può convertire il codice .net in SQL. :) A quel punto potresti dire var customer = _db.Customers.Where(it => it.id == id).First();.
cHao,

8

IMO, si tratta di essere espliciti contro impliciti. Quando scrivo codice, voglio che funzioni perché l'ho fatto funzionare, non solo perché tutte le parti si trovano lì. Se esegui una query su tutti i record e il codice funziona, avrai la tendenza ad andare avanti. Più tardi se qualcosa cambia e ora il tuo codice non funziona, è un vero problema eseguire il debug di molte query e funzioni alla ricerca di un valore che dovrebbe essere lì e gli unici valori di riferimento sono *.

Anche in un approccio a livelli N, è comunque meglio isolare le interruzioni dello schema del database al livello dati. Se il livello di dati passa * alla logica aziendale e molto probabilmente al livello di presentazione, si sta espandendo in modo esponenziale il proprio ambito di debug.


3
Questo è probabilmente uno dei motivi più importanti qui, e ha solo una piccola frazione dei voti. La manutenibilità di una base di codice disseminata select *è molto peggio!
Eamon Nerbonne,

6

perché se la tabella ottiene nuove colonne, allora ottieni tutte quelle anche quando non ne hai bisogno. con varcharsquesto può diventare un sacco di dati extra che devono viaggiare dal DB

alcune ottimizzazioni DB possono anche estrarre i record di lunghezza non fissa in un file separato per accelerare l'accesso alle parti di lunghezza fissa, usando select * sconfigge lo scopo di quello


1

A parte il sovraccarico, qualcosa che vuoi evitare in primo luogo, direi che come programmatore non dipendi dall'ordine delle colonne definito dall'amministratore del database. Seleziona ogni colonna anche se ne hai bisogno tutte.


3
D'accordo, anche se consiglierei di estrarre i valori da un set di risultati in base al nome della colonna in ogni caso.
Rory Hunter,

Distaccato, portato. Utilizzare i nomi delle colonne, non dipendono dall'ordine delle colonne. L'ordine delle colonne è una dipendenza fragile. I nomi dovrebbero essere (speri) derivati ​​da un vero sforzo di progettazione, oppure alias esplicitamente colonne composte o calcoli o nomi di colonne in conflitto nella query e fare riferimento all'alias esplicito specificato. Ma fare affidamento sull'ordine è praticamente solo nastro adesivo e preghiera ...
Craig,

1

Non vedo alcun motivo per cui non dovresti usare per lo scopo che è build - recuperare tutte le colonne da un database. Vedo tre casi:

  1. Una colonna viene aggiunta nel database e la si desidera anche nel codice. a) Con * fallirà con un messaggio appropriato. b) Senza * funzionerà, ma non farà ciò che ti aspetti, il che è piuttosto male.

  2. Una colonna viene aggiunta nel database e non la si desidera nel codice. a) Con * fallirà; questo significa che * non si applica più poiché la sua semantica significa "recupera tutto". b) Senza * funzionerà.

  3. Una colonna viene rimossa Il codice fallirà in entrambi i modi.

Ora il caso più comune è il caso 1 (dato che hai usato *, il che significa che probabilmente vorrai tutto); senza * puoi avere un codice che funziona bene ma non fa quello che ci si aspetta, il che è molto, molto peggio di quel codice che fallisce con un messaggio di errore adeguato .

Non sto prendendo in considerazione il codice che recupera i dati della colonna in base all'indice di colonna, che secondo me è soggetto a errori. È molto più logico recuperarlo in base al nome della colonna.


La tua premessa è errata. Select *era inteso più come una comodità per l'interrogazione ad hoc, non per scopi di sviluppo dell'applicazione. O per l'uso in costrutti statistici come quelli select count(*)che consentono al motore di query di decidere se utilizzare un indice, quale indice utilizzare e così via e non si stanno restituendo dati di colonne effettivi. O per l'uso in clausole simili where exists( select * from other_table where ... ), che è di nuovo un invito al motore di query a scegliere autonomamente il percorso più efficiente e la subquery viene utilizzata solo per vincolare i risultati dalla query principale. Ecc.
Craig

@Craig Credo che ogni libro / tutorial su SQL affermi che select *ha la semantica del recupero di tutte le colonne; se la tua applicazione ha davvero bisogno di questo, non vedo alcun motivo per cui non usarlo. Puoi indicare qualche riferimento (Oracle, IBM, Microsoft ecc.) Che menziona lo scopo per il quale è select *stato costruito non è quello di recuperare tutte le colonne?
m3th0dman,

Bene, ovviamente select *esiste per recuperare tutte le colonne ... come funzione di convenienza, per interrogazioni ad hoc, non perché è una grande idea nel software di produzione. I motivi sono già stati trattati abbastanza bene nelle risposte in questa pagina, motivo per cui non ho creato la mia risposta dettagliata: •) problemi di prestazioni, dati ripetuti sul marshalling della rete che non si utilizzano mai, •) problemi con l'aliasing delle colonne, •) errori di ottimizzazione del piano di query (in alcuni casi mancato utilizzo degli indici), •) I / O del server inefficiente nei casi in cui una selezione limitata avrebbe potuto utilizzare esclusivamente gli indici, ecc.
Craig

Forse c'è un caso limite qui o là che giustifica l'uso select *in una vera applicazione di produzione, ma la natura di un caso limite è che non è il caso comune . :-)
Craig

@Craig I motivi sono contro il recupero di tutte le colonne da un database e non contro l'utilizzo select *; quello che stavo dicendo se hai davvero bisogno di tutte le colonne, non vedo alcun motivo per cui non dovresti usare select *; anche se pochi devono esserci scenari in cui sono necessarie tutte le colonne.
m3th0dman,

1

Pensala in questo modo ... se esegui una query su tutte le colonne da una tabella che ha solo una piccola stringa o campi numerici, per un totale di 100k di dati. Cattiva pratica, ma si esibirà. Ora aggiungi un singolo campo che contiene, diciamo, un'immagine o un documento di 10 MB. ora la tua query con prestazioni veloci inizia immediatamente e misteriosamente a funzionare male, solo perché un campo è stato aggiunto alla tabella ... potresti non aver bisogno di quell'enorme elemento di dati, ma perché l'hai fatto Select * from Tablelo ottieni comunque.


6
questo sembra semplicemente ripetere il punto già espresso poche ore fa in una prima risposta e in un paio di altre risposte
moscerino
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.