Select * è ancora un grande no-no su SQL Server 2012?


41

Ai tempi di ieri, era considerato un grande no-no da fare select * from tableo select count(*) from tableper il successo delle prestazioni.

È ancora così nelle versioni successive di SQL Server (sto usando il 2012, ma suppongo che la domanda si applicherebbe al 2008-2014)?

Modifica: Dal momento che le persone sembrano darmi una leggera spinta qui, sto guardando questo da un punto di vista accademico / di riferimento, non se sia la cosa "giusta" da fare (che ovviamente non lo è)

Risposte:


50

Se si SELECT COUNT(*) FROM TABLErestituisce solo una riga (il conteggio), è relativamente leggero ed è il modo per ottenere quel dato.

E SELECT *non è un no-no fisico, in quanto è legale e permesso.

Tuttavia, il problema SELECT *è che è possibile causare molti più movimenti di dati. Operi su ogni colonna della tabella. Se includi SELECTsolo alcune colonne, potresti essere in grado di ottenere la tua risposta da uno o più indici, il che riduce l'I / O e anche l'impatto sulla cache del server.

Quindi, , è sconsigliato come pratica generale perché è uno spreco di risorse.

L'unico vero vantaggio di SELECT *non è digitare tutti i nomi delle colonne. Ma da SSMS è possibile utilizzare il trascinamento della selezione per ottenere i nomi delle colonne nella query ed eliminare quelli non necessari.

Un'analogia: se qualcuno usa SELECT *quando non ha bisogno di ogni colonna, userebbe ancheSELECT senza una WHERE(o qualche altra clausola di limitazione) quando non ha bisogno di ogni riga?


24

Oltre alla risposta già fornita dal provider, ritengo che valga la pena sottolineare che gli sviluppatori sono spesso troppo pigri quando lavorano con ORM moderni come Entity Framework. Mentre i DBA fanno del loro meglio per evitare SELECT *, gli sviluppatori spesso scrivono il semanticamente equivalente, ad esempio, in c # Linq:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User").ToList();

In sostanza, ciò comporterebbe quanto segue:

SELECT * FROM MyTable WHERE FirstName = 'User'

C'è anche un sovraccarico aggiuntivo che non è già stato coperto. Queste sono le risorse necessarie per elaborare ciascuna colonna di ogni riga nell'oggetto rilevante. Inoltre, per ogni oggetto tenuto in memoria, quell'oggetto deve essere ripulito. Se hai selezionato solo le colonne di cui hai bisogno, potresti facilmente salvare oltre 100 MB di RAM. Sebbene non sia un importo enorme da solo, è l'effetto cumulativo della raccolta dei rifiuti ecc. Che è il lato cliente dei costi.

Quindi sì, almeno per me, è e sarà sempre un grande no. Dobbiamo anche essere istruiti sui costi "nascosti" per farlo di più.

appendice

Ecco un esempio di come estrarre solo i dati necessari come richiesto nei commenti:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User")
                             .Select(entity => new { entity.FirstName, entity.LastNight });

13

Prestazioni: una query con SELECT * probabilmente non sarà mai una query di copertura ( spiegazione di Simple Talk , spiegazione di Stack Overflow ).

A prova di futuro: la tua query potrebbe restituire tutte e sette le colonne oggi, ma se qualcuno aggiunge cinque colonne nel corso dell'anno successivo, in un anno la query restituirà dodici colonne, sprecando IO e CPU.

Indicizzazione: se si desidera che le viste e le funzioni con valori di tabella partecipino all'indicizzazione in SQL Server, è necessario creare tali viste e funzioni con schema, che proibisce l'uso di SELECT *.

Best practice : non utilizzare mai SELECT *nel codice di produzione.

Per le subquery, preferisco WHERE EXISTS ( SELECT 1 FROM … ).

Modifica : Per rispondere al commento di Craig Young di seguito, l'uso di "SELEZIONA 1" in una sottoquery non è un "'ottimizzazione" - è così che posso alzarmi di fronte alla mia classe e dire "non usare SELEZIONA *, senza eccezioni! "

L'unica eccezione a cui riesco a pensare è dove il client sta eseguendo una sorta di operazione di tabella pivot e richiede tutte le colonne presenti e future.

Potrei accettare un'eccezione che coinvolge CTE e tabelle derivate, anche se vorrei vedere i piani di esecuzione.

Nota che considero COUNT(*)un'eccezione a questo perché è un diverso uso sintattico di "*".


10

In SQL Server 2012 (o qualsiasi versione dal 2005 in poi) l'utilizzo SELECT *...è solo un possibile problema di prestazioni nell'istruzione SELECT di livello superiore di una query.

Quindi NON è un problema in Views (*), nelle sottoquery, nelle clausole EXIST, nei CTE, né in SELECT COUNT(*)..ecc. Ecc. Si noti che questo è probabilmente vero anche per Oracle, DB2 e forse PostGres (non sono sicuro) , ma è molto probabile che sia ancora un problema in molti casi per MySql.

Per capire perché (e perché può ancora essere un problema in un SELECT di primo livello), è utile capire perché è mai stato un problema, perché usare SELECT *..significa " restituire TUTTE le colonne ". In generale, ciò restituirà molti più dati di quanto si desideri, il che ovviamente può comportare molti più IO, sia su disco che in rete.

Ciò che è meno ovvio è che ciò limita anche gli indici e i piani di query che un ottimizzatore SQL può utilizzare, poiché sa che alla fine deve restituire tutte le colonne di dati. Se è in grado di sapere in anticipo che si desidera solo determinate colonne, spesso è possibile utilizzare piani di query più efficienti sfruttando gli indici che hanno solo quelle colonne. Fortunatamente c'è un modo per saperlo in anticipo, che è per te specificare esplicitamente le colonne che desideri nell'elenco delle colonne. Ma quando usi "*", lo stai perdendo a favore di "dammi tutto, capirò di cosa ho bisogno".

Sì, c'è anche un ulteriore utilizzo di CPU e memoria per l'elaborazione di ogni colonna, ma è quasi sempre minore rispetto a queste due cose: la notevole larghezza di banda aggiuntiva del disco e della rete richiesta per le colonne che non ti servono e devi usare un piano di query ottimizzato perché deve includere ogni colonna.

Quindi cosa è cambiato? Fondamentalmente, gli Ottimizzatori SQL hanno incorporato con successo una funzione chiamata "Ottimizzazione delle colonne" che significa semplicemente che ora possono capire nelle sottoquery di livello inferiore se si intende effettivamente utilizzare una colonna nei livelli superiori della query.

Il risultato è che non importa più se si utilizza 'SELECT * ..' nei livelli inferiore / interno di una query. Invece, ciò che conta davvero è ciò che è nell'elenco delle colonne del SELECT di primo livello. A meno che non lo utilizzi SELECT *..nella parte superiore, quindi, ancora una volta, devi presumere che desideri TUTTE le colonne e quindi non puoi utilizzare le ottimizzazioni delle colonne in modo efficace.

(* - si noti che esiste un problema di rilegatura minore diverso in Views con il *quale non registrano sempre la modifica negli elenchi di colonne quando si utilizza "*". Esistono altri modi per risolvere questo problema e ciò non influisce sulle prestazioni.)


5

C'è un motivo in più per non usarlo SELECT *: se l'ordine delle colonne restituite cambia, la tua applicazione si interromperà ... se sei fortunato. Se non lo sei, avrai un bug sottile che potrebbe non essere rilevato per molto tempo. L'ordine dei campi in una tabella è un dettaglio dell'implementazione che non dovrebbe mai essere preso in considerazione dalle applicazioni, poiché l'unica volta che è persino visibile è se si utilizza a SELECT *.


4
Questo è irrilevante. Se accedi alle colonne in base all'indice di colonna nel codice dell'applicazione, ti meriti di avere un'applicazione non funzionante. L'accesso alle colonne per nome produce sempre un codice dell'applicazione molto più leggibile e non è quasi mai il collo di bottiglia delle prestazioni.
Lie Ryan,

3

È consentito dal punto di vista fisico e problematico select * from table, tuttavia è una cattiva idea. Perché?

Prima di tutto, scoprirai che stai restituendo colonne che non ti servono (risorse pesanti).

In secondo luogo, ci vorrà più tempo su una tabella di grandi dimensioni rispetto alla denominazione delle colonne perché quando selezioni *, stai effettivamente selezionando i nomi delle colonne dal database e dicendo "dammi i dati associati alle colonne che hanno nomi in questo altro elenco ". Mentre questo è veloce per il programmatore, immagina di farlo cercando sul computer di una banca che potrebbe avere letteralmente centinaia di migliaia di ricerche in un minuto.

In terzo luogo, farlo in realtà rende più difficile per lo sviluppatore. Con quale frequenza devi passare da SSMS a VS per ottenere tutti i nomi di una colonna?

In quarto luogo, è un segno di programmazione pigra e non credo che nessuno sviluppatore vorrebbe quella reputazione.


Il tuo secondo argomento in questa forma attuale presenta alcuni piccoli errori. Innanzitutto, tutto RDBMS memorizza nella cache lo schema delle tabelle, principalmente perché lo schema verrà comunque caricato nella fase di analisi della query per determinare quale colonna esiste o manca nella tabella dalla query. Pertanto, il parser di query ha già interrogato l'elenco dei nomi delle colonne da solo e immediatamente sostituisce * con un elenco delle colonne. Quindi, la maggior parte dei motori RDBMS tenta di memorizzare nella cache tutto ciò che può, quindi se si emette la tabella SELECT * FROM, la query compilata verrà memorizzata nella cache in modo che l'analisi non avvenga ogni volta. E gli sviluppatori sono pigri :-)
Gabor Garami,

Per quanto riguarda il secondo argomento, si tratta di un malinteso comune: il problema con SELECT * non è la ricerca dei metadati, poiché se si nominano le colonne, SQL Server deve ancora convalidare i loro nomi, controllare i tipi di dati, ecc.
Aaron Bertrand

@Gabor Uno dei problemi con SELECT * si verifica quando lo metti in vista. Se si modifica lo schema sottostante, la vista può diventare confusa: ora ha un concetto diverso dello schema della tabella (il suo) rispetto alla tabella stessa. Ne parlo qui .
Aaron Bertrand

3

Può essere un problema se si inserisce il Select * ...codice in un programma, perché, come sottolineato in precedenza, il database potrebbe cambiare nel tempo e avere più colonne rispetto a quanto previsto quando si è scritta la query. Questo può portare al fallimento del programma (nel migliore dei casi) o il programma potrebbe andare per il verso giusto e corrompere alcuni dati perché sta esaminando i valori di campo che non è stato scritto per gestire. In breve, il codice di produzione dovrebbe SEMPRE specificare i campi da restituire in SELECT.

Detto questo, ho meno problemi quando Select *fa parte di una EXISTSclausola, poiché tutto ciò che verrà restituito al programma è un valore booleano che indica il successo o il fallimento della selezione. Altri potrebbero non essere d'accordo con questa posizione e io rispetto la loro opinione al riguardo. Potrebbe essere leggermente meno efficiente codificare Select *rispetto al codice "Seleziona 1" in una EXISTSclausola, ma non credo che ci sia alcun pericolo di corruzione dei dati, in entrambi i casi.


In realtà, sì, avevo intenzione di fare riferimento alla clausola EXISTS. Errore mio.
Mark Ross,

2

Molte risposte perché select *è sbagliato, quindi mi occuperò quando ritengo che sia giusto o almeno OK.

1) In un EXISTS, il contenuto della parte SELECT della query viene ignorato, quindi puoi persino scrivere SELECT 1/0e non si verificherà alcun errore. EXISTSverifica solo che alcuni dati tornerebbero e restituisce un valore booleano basato su quello.

IF EXISTS(
    SELECT * FROM Table WHERE X=@Y
)

2) Questo potrebbe far scoppiare una tempesta di fuoco, ma mi piace usare select *i trigger della tabella della cronologia. Inoltre select *, impedisce alla tabella principale di ottenere una nuova colonna senza aggiungere la colonna alla tabella della cronologia, inoltre si verifica un errore immediatamente quando viene inserita / aggiornata / eliminata nella tabella principale. Ciò ha impedito numerose volte in cui gli sviluppatori aggiungevano colonne e si sono dimenticati di aggiungerlo alla tabella della cronologia.


3
Preferisco ancora SELECT 1perché, ovviamente, avvisa i futuri manutentori del codice delle tue intenzioni. Non è un requisito , ma se lo vedo ... WHERE EXISTS (SELECT 1 ...)abbastanza chiaramente si annuncia come un test di verità.
cambio

1
@zlatan Molte persone usano SELECT 1basandosi su un mito secondo cui le prestazioni sarebbero migliori di SELECT *. Tuttavia, entrambe le opzioni sono perfettamente accettabili. Non ci sono differenze nelle prestazioni a causa del modo in cui l'ottimizzatore gestisce gli ESISTI. Né alcuna differenza nella leggibilità a causa della parola "ESISTI" che annuncia chiaramente un test di verità.
Disilluso,

Al punto 2, capisco il tuo ragionamento, ma ci sono ancora dei rischi. Lasciami "dipingere uno scenario per te" ... Lo sviluppatore aggiunge Column8alla tabella principale dimenticando la tabella della cronologia. Lo sviluppatore scrive un gruppo di codice creato nella colonna 8. Quindi aggiunge Column9alla tabella principale; questa volta ricordando di aggiungere anche alla storia. Più tardi, durante i test, si rende conto di aver dimenticato di aggiungere Column9alla cronologia (grazie alla tua tecnica di rilevamento degli errori) e di prontamente lo aggiunge. Ora il trigger sembra funzionare, ma i dati nelle colonne 8 e 9 sono confusi nella cronologia. : S
Disilluso,

cont ... Il punto è che lo scenario "inventato" sopra è solo uno dei tanti che potrebbero far fallire il tuo trucco per il rilevamento degli errori e peggiorare le cose. Fondamentalmente hai bisogno di una tecnica migliore. Uno che non si basa sul trigger che fa ipotesi sull'ordine delle colonne in una tabella da cui si seleziona. Suggerimenti: - Recensioni di codici personali con liste di controllo dei tuoi errori comuni. - Revisioni del codice peer. - Tecnica alternativa per il monitoraggio della cronologia (personalmente considero i meccanismi basati su trigger come reattivi anziché proattivi e quindi soggetti a errori).
Disilluso,

@CraigYoung Questa è una possibilità. Ma strozzerei qualcuno se lo facessero. Questo non è un errore che potresti facilmente commettere
UnhandledExcepSean
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.