Est-ce que select * est toujours un gros no-no sur SQL Server 2012?

41

À l'époque d'antan, c'était considéré comme un non-non à faire select * from tableou à select count(*) from tablecause de la performance.

Est-ce toujours le cas dans les versions ultérieures de SQL Server (j'utilise 2012, mais je suppose que la question s'appliquerait à 2008 - 2014)?

Edit: Puisque les gens semblent me parler légèrement ici, je regarde cela d’un point de vue académique / de référence, et non pas si c’est la bonne chose à faire (ce qui n’est bien sûr pas le cas)

Piers Karsenbarg
la source

Réponses:

50

Si vous SELECT COUNT(*) FROM TABLEne retournez qu'une seule ligne (le nombre), il est relativement léger et constitue le moyen d'obtenir cette donnée.

Et ce SELECT *n’est pas un non-non physique, en ce sens qu’il est légal et autorisé.

Cependant, le problème SELECT *est que vous pouvez causer beaucoup plus de mouvement de données. Vous opérez sur chaque colonne de la table. Si vous SELECTne comprenez que quelques colonnes, vous pourrez peut-être obtenir votre réponse à partir d'un ou plusieurs index, ce qui réduit les E / S et l'impact sur le cache du serveur.

Donc, oui, il est recommandé de ne pas en faire une pratique générale, car cela gaspille vos ressources.

Le seul avantage réel de SELECT *ne pas taper tous les noms de colonnes. Mais depuis SSMS, vous pouvez utiliser le glisser-déposer pour obtenir les noms de colonne dans votre requête et supprimer ceux dont vous n’avez pas besoin.

Une analogie: Si les utilisations quelqu'un SELECT *quand ils ne ont pas besoin chaque colonne, seraient - ils aussi utiliser SELECTsans WHERE(ou une autre clause limitative) quand ils ne ont pas besoin chaque ligne?

RLF
la source
24

En plus de la réponse déjà fournie par le fournisseur, j'estime qu'il convient de souligner que les développeurs sont souvent trop paresseux lorsqu'ils travaillent avec des ORM modernes tels que Entity Framework. Alors que les administrateurs de bases de données font de leur mieux pour éviter SELECT *, les développeurs écrivent souvent l'équivalent sémantique, par exemple, en c # Linq:

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

Cela résulterait essentiellement en ce qui suit:

SELECT * FROM MyTable WHERE FirstName = 'User'

Il existe également des frais généraux supplémentaires qui n'ont pas encore été couverts. Il s’agit des ressources nécessaires pour traiter chaque colonne de chaque ligne dans l’objet correspondant. De plus, pour chaque objet gardé en mémoire, cet objet doit être nettoyé. Si vous ne sélectionnez que les colonnes dont vous avez besoin, vous pouvez facilement économiser plus de 100 Mo de bélier. Bien que ce ne soit pas une somme énorme en soi, c’est l’effet cumulatif de la récupération de place, etc.

Donc oui, pour moi au moins, c'est et sera toujours un grand non. Nous devons également éduquer sur les coûts "cachés" de faire cela plus aussi.

Addenda

Voici un exemple de extraction des données dont vous avez besoin, comme demandé dans les commentaires:

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

Performance: une requête avec SELECT * ne sera probablement jamais une requête couvrant ( explication de conversation simple , explication de dépassement de capacité de pile ).

Mise à l'épreuve du temps: votre requête peut renvoyer les sept colonnes aujourd'hui, mais si quelqu'un ajoute cinq colonnes au cours de la prochaine année, votre requête renvoie douze colonnes au cours d'une année, gaspillant ainsi IO et CPU.

Indexation: si vous souhaitez que vos vues et fonctions à valeur de table participent à l'indexation dans SQL Server, ces vues et fonctions doivent être créées à l'aide de la liaison de schéma, qui interdit l'utilisation de SELECT *.

Meilleure pratique : ne jamais utiliser SELECT *dans le code de production.

Pour les sous-requêtes, je préfère WHERE EXISTS ( SELECT 1 FROM … ).

Edit : Pour répondre au commentaire de Craig Young ci-dessous, l'utilisation de "SELECT 1" dans une sous-requête n'est pas une "'optimisation" - c'est pour que je puisse me lever devant ma classe et dire "n'utilisez pas SELECT *, sans exception! "

La seule exception à laquelle je peux penser est celle où le client effectue une sorte d'opération de tableau croisé dynamique et requiert toutes les colonnes présentes et futures.

J'accepte peut-être une exception impliquant des CTE et des tables dérivées, mais j'aimerais voir des plans d'exécution.

Notez que je considère COUNT(*)une exception à cela car il s’agit d’une utilisation syntaxique différente de "*".

Greenstone Walker
la source
10

Dans SQL Server 2012 (ou toute version à partir de 2005), l'utilisation SELECT *...n'est qu'un problème de performances possible dans l'instruction SELECT de niveau supérieur d'une requête.

Donc, ce n'est PAS un problème dans Views (*), dans les sous-requêtes, dans les clauses EXIST, dans les CTE, ni dans SELECT COUNT(*)..etc., etc. Notez que cela est probablement également vrai pour Oracle, DB2 et peut - être PostGres (pas sûr) , mais il est très probable que cela reste un problème dans beaucoup de cas pour MySql.

Pour comprendre pourquoi (et pourquoi cela peut toujours être un problème dans un SELECT de niveau supérieur), il est utile de comprendre pourquoi il en a déjà été un, car son utilisation SELECT *..signifie " renvoyer TOUTES les colonnes ". En général, cela renvoie beaucoup plus de données que vous ne le souhaitez vraiment, ce qui peut évidemment entraîner beaucoup plus d'E / S, à la fois sur disque et sur le réseau.

Ce qui est moins évident, c'est que cela limite également les index et les plans de requête qu'un optimiseur SQL peut utiliser, car il sait qu'il doit finalement renvoyer toutes les colonnes de données. S'il peut savoir à l'avance que vous ne voulez que certaines colonnes, il peut souvent utiliser des plans de requête plus efficaces en exploitant les index contenant uniquement ces colonnes. Heureusement, il existe un moyen de le savoir à l'avance, c'est-à-dire de spécifier explicitement les colonnes souhaitées dans la liste. Mais lorsque vous utilisez "*", vous renoncez à "tout donner, je vais trouver ce dont j'ai besoin".

Oui, chaque colonne nécessite également une utilisation accrue de la CPU et de la mémoire, mais elle est presque toujours mineure comparée à ces deux choses: la bande passante supplémentaire importante nécessaire pour le disque et la bande passante réseau requise pour les colonnes inutiles plan de requête optimisé car il doit inclure chaque colonne.

Alors qu'est-ce qui a changé? Fondamentalement, les optimiseurs SQL ont intégré avec succès une fonctionnalité appelée "Optimisation de colonne", ce qui signifie simplement qu'ils peuvent désormais déterminer dans les sous-requêtes de niveau inférieur si vous envisagez d'utiliser une colonne dans les niveaux supérieurs de la requête.

Le résultat de ceci est que cela n'a plus d'importance si vous utilisez 'SELECT * ..' dans les niveaux inférieurs / internes d'une requête. Au lieu de cela, ce qui compte vraiment, c’est ce qui est dans la liste des colonnes du SELECT de niveau supérieur. À moins que vous n'utilisiez SELECT *..dans le haut, alors encore une fois, il faut supposer que vous voulez TOUTES les colonnes et que vous ne pouvez donc pas utiliser efficacement les optimisations de colonne.

(* - Notez qu'il existe un problème de liaison mineur et différent dans les vues, *où elles n'enregistrent pas toujours les modifications dans les listes de colonnes lorsque "*" est utilisé. Il existe d'autres moyens de résoudre ce problème sans que cela affecte les performances.)

RBarryYoung
la source
5

Il y a une autre petite raison à ne pas utiliser SELECT *: si l'ordre des colonnes renvoyé change, votre application sera interrompue ... si vous avez de la chance. Si vous ne l'êtes pas, vous aurez un bug subtil qui pourrait rester indétecté pendant longtemps. L'ordre des champs dans une table est un détail d'implémentation qui ne devrait jamais être considéré par les applications, car le seul moment où il est visible est si vous utilisez un SELECT *.

Jon de tous les métiers
la source
4
Ceci n'est pas pertinent. Si vous accédez aux colonnes par index de colonne dans votre code d'application, vous méritez d'avoir une application endommagée. L'accès aux colonnes par nom produit toujours un code d'application bien plus lisible et ne constitue presque jamais le goulot d'étranglement des performances.
Lie Ryan
3

Il est autorisé physiquement et problématique à utiliser select * from table, cependant, c'est une mauvaise idée. Pourquoi?

Tout d'abord, vous constaterez que vous renvoyez des colonnes dont vous n'avez pas besoin (ressources lourdes).

Deuxièmement, une table volumineuse prendra plus de temps que de nommer les colonnes car lorsque vous sélectionnez *, vous sélectionnez en fait les noms de colonne dans la base de données et vous dites "donnez-moi les données associées aux colonnes dont le nom figure dans cette autre liste. . " Bien que cela soit rapide pour le programmeur, imaginez que vous fassiez cette recherche sur l'ordinateur d'une banque qui pourrait avoir littéralement des centaines de milliers de recherches en une minute.

Troisièmement, cela rend en réalité la tâche plus difficile pour le développeur. À quelle fréquence devez-vous basculer de SSMS à VS pour obtenir tous les noms de colonnes?

Quatrièmement, c'est un signe de programmation paresseuse et je ne pense pas qu'un développeur veuille cette réputation.

CharlieHorse
la source
Votre deuxième argument sous cette forme actuelle comporte quelques petites erreurs. Tout d'abord, tous les SGBDR mettent en cache le schéma des tables, principalement parce que le schéma sera chargé de toute façon à l'étape d'analyse de la requête pour déterminer la colonne existante ou manquante dans la table à partir de la requête. Ainsi, l'analyseur de requête a déjà interrogé lui-même la liste de noms de colonnes et a immédiatement remplacé * par une liste de colonnes. Ensuite, la plupart des moteurs de SGBDR tentent de mettre en cache tout ce qu'il peut. Par conséquent, si vous émettez une table SELECT * FROM, la requête compilée sera mise en cache afin que l'analyse ne se produise pas à chaque fois. Et les développeurs sont paresseux :-)
Gabor Garami
En ce qui concerne votre deuxième argument, il s'agit d'une idée fausse commune - le problème avec SELECT * n'est pas la recherche de métadonnées, car si vous nommez les colonnes, SQL Server doit toujours valider leurs noms, vérifier les types de données, etc.
Aaron Bertrand
@Gabor L'un des problèmes avec SELECT * se produit lorsque vous mettez cela dans une vue. Si vous modifiez le schéma sous-jacent, la vue peut devenir confuse - le concept du schéma de la table (le sien) est différent de celui de la table elle-même. Je parle de cela ici .
Aaron Bertrand
3

Cela peut poser un problème si vous insérez le Select * ...code dans un programme, car, comme indiqué précédemment, la base de données peut changer au fil du temps et comporter plus de colonnes que ce à quoi vous vous attendiez lorsque vous avez écrit la requête. Cela peut conduire à un échec du programme (dans le meilleur des cas) ou bien le programme risque de mal tourner et de corrompre certaines données car il examine des valeurs de champ pour lesquelles il n'a pas été écrit. En bref, le code de production doit TOUJOURS spécifier les champs à retourner dans le fichier SELECT.

Cela dit, j'ai moins de problèmes lorsque cela Select *fait partie d'un EXISTSarticle, car tout ce qui va être renvoyé au programme est un booléen indiquant le succès ou l'échec du choix. D'autres peuvent être en désaccord avec cette position et je respecte leur opinion à ce sujet. Il PEUT être légèrement moins efficace de coder Select *que de coder "Select 1" dans une EXISTSclause, mais je ne pense pas qu'il y ait de risque de corruption des données, de toute façon.

Mark Ross
la source
En fait, oui, j'avais l'intention de faire référence à la clause EXISTS. Mon erreur.
Mark Ross
2

Il y a beaucoup de réponses à cela select *, alors je couvrirai quand j'estime que c'est bien ou du moins que c'est OK.

1) Dans un EXISTS, le contenu de la partie SELECT de la requête est ignoré. Vous pouvez donc écrire SELECT 1/0et éviter les erreurs. EXISTSvérifie simplement que certaines données renverraient et renvoie un booléen basé sur cela.

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

2) Cela pourrait déclencher une tempête de feu, mais j'aime utiliser les select *déclencheurs de ma table d'historique. Par select *, il empêche la table principale d’obtenir une nouvelle colonne sans l’ajouter à la table historique, car elle est immédiatement erronée lorsqu’elle est insérée / mise à jour / supprimée dans la table principale. Cela a empêché de nombreuses fois les développeurs d’ajouter des colonnes et d’oublier de l’ajouter à la table de l’historique.

UnhandledExcepSean
la source
3
Je préfère quand même SELECT 1parce que cela indique très clairement aux futurs responsables de la maintenance du code votre intention. Ce n'est pas une obligation , mais si je le vois, ... WHERE EXISTS (SELECT 1 ...)cela s'annonce évidemment comme un test de vérité.
Swasheck
1
@zlatanMany utilise SELECT 1un mythe selon lequel les performances seraient meilleures que SELECT *. Cependant, les deux options sont parfaitement acceptables. Il n’ya pas de différence de performances en raison de la manière dont optimser traite les EXISTS. Ni aucune différence de lisibilité à cause du mot "EXISTS" qui annonce clairement un test de vérité.
Desillusioned
Au point 2, je comprends votre raisonnement, mais il y a toujours des risques. Laissez-moi 'peindre un scénario pour vous' ... Developer ajoute Column8à la table principale en oubliant la table d'historique. Developer écrit un tas de code transmis à la colonne 8. Ensuite, il ajoute des éléments Column9à la table principale. cette fois en rappelant d'ajouter également à l'histoire. Plus tard, lors des tests, il réalise qu’il a oublié d’ajouter Column9à l’historique (grâce à votre technique de détection des erreurs) et l’ajoute rapidement. Maintenant, le déclencheur semble fonctionner, mais les données des colonnes 8 et 9 sont mélangées dans l'historique. : S
Désillusionné le
suite ... Le fait est que le scénario "concocté" ci-dessus n'est qu'un des nombreux scénarios susceptibles d'entraîner une défaillance de votre astuce de détection d'erreur, qui aggraverait la situation. Fondamentalement, vous avez besoin d'une meilleure technique. Une solution qui ne repose pas sur le fait que votre déclencheur émet des hypothèses sur l’ordre des colonnes dans une table que vous sélectionnez. Suggestions: - Révisions de codes personnels avec des listes de contrôle de vos erreurs courantes. - Revues de code par les pairs. - Autre technique de suivi de l'historique (personnellement, j'estime que les mécanismes à base de déclencheurs sont réactifs plutôt que proactifs et donc sujets aux erreurs).
Desillusioned
@CraigYoung C'est une possibilité. Mais j'étoufferais quelqu'un s'ils le faisaient. Ce n'est pas une erreur que vous pourriez facilement commettre
UnhandledExcepSean