web-dev-qa-db-fra.com

Pourquoi "sélectionner * dans le tableau" est-il considéré comme une mauvaise pratique

Hier, je discutais avec un programmeur "amateur" (je suis moi-même un programmeur professionnel). Nous sommes tombés sur une partie de son travail, et il a dit qu'il interroge toujours toutes les colonnes de sa base de données (même sur/dans le serveur de production/code).

J'ai essayé de le convaincre de ne pas le faire, mais je n'ai pas encore réussi. À mon avis, un programmeur ne devrait interroger que ce qui est réellement nécessaire pour des raisons de "joliesse", d'efficacité et de trafic. Suis-je dans l'erreur avec ma vue?

98
the baconing

Pensez à ce que vous récupérez et à la façon de les lier aux variables de votre code.

Réfléchissez maintenant à ce qui se passe lorsque quelqu'un met à jour le schéma de table pour ajouter (ou supprimer) une colonne, même celle que vous n'utilisez pas directement.

L'utilisation de select * lorsque vous tapez des requêtes à la main est très bien, pas lorsque vous écrivez des requêtes de code.

67
gbjbaanb

Modifications de schéma

  • Récupérer par ordre --- Si le code récupère la colonne # comme moyen d'obtenir les données, une modification du schéma entraînera le réajustement des numéros de colonne. Cela gâchera l'application et de mauvaises choses se produiront.
  • Récupérer par nom --- Si le code récupère la colonne par nom tel que foo et qu'une autre table de la requête ajoute une colonne foo, la façon dont cela est géré peut provoquer des problèmes lors de la tentative de récupérez la colonne à droitefoo.

Dans tous les cas, un changement de schéma peut entraîner des problèmes d'extraction des données.

Vérifiez également si une colonne qui était utilisée est supprimée du tableau. Le select * from ... Fonctionne toujours mais des erreurs se produisent lors de la tentative d'extraire les données du jeu de résultats. Si la colonne est spécifiée dans la requête, la requête produira une erreur à la place, donnant une indication claire quant à quoi et où est le problème.

Frais généraux des données

Certaines colonnes peuvent être associées à une quantité importante de données. Si vous sélectionnez *, Vous tirerez tous les données. Oui, voici que varchar(4096) c'est sur 1000 lignes que vous avez sélectionnées en arrière vous donnant un supplément possible de 4 mégaoctets de données dont vous n'avez pas besoin, mais qui sont quand même envoyées sur le câble.

En relation avec le changement de schéma, ce varchar pourrait ne pas exister là lorsque vous avez créé la table pour la première fois, mais maintenant il est là.

Omission de transmettre l'intention

Lorsque vous sélectionnez en arrière * Et obtenez 20 colonnes mais n'en avez besoin que de 2, vous ne transmettez pas l'intention du code. Quand on regarde la requête qui fait un select * On ne sait pas quelles sont les parties importantes. Puis-je modifier la requête pour utiliser cet autre plan à la place pour l'accélérer en n'incluant pas ces colonnes? Je ne sais pas, car l'intention de ce que renvoie la requête n'est pas claire.


Regardons quelques violons SQL qui explorent un peu plus ces changements de schéma .

Tout d'abord, la base de données initiale: 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);

Et les colonnes que vous récupérez sont oneid=1, data=42, twoid=2 Et other=43.

Maintenant, que se passe-t-il si j'ajoute une colonne au tableau 1? http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

Et mes résultats de la même requête que précédemment sont oneid=1, data=42, twoid=2 Et other=foo.

Un changement dans l'une des tables perturbe les valeurs d'un select * Et soudainement votre liaison de 'autre' à un int va générer une erreur et vous ne savez pas pourquoi.

Si, à la place, votre instruction SQL était

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

La modification du tableau 1 n'aurait pas perturbé vos données. Cette requête s'exécute de la même manière avant la modification et après la modification.


Indexage

Lorsque vous faites un select * from Vous tirez tous les lignes forment tous les tableaux qui correspondent aux conditions. Même les tables dont vous ne vous souciez vraiment pas. Bien que cela signifie que davantage de données sont transférées, un autre problème de performances se cache plus loin dans la pile.

Index. (lié à SO: Comment utiliser l'index dans l'instruction select? )

Si vous retirez un grand nombre de colonnes, l'optimiseur de plan de base de données - mai ne tenez pas compte de l'utilisation d'un index, car vous aurez toujours besoin de récupérer toutes ces colonnes de toute façon et il faudrait plus de temps pour utiliser l'index et puis récupérez toutes les colonnes de la requête que ce ne serait que pour effectuer une analyse complète de la table.

Si vous ne faites que sélectionner, disons, le nom de famille d'un utilisateur (ce que vous faites beaucoup et avez donc un index dessus), la base de données peut faire un scan d'index uniquement ( scan d'index wiki postgres uniquement , analyse complète de la table mysql vs analyse complète de l'index , analyse indexée uniquement: éviter l'accès à la table ).

Si possible, il y a pas mal d'optimisations concernant la lecture uniquement à partir des index. Les informations peuvent être extraites plus rapidement sur chaque page d'index, car vous en tirez également moins - vous n'introduisez pas toutes ces autres colonnes pour le select *. Il est possible qu'une analyse d'index uniquement renvoie des résultats de l'ordre de 100x plus vite (source: Sélectionnez * est mauvais ).

Cela ne veut pas dire qu'une analyse d'index complète est excellente, c'est toujours une analyse complète - mais c'est mieux qu'une analyse de table complète. Une fois que vous commencez à pourchasser toutes les façons dont select * Nuit aux performances, vous en trouvez de nouvelles.

Lecture connexe

179
user40980

Autre préoccupation: si c'est une requête JOIN et que vous récupérez les résultats de la requête dans un tableau associatif (comme cela pourrait être le cas en PHP), elle est sujette aux bogues.

Le truc c'est que

  1. si la table foo a des colonnes id et name
  2. si la table bar a des colonnes id et address,
  3. et dans votre code, vous utilisez SELECT * FROM foo JOIN bar ON foo.id = bar.id

devinez ce qui se passe lorsque quelqu'un ajoute une colonne name à la table bar.

Le code cessera soudainement de fonctionner correctement, car maintenant la colonne name apparaît dans les résultats deux fois et si vous stockez les résultats dans un tableau, les données de la seconde name (bar.name) remplacera le premier name (foo.name)!

C'est un bug assez désagréable car il n'est pas évident. Cela peut prendre un certain temps à comprendre, et il est impossible que la personne qui ajoute une autre colonne à la table ait anticipé un tel effet secondaire indésirable.

(Histoire vraie).

Donc, n'utilisez pas *, contrôlez les colonnes que vous récupérez et utilisez des alias le cas échéant.

38
Konrad Morawski

L'interrogation de chaque colonne peut être parfaitement légitime, dans de nombreux cas.

Toujours l'interrogation de chaque colonne ne l'est pas.

C'est plus de travail pour votre moteur de base de données, qui doit s'éteindre et fouiller autour de ses métadonnées internes pour déterminer quelles colonnes il doit traiter avant de pouvoir continuer à réellement obtenir les données et les renvoyer. D'accord, ce n'est pas le plus gros surcoût au monde, mais les catalogues système peuvent être un goulot d'étranglement appréciable.

C'est plus de travail pour votre réseau, car vous retirez n'importe quel nombre de champs alors que vous n'en voudrez qu'un ou deux. Si quelqu'un [d'autre] va et ajoute quelques dizaines de champs supplémentaires, qui contiennent tous de gros morceaux de texte, votre débit passe soudainement par le plancher - sans raison apparente. Cela est aggravé si votre clause "where" n'est pas particulièrement bonne et que vous retirez également de nombreuses lignes - cela peut potentiellement entraîner de nombreuses données qui traversent le réseau pour vous (c'est-à-dire que cela va être lent).

C'est plus de travail pour votre application, avoir à retirer et à stocker toutes ces données supplémentaires dont il ne se soucie probablement pas.

Vous courez le risque que les colonnes changent leur ordre. OK, vous ne devriez pas avoir à vous en préoccuper (et vous ne le ferez pas si vous ne sélectionnez que les colonnes dont vous avez besoin) mais, si vous allez les chercher toutes en même temps et que quelqu'un [autre] décide de réorganiser l'ordre des colonnes dans le tableau, cette exportation CSV soigneusement conçue que vous donnez aux comptes dans le couloir va soudainement au pot - encore une fois, sans raison apparente.

BTW, j'ai dit "quelqu'un [d'autre]" plusieurs fois, ci-dessus. N'oubliez pas que les bases de données sont intrinsèquement multi-utilisateurs; vous n'avez peut-être pas le contrôle sur eux que vous pensez avoir.

22
Phill W.

La réponse courte est: cela dépend de la base de données qu'ils utilisent. Les bases de données relationnelles sont optimisées pour extraire les données dont vous avez besoin d'une manière rapide, fiable et atomique. Sur les grands ensembles de données et les requêtes complexes, c'est beaucoup plus rapide et probablement plus sûr que SELECTing * et faites l'équivalent des jointures du côté 'code'. Les magasins de valeurs-clés peuvent ne pas avoir de telles fonctionnalités implémentées ou ne pas être suffisamment matures pour être utilisées en production.

Cela dit, vous pouvez toujours remplir la structure de données que vous utilisez avec SELECT * et résoudre le reste dans le code, mais vous trouverez des goulots d'étranglement des performances si vous souhaitez évoluer.

La comparaison la plus proche est le tri des données: vous pouvez utiliser le tri rapide ou le tri à bulles et le résultat sera correct. Mais ne sera pas optimisé et rencontrera certainement des problèmes lorsque vous introduisez la concurrence et que vous devez trier atomiquement.

Bien sûr, il est moins coûteux d'ajouter RAM et CPU que d'investir dans un programmeur capable de faire des requêtes SQL et a même une vague compréhension de ce qu'est un JOIN.

11
lorenzog

L'OMI, c'est d'être explicite vs implicite. Quand j'écris du code, je veux qu'il fonctionne parce que je l'ai fait fonctionner, pas seulement parce que toutes les parties se trouvent juste là. Si vous interrogez tous les enregistrements et que votre code fonctionne, vous aurez alors tendance à passer à autre chose. Plus tard, si quelque chose change et que votre code ne fonctionne plus, c'est une peine royale de déboguer de nombreuses requêtes et fonctions à la recherche d'une valeur qui devrait être là et les seules valeurs de référence sont *.

Toujours dans une approche à plusieurs niveaux, il est préférable d'isoler les perturbations du schéma de base de données au niveau des données. Si votre niveau de données passe * à la logique métier et très probablement au niveau de présentation, vous étendez de manière exponentielle votre portée de débogage.

8
zkent

parce que si la table obtient de nouvelles colonnes, vous obtenez toutes celles-ci même lorsque vous n'en avez pas besoin. avec varchars cela peut devenir beaucoup de données supplémentaires qui doivent voyager de la base de données

certaines optimisations de base de données peuvent également extraire les enregistrements de longueur non fixe dans un fichier séparé pour accélérer l'accès aux parties de longueur fixe, en utilisant select * défait le but de cette

6
ratchet freak

Mis à part les frais généraux, ce que vous voulez éviter en premier lieu, je dirais qu'en tant que programmeur, vous ne dépendez pas de l'ordre des colonnes défini par l'administrateur de la base de données. Vous sélectionnez chaque colonne même si vous en avez besoin.

1
dj bazzie wazzie

Je ne vois aucune raison pour laquelle vous ne devriez pas utiliser pour le but de sa construction - récupérer toutes les colonnes d'une base de données. Je vois trois cas:

  1. Une colonne est ajoutée dans la base de données et vous la souhaitez également dans le code. a) Avec * échouera avec un message correct. b) Sans * fonctionnera, mais ne fera pas ce que vous attendez, ce qui est plutôt mauvais.

  2. Une colonne est ajoutée dans la base de données et vous ne voulez pas qu'elle soit dans le code. a) avec * échouera; cela signifie que * ne s'applique plus puisque sa sémantique signifie "récupérer tout". b) Sans * fonctionnera.

  3. Une colonne est supprimée. Le code échouera dans les deux cas.

Maintenant, le cas le plus courant est le cas 1 (puisque vous avez utilisé * ce qui signifie tout ce que vous voulez probablement tout); sans * vous pouvez avoir un code qui fonctionne bien mais ne fait pas ce qui est attendu, ce qui est bien pire que ce code qui échoue avec un message d'erreur approprié.

Je ne prends pas en considération le code qui récupère les données de colonne en fonction de l'index de colonne qui est à mon avis sujet aux erreurs. Il est beaucoup plus logique de le récupérer en fonction du nom de la colonne.

1
m3th0dman

Pensez-y de cette façon ... si vous interrogez toutes les colonnes d'une table qui n'a que quelques petites chaînes ou champs numériques, cela fait 100k de données. Mauvaise pratique, mais ça va marcher. Ajoutez maintenant un seul champ qui contient, disons, une image ou un document Word de 10 Mo. maintenant, votre requête à exécution rapide commence immédiatement et mystérieusement à mal fonctionner, simplement parce qu'un champ a été ajouté à la table ... vous n'avez peut-être pas besoin de cet énorme élément de données, mais parce que vous avez fait Select * from Table vous l'obtenez de toute façon.

1
kevin mitchell