Différence entre ...

Discussion dans 'Web, design' créé par SkYlEsS, 24 Avril 2007.

Statut de la discussion:
Fermée.
  1. Offline
    La Poubelle Elite
    Un select count(ID) est tout aussi rapide qu'un select count(*).

    Pour la généralisation à un simple SELECT * FROM TABLE, c'était suite à :

    La question initale qui demandait COUNT(*) vs SELECT * FROM TABLE avecc un traitement dans PHP.

    Et surtout:

    Tifox l'hérétique. ;)


    Je l'avoue, le post a dévié sur l'utilisation de l'instruction *, autant faire une petite discution intéressante la dessus :D
    La Poubelle, 27 Avril 2007
    #21
  2. Offline
    Tifox ou pas
    Voila, je viens d'effectuer plein de petits test, sur des DB mySQL, SQL Server et DB2. La table contenait 80 champs de tout types, dont certain a null aléatoirement et un id int. J'ai charger 5 millions d'entrée pour un total de plus de 15Go de données.

    Dans tout les cas, le count(*) a été plus rapide (un poil ou beaucoup) que count(id) ou count (nom_d'un_champs).

    Le select maintenant, la c'est plus complexe. Un select(*) ne lui même est plus rapide qu'un select(champs1, champs 2, ...) d'après ce que j'ai vu sur DB2, mais je n'ai pas su évaluer la chose sur les serveurs SQL car je n'ai pas trouvé comment faire un select sans afficher les résultats (ce qui n'a d'ailleurs aucun intérêt). Vu que ces résultats contiennent parfois des Go de données, c'est forcément ça qui ralentit tout. Donc pour le select, si l'opération en elle même semble plus rapide avec un select(*), vu qu'il faut de toute façon rapatrier les donnée quelques part, il vaudra toujours mieux ne rapatrier que les champs dont a réellement besoin.
    Tifox, 27 Avril 2007
    #22
  3. Offline
    SkYlEsS Kawai
    Et entre count(*) et mysql_num_rows ? :-D
    SkYlEsS, 27 Avril 2007
    #23
  4. Offline
    Bingo Beer Addict
    Je pense que sur certains SGBD (PotgreSQL par exemple, je ne peux pas généraliser à d'autres que je connais moins), c'est seulement les accès disque qui vont être plus intenses au moment de l'envoi du résultat de la requête.
    PostgreSQL récupère d'abord une liste des identifiants des enregistrements à renvoyer. (ces identifiants sont des valeurs internes au serveur, ce ne sont pas les clés primaires)
    Il trie cette liste pour la classer soit dans l'ordre du ORDER BY si il existe, soit dans l'ordre physique des enregistrements sur le disque si il n'y a pas de tri.
    Ensuite il "fetch" les enregistrements en parcourant la table dans l'ordre déterminé précédemment, et à chaque enregistrement il lit les champs demandé.

    Exemple sur une table d'environ 30 millions d'enregistrement avec une requête qui en renvoie environ 300.000 :

    Avec deux champs sélectionnés :

    EXPLAIN ANALYZE
    SELECT name, nw_geometry
    FROM nw
    WHERE frc=1;


    "Bitmap Heap Scan on nw (cost=5716.58..692820.62 rows=305210 width=171) (actual time=1491.495..665969.803 rows=314909 loops=1)"
    " Recheck Cond: (frc = 1)"
    " -> Bitmap Index Scan on nw_frc_btree (cost=0.00..5640.28 rows=305210 width=0) (actual time=1383.121..1383.121 rows=314909 loops=1)"
    " Index Cond: (frc = 1)"
    "Total runtime: 666065.283 ms"

    Avec tous les champs sélectionnés (en gros, le double en largeur d'enregistrement) :

    EXPLAIN ANALYZE
    SELECT *
    FROM nw
    WHERE frc=1;


    "Bitmap Heap Scan on nw ( cost=5716.58..692820.62 rows=305210width=354) (actual time=473.733..253735.608 rows=314909 loops=1)"
    " Recheck Cond: (frc = 1)"
    " -> Bitmap Index Scan on nw_frc_btree (cost=0.00..5640.28 rows=305210 width=0) (actual time=386.953..386.953 rows=314909 loops=1)"
    " Index Cond: (frc = 1)"
    "Total runtime: 253830.356 ms"


    Pour PostgreSQL, aucune différence au niveau du coût. Au niveau du serveur, j'ai constaté une légère augmentation des accès mémoire, mais pas d'augmentation de la mémoire utilisée.


    Toujours rien à voir avec le message originel, mais c'était pour faire avancer l'autre schmilblick ! ;)
    Bingo, 27 Avril 2007
    #24
  5. Offline
    Bingo Beer Addict
    COUNT(*) sera systématiquement plus rapide, parce qu'il n'a pas la même signification que Count(field).
    COUNT(*) a une signification spéciale : il renvoie la cardinalité de la table, c'est à dire le nombre de lignes.
    COUNT(field) renvoie le nombre d'enregistrements où field est non nul, ça revient à "COUNT(*) WHERE field IS NOT NULL".

    C'est plus long parce que pas mal de moteurs de bases de données (comme MySQL) cachent le nombre de lignes d'une table, ils n'ont donc pas besoin d'éxécuter la requête. Pour COUNT(id), si il ne sont pas transactionnels et qu'il y a un index sur id, ils se contentent de compter les enregistrements dans l'index, ça reste donc très rapide.
    Pas mal de SGBD transactionnels ont aussi des informations de transaction dans les index, du coup ils peuvent aussi optimiser les COUNT.

    PostgreSQL est un cas très spécial dans le monde des SGBD car il ne cache pas le nombre de lignes. C'est dû au fait qu'il soit fortement axé sur les transactions.
    Ses index ne contiennent pas d'information de transaction, donc PostgreSQL ne peut pas se baser sur eux pour compter ni le nombre de lignes, ni le nombre de lignes non nulles : il est obligé de parcourir toute la table !
    Conséquence directe : COUNT(*) est très lent dans PostgreSQL, et prend exactement le même temps que COUNT(field).

    EXPLAIN ANALYZE
    SELECT COUNT(*)
    FROM nw
    WHERE frc=1;


    "Aggregate (cost=693583.64..693583.65 rows=1 width=0) (actual time=335830.861..335830.861 rows=1 loops=1)"
    " -> Bitmap Heap Scan on nw (cost=5716.58..692820.62 rows=305210 width=0) (actual time=447.847..335715.959 rows=314909 loops=1)"
    " Recheck Cond: (frc = 1)"
    " -> Bitmap Index Scan on nw_frc_btree (cost=0.00..5640.28 rows=305210 width=0) (actual time=361.512..361.512 rows=314909 loops=1)"
    " Index Cond: (frc = 1)"
    "Total runtime: 335831.777 ms"



    EXPLAIN ANALYZE
    SELECT COUNT(gid)
    FROM nw
    WHERE frc=1;


    "Aggregate (cost=693583.64..693583.65 rows=1 width=4) (actual time=539638.289..539638.290 rows=1 loops=1)"
    " -> Bitmap Heap Scan on nw (cost=5716.58..692820.62 rows=305210 width=4) (actual time=1074.860..539482.828 rows=314909 loops=1)"
    " Recheck Cond: (frc = 1)"
    " -> Bitmap Index Scan on nw_frc_btree (cost=0.00..5640.28 rows=305210 width=0) (actual time=988.216..988.216 rows=314909 loops=1)"
    " Index Cond: (frc = 1)"
    "Total runtime: 539639.177 ms"

    COUNT(gid) reste un peu plus cher, parce qu'il faut regarder la valeur du champ pour savoir si il est nul, d'où le "width=4", alors qu'avec COUNT(*) width=0.
    Bingo, 27 Avril 2007
    #25
  6. Offline
    Tifox ou pas
    Et bien voila, ont sait tout sur le count() :p
    Tifox, 27 Avril 2007
    #26
  7. Offline
    oNi- Elite
    mysql_num_rows n'est pas du SQL.

    Ta question à la base était de savoir s'il était plus efficace/rapide de faire un COUNT ou, un SELECT suivi de la fonction php mysql_num_rows() sur le résultat du SELECT.
    La réponse est celle que cvm t'a fournie, à savoir le COUNT.

    Maintenant, ça ne veut pas dire qu'il faut refaire un COUNT si tu as déjà fait un SELECT bien entendu.
    oNi-, 27 Avril 2007
    #27
  8. Offline
    SkYlEsS Kawai
    Mais si je veux compter le nombre d'id et en même temps récupérer des infos, que vaut-il mieux faire ?

    SELECT champ_1, champ_2 COUNT(*) FROM table WHERE ...

    ou

    SELECT COUNT(*) FROM table WHERE ... suivi de SELECT champ_1, champ_2 FROM table WHERE ...

    :-D
    SkYlEsS, 27 Avril 2007
    #28
  9. Offline
    Bingo Beer Addict
    En MySQL (je ne sais pas à partir de quelle version), tu peux faire :
    SELECT SQL_CALC_FOUND_ROWS champ_1, champ_2 FROM table WHERE ...;
    Puis :
    SELECT FOUND_ROWS();

    Mais tu dois faire attention à ce qu'aucune autre requête n'ait été exécutée dans cette connexion avant ton SELECT FOUND_ROWS(), car ça renvoie le nombre de lignes dans la dernière requête exécutée sur cette connexion.

    Par contre, c'est très pratique si tu veux faire un SELECT ... LIMIT xx , parce qu'il te donne le nombre de lignes au total. Du coup c'est plus facile de faire des trucs du style "résultats 11 à 20 sur 138".
    Bingo, 27 Avril 2007
    #29
  10. Offline
    La Poubelle Elite
    Pas mal le système de PostgreSQL. Ca a l'air d'être un bon système de BDD pour un serveur WEB public quand on ne connait pas le niveau des utilisateurs ainsi que l'utilisation qu'ils vont faire.

    Ca évite des carnages mémoires.

    Thanks pour l'info =]
    La Poubelle, 27 Avril 2007
    #30
  11. Offline
    oNi- Elite
    Ce n'est pas du tout pareil :
    Dans le premier cas, il te faut faire un group by et tu auras dans la colonne du COUNT(*) le nombre d'enregistrements ayant les mêmes informations que cette ligne
    Exemple trivial:
    Code:
    SELECT COUNT(*), id_contact FROM contact WHERE titre ='Madame' GROUP BY id_contact;
    te donnera
    1 5
    1 15
    1 22
    etc
    donc comme on groupe sur l'id (clé primaire) et qu'il est unique, tu auras toujours 1 dans la colonne du COUNT(*).

    Maintenant si tu fais un group by su un champ qui n'est pas obligatoirement unique, là tu pourrais avoir par exemple :
    Code:
    SELECT COUNT(*), prenom FROM contact WHERE titre ='Madame' GROUP BY prenom;
    qui te donnerait :
    2 Agnès
    1 Anne
    3 Catherine
    etc

    Voilà pour le premier cas.

    Alors que dans l'autre :
    Code:
    SELECT COUNT(*) FROM contact WHERE titre ='Madame';
    te donnera 1 seule ligne contenant le nombre ( x ) d'enregistrements que tu trouveras dans le résultat du SELECT que tu vas effectuer juste après :
    Code:
    SELECT id_contact, etc FROM contact WHERE titre ='Madame';
    qui te fournira donc x lignes (résultat du COUNT précédent donc) avec les colonnes sélectionnées.


    EDIT, en français ça donne un peu mieux :
    Réponse : tout dépend de ce que tu cherches à faire, savoir le nombre de lignes renvoyées par le SELECT ou connaître le nombre d'occurences pour une même valeur.

    EDIT² : Donc le 2eme cas ne sert à rien, vu que la réponse du COUNT avant le SELECT peut être fourni par mysql_num_rows() sur le résultat du SELECT.
    oNi-, 27 Avril 2007
    #31
  12. Offline
    Bingo Beer Addict
    PostgreSQL est excellent en TOUT, SAUF pour :
    - COUNT(*)
    - MAX() ou MIN() (mais résolu depuis la version 8.0)

    Pour le COUNT(*), je ne vois pas bien comment ils pourraient résoudre ça. A part en ajoutant les informations de transaction aux index, mais ce serait un changement profond, donc peu probable.
    Bingo, 27 Avril 2007
    #32
  13. Offline
    SkYlEsS Kawai
    Quelle est la différence entre mysql_fetch_assoc et mysql_fetch_array ?

    L'un s'emploie-t-il alors que l'autre ne s'emploie pas dans quels cas ?

    Que faut-il mieux faire pour un simple SELECT par exemple ?

    :=)
    SkYlEsS, 10 Mai 2007
    #33
  14. Offline
    SkYlEsS Kawai
    up :]
    SkYlEsS, 11 Mai 2007
    #34
  15. Offline
    oNi- Elite
    mysql_fetch_array() : donne un tableau indexé sur le nom des champs et un offset (base 0) **
    mysql_fetch_assoc() : donne un tableau indexé sur le nom des champs
    mysql_fetch_row() : donne un tableau indexé par un offset (base 0)

    parait que le plus rapide est _row.
    y en a encore d'autres...

    **EDIT : $data['nom_champ'] ou $data[0].
    oNi-, 11 Mai 2007
    #35
  16. Offline
    SkYlEsS Kawai
    Est-ce une mauvaise manie de faire un $data['0'] au lieu d'un $data[0] ?
    SkYlEsS, 12 Mai 2007
    #36
  17. Offline
    oNi- Elite
    A priori l'emploi des quotes c'est pour les tableaux associatifs.
    Ici le 0 est l'indice, donc je dirais oui :)
    oNi-, 12 Mai 2007
    #37
  18. Offline
    moustic Touriste
    J'arrive peut-être en plein milieu de la discussion mais quand ont fait un select * on a en retour un pointeur sur la première ligne qui contient le ou les critère de la requete, les données elles même ne sont retournée que lorsque l'on y accede.

    Donc à mon avis pas de différence entre un select * et un select champ1, champ2,...
    moustic, 13 Mai 2007
    #38
  19. Offline
    Tifox ou pas
    Tu es sur de ça ? Ca me parait bizarre, car ça sous-entend qu'il y a au moins 2 appel réseau à la BD, une pour avoir le pointeur, et l'autre pour avoir les données. Sans compter que si c'est effectivement séparer, il faut gérer les accès concurrent vu que le select et l'accès des données ne sont pas toujours suivi, donc risque de modification des données par un tiers.
    Tifox, 13 Mai 2007
    #39
  20. Offline
    neku Codeur roumain
    Je confirme que pour MySQL le retour d'un select est un pointeur sur les données et les données sont récupérées lors de l'accès.
    Maintenant il est possible de stocker directement la totalité des données en RAM pour ne pas devoir récupérer les données à chaque accès.

    Maintenant à savoir si php stock ou pas ...
    neku, 14 Mai 2007
    #40
Statut de la discussion:
Fermée.