connexion php<->mysql et performances en fonction des requetes

connexion php<->mysql et performances en fonction des requetes - SQL/NoSQL - Programmation

Marsh Posté le 28-07-2005 à 23:15:43    

Salut
 
Je travaille avec le puissant environnement php et la base de données mysql via l'extension mysql.
Mais ma question peut se généraliser à tout environnement / SGBD.
 
Je voudrais savoir, théoriquement, si :
- faire un mysql_query basique qui sélectionne 1000 lignes suivi de 1000 mysql_fetch_row (puor recupérer chacune des 1000 lignes sous forme d'un tableau)
 
revient il au même que
 
- faire 1000 msql_query basiques différents qui sélectionne chacun une ligne, suivi de 1000 mysql_fetch_row (
 
 au niveau rapidité/perf ? bande passante ? charge serveur base de données ?
 
Je suppose que le premier est plus rapide, ce serait logique.
 
Mais en réalité je ne sais pas ce qu'il se passe dans le cas 1)
Que retourne réellement le mysql_query ?
Une "ressource" dit la doc. Mais est ce que accéder à cette ressource avec les fonctions qui vont bien (mysql_fetch_row par exemple) fait une requete au serveur ?  
Je pense bien que oui. Cette requete est elle du même type que la requete émise lors d'un mysql_query ?
 
Si c'est le cas, dans le cas 1) comme le cas 2 il y a sans cesse des requetes au serveur.
 
A voir par contre si dans le cas 1) on peut dire qu'il y a 1001 requetes, alors que dans le 2) il y aurait 2000 requêtes ?
 
Je ne trouve pas d'explication à ce sujet, merci de m'éclaircir
Merci

Reply

Marsh Posté le 28-07-2005 à 23:15:43   

Reply

Marsh Posté le 28-07-2005 à 23:21:08    

Tu sors immédiatement de ton esprit l'eventualité d'utiliser la 2ème méthode :o  :D  
 
Quand tu envoies une requête (par mysql_query) :
- la requête est envoyée
- mysql construit le résultat
- mysql envoie le résultat vers PHP qui le lit, en entier, et le met en mémoire (identifié par la fameuse "ressource" )
- la fonction mysql_query se termine
 
Donc, quand ton script sort de la fonction mysql_query, tout le résultat de la requête est côté PHP. Ensuite, tous les appels à mysql_fetch_row (fetch_array ...) se font uniquement sur ce qui a été mémorisé par PHP :)
D'ailleurs, tu peux envoyer d'autres requêtes, et parcourir tout les résultats obtenus dans l'ordre que tu veux.
En fait, je me demande même si tu ne pourrais pas faire un mysql_close juste après le mysql_query, sans que ca ne t'empêche de parcourir le résultat (bien que la connexion soit fermée) [:figti]


Message édité par mrbebert le 28-07-2005 à 23:24:17
Reply

Marsh Posté le 28-07-2005 à 23:34:37    

Ok merci de ta réponse c'est plus clair
 
donc question dans la continuité :
une table contient 500 lignes.
 
On a besoin de sélectionner, disons 50 lignes.
 
Vaut il mieux faire  
- 1 requete qui sélectionne tout et on trie en php après
- 50 requetes car ce serait plus rapide
 
(théoriquement, est ce possible de répondre à cette question ?)
(rappelons que le select est ultra basique : select toto from tata where titi = id)

Reply

Marsh Posté le 28-07-2005 à 23:41:11    

Si tu as besoin de 50 lignes, tu récupères 50 lignes [:proy]  
 
(et tu fais le tri avec la requête, bien sur :o )

Reply

Marsh Posté le 28-07-2005 à 23:45:19    

euh... sauf que comme j'ai 50 ids différents, tu voudrais que je fasse  
 
WHERE id = 1 OR id = 3 OR id = 15 OR id=35 ?

Reply

Marsh Posté le 28-07-2005 à 23:53:01    

Ah ! Oui, c'est vrai que dans cette situation, c'est pas très pratique [:figti]  
Mais je pense que quelque chose du genre " ... WHERE id IN (1, 3, 15, 35)" reste plus performant que de passer plusieurs requêtes [:proy]  
 
(au détriment peut être de la lisibilité du script)

Reply

Marsh Posté le 29-07-2005 à 00:34:56    

oui tu as raison, c'est ça !
merci A+

Reply

Marsh Posté le 29-07-2005 à 01:59:57    

mrbebert > Pour la décomposition technique d'une requête, c'est, à priori, si je suppose que le requêteur interne de PHP fonctionne d'une façon similaire à ODBC ou OLE DB (ça me semblerait logique), diffère légèrement de ce que tu dis :
 
Cas le plus simple : requête "static" et curseur en mode "client" : (c'est à dire que le jeu de résultat est READONLY, et que le jeu de résultat n'est pas modifié par d'éventuelles mises à jour des données de bases par d'autres requêtes)
 
Lors du Query :
- Envoi de la requête vers MySQL
- Parsing de la requête
- Optimisation de la requête
- Execution de la requête (recherche des lignes)
- Génération en mémoire d'un index des lignes trouvées
- Mise en mémoire tampon d'un certain nombre de "pages" du résultat
- Notification à PHP que la requête a été terminée (en cas de requête assynchrone, MySQL peut répondre dès l'ajout de la première entrée dans l'index des lignes trouvées, avant même la mise en tampon)
 
Ensuite, lors d'un fetch :
- MySQL envoie une "page" de résultats vers PHP (une page représentente une taille mémoire)
- PHP va chercher la ligne dans cette page tampon.
- Les fetch suivants vont chercher dans cette page, puis redemander une nouvelle page à MySQL lorsqu'on arrive à la fin.
- Lorsque MySQL n'a plus de page préparées en mémoire, il en re-crée en fonction de l'index des lignes trouvées par la requête.
 
En bref, si tu fais un "mysql_query" avec l'une ou l'autre de ces deux requêtes :
 
select * from latable where id = 1
 
ou
 
select * from latable
 
avec "latable" qui contient 20 milliard de lignes, ça doit mettre (à la taille de transfère d'une page près) rigoureusement le même temps.
 
par contre, évidement, les 20 milliards de fetch qui vont suivre, ça va pendre du temps, mais moins que si on refait la requête pour chaque valeur de l'id, puisque la première partie de la transaction n'a plus lieu lors des fetch.
 
évidement, cette requête sera par contre bien plus lente :
 
select min(id) from latable
 
=> En effet, puisque même si une seule ligne est retournée, MySQL a dû lire en interne l'intégralité des lignes de la table avant de fournir le premier résultat. (bon, pas dans ce cas, puisque logiquement id sera dans un index ordonné ;)

Reply

Marsh Posté le 29-07-2005 à 02:06:09    

PS: l'utilisation de ce système est d'ailleurs évident de la par du moteur de PHP.
 
Car une simple ligne de résultat n'occupe pas une trame entière sur le réseau (ou alors t'as une sacrée table :D)
 
Résultat, faire 1000 fetch (classique), ça reviendrait à faire 1000 trammes (enfin, au moins le doublie à cause des accusés de réception nécessaire aux deux pour savoir s'ils communiquent comme il faut.
 
1000 trames réseau, même sur un réseau à 100MBps, ça commence à faire du monde et prendre du temps. Si on imagine un site web assez fréquenté, on arriverait immédiatement à une saturation du réseau avant même de saturer quoi que ce soit d'autre.
 
Hors une "page" de 1000 lignes classiques, à priori, ça ne prends pas plus de place d'un point de vue trame qu'une ligne toute seule. Donc là, y'a pas photo, si on veut un truc performant, il faut bosser avec cette mémoire tampon.

Reply

Marsh Posté le 29-07-2005 à 02:08:33    

à noter d'ailleurs que faire 1000 requêtes à la suite, c'est générer 1000 trames aussi. donc là où on peut récupérer 1000 lignes en quelques trames, ou 1 par trame, y'a pas photo, c'est bien plus rapide. le réseau deviendrait rapidement le goulot d'étranglement.

Reply

Marsh Posté le 29-07-2005 à 02:08:33   

Reply

Marsh Posté le 29-07-2005 à 10:00:06    

Je pense pas qu'il fonctionne comme ca [:figti]  
 
Du moins, pour la fonction mysql_query() (avec les paramètres "par défaut" ). Par contre, il existe une fonction mysql_unbuffered_query() qui s'en rapproche. Elle permet de lire les lignes par mysql_fetch_row() au fur et à mesure qu'elles arrivent du serveur :)  
 
(je vais faire quelques tests :) )

Reply

Marsh Posté le 29-07-2005 à 10:40:55    

J'ai testé avec le petit script suivant :

Code :
  1. <?
  2. function getmicrotime(){
  3. list($usec, $sec) = explode(" ", microtime());
  4. return ((float)$usec + (float)$sec);
  5. }
  6. // initialisations
  7. echo "Debut script";
  8. mysql_connect('localhost','root','') or die('ERREUR : connexion');
  9. mysql_select_db('test') or die('ERREUR : select_db');
  10. // execution requete
  11. $t_0 = getmicrotime();
  12. $res = mysql_query('SELECT * FROM test');
  13. $t_1 = getmicrotime();
  14. echo "\nExecution requete : " . ($t_1 - $t_0) . ' sec';
  15. // cloture connexion
  16. mysql_close();
  17. // pause 30 secondes
  18. echo "\nPause 30 sec";
  19. sleep(30);
  20. // parcours du resultat
  21. $nb_lignes = 0;
  22. while ($r = mysql_fetch_row($res)) {
  23. $nb_lignes++;
  24. }
  25. echo "\nNombre de lignes : " . $nb_lignes;
  26. echo "\nFin script\n";
  27. ?>


J'exécute une requête, je ferme la connexion puis je parcours le résultat. Pour être vraiment sur, j'ai mis une pause pendant laquelle je peux couper le serveur MySQL. Quand le script arrive sur les mysql_fetch_row(), il n'y a plus de serveur MySQL :D  
(la table test.test contient 193900 lignes de 211 octets)
 
Ca me donne le résultat suivant :

Code :
  1. Debut script
  2. Execution requete : 1.98890590668 sec
  3. Pause 30 sec
  4. Nombre de lignes : 193900
  5. Fin script

Pas de problème pour parcourir le résultat, même si en étant déconnecté du serveur MySQL :pt1cable:


Message édité par mrbebert le 29-07-2005 à 10:42:18
Reply

Marsh Posté le 29-07-2005 à 10:49:43    

En remplacant mysql_query par mysql_unbuffered_query, le comportement n'est plus le même.
 
En fermant la connexion (et en coupant MySQL) :

Code :
  1. Debut script
  2. Execution requete : 0.00264811515808 sec
  3. Pause 30 sec
  4. Nombre de lignes : 0
  5. Fin script

Déjà, la requête est nettement plus rapide qu'avec mysql_query (normal)
Ca plante pas mais, lorsqu'il doit parcourir le résultat, il n'y a plus aucun lignes disponible [:figti]  
 
Maintenant, en enlevant le mysql_close() :
(mais toujours en coupant le serveur MySQL à l'insu du script)

Code :
  1. Debut script
  2. Execution requete : 0.00203490257263 sec
  3. Pause 30 sec
  4. Nombre de lignes : 308
  5. Fin script

Là, c'est curieux :pt1cable:  
Effectivement, j'ai l'impression que les données arrivent par "page". La 1ère étant envoyée par le mysql_unbuffered_query(). Tant qu'il lit les données dans cette page, ca va. C'est seulement lorsqu'il a besoin d'une autre page qu'il se rend compte qu'il n'y a plus d'autres lignes disponibles [:figti]  
 
Par contre, pas de message d'erreur pour indiquer qu'on a perdu la connexion :o


Message édité par mrbebert le 29-07-2005 à 10:52:02
Reply

Marsh Posté le 29-07-2005 à 12:21:11    

c'est chelou le fonctionnement de PHP :o
 
avec VBScript, si tu fermes la connection, toutes les ressources affectées par la connection sont perdues (si t'as un recordset ouvert alors que la cnx se ferme, tu obtiens une erreur lors de la consultation du recordset t'indiquant que la connection n'est plus ouverte.

Reply

Marsh Posté le 29-07-2005 à 12:27:02    

C'est la différence query/unbuffered_query : la première permet de parcourir le dataset hors connecté, de s'y balader. L'unbuffered_query oblige à être connecté, et se comporte style curseur en avant : impossible de fouiller n'importe où. Du coup performances max.
 
Et dans moult cas, vaut mieux utiliser un unbeffered, vu que ça se résume à un simple fetch....

Reply

Marsh Posté le 29-07-2005 à 13:01:54    

Pourquoi si il faut mieux utiliser unbuffered query, peu de gens en parlent ?
 
Quels défauts cela a t'il ?

Reply

Marsh Posté le 29-07-2005 à 13:57:51    

J'en sais rien [:spamafote]. Y'a plein de bizarreries :

  • include au lieu de require pour des fichiers indispensables
  • include au lieu de readfile pour les fichiers statiques
  • echo avec de la concaténation

Bref, plein de trucs à la con relayés par tout le monde...

Reply

Marsh Posté le 29-07-2005 à 14:24:24    

matthieu_phpmv a écrit :

Pourquoi si il faut mieux utiliser unbuffered query, peu de gens en parlent ?
 
Quels défauts cela a t'il ?

Ca à quand même certaines contraintes.
 
Par exemple :

Code :
  1. $res1 = mysql_unbuffered_query('SELECT * FROM table1');
  2. while ($r = mysql_fetch_row($res1)) {
  3.     $res2 = mysql_query('INSERT INTO table2 VALUES(' . ($r[0]+$r[1]) . ')');
  4.     ....
  5. }

Ca va pas marcher comme prévu. Tu lis le 1er enregistrement de $res1, puis la 2ème requête annule la 1ère requête. Donc, le 2ème appel à mysql_fetch_row() va renvoyer false alors que tu n'as pas lu tous les résultats.
De même, tu ne peux pas exécuter 2 requêtes et parcourir les 2 résultats en même temps
Pour un débutant, ce genre de bug est pas évident à trouver [:proy]  
 
Des fonctions comme mysql_result() ou mysql_num_rows()  ne fonctionnent pas. Tu peux pas te déplacer comme tu veux dans le résultat (revenir au 1er enregistrement ...).
 
Il y a peut être des problèmes de verrouillage de ressources. Je serais pas étonné que les tables restent verrouillées tant que tout n'a pas été lu. Alors qu'avec un mysql_query(), tout est lu d'un coup, la table est libérée et ensuite, le script peut prendre le temps qu'il veut pour traiter les résultats.
 
Perso, j'utilise mysql_unbuffered_query() seulement lorsque je m'attends à un résultat volumineux [:proy]


Message édité par mrbebert le 29-07-2005 à 14:26:22
Reply

Marsh Posté le 29-07-2005 à 14:26:22    

Oui, y'a un lock sur la table :(

Reply

Marsh Posté le 29-07-2005 à 14:26:50    

J'avais oublié ce paramètre :D

Reply

Marsh Posté le 29-07-2005 à 14:59:19    

en fait, le buffered est très intéressant en cas de requêtes qui ne retournent qu'un petit jeu de données : ainsi, on ne noie pas le serveur web avec des données, et le traîtement ensuite en mémoire est instantanné, alors que le transfert réseau a été très court.
 
par contre, en effet, avec les gros volumes, ça me semble totalement très tout plein beaucoup vraiment énormément un max extraordinairement dangereux :)
 
genre charger un curseur de 100 Mo de données, si y'as plusieurs personnes qui éxécutent la même page en même temps, ben t'as ton serveur qui part sur pluton direct :D


Message édité par Arjuna le 29-07-2005 à 15:00:11
Reply

Marsh Posté le 29-07-2005 à 15:04:09    

Ah oui mais si il lock les tables ça limite tout de suite l'intérêt de la chose également.  
Dommage aussi qu'on ne puisse pas faire de num_rows car c'est quand même très utilisé (dans mon cas en tout cas ;)).
merci pour vos précisions

Reply

Marsh Posté le 29-07-2005 à 15:05:36    

quand je regarde tout ça, je me dis que finalement, ADO avec OLEDB/ODBC, même si c'est pas ce qu'il y a de mieu niveau performances, c'est quand même vachement moins gore que les libs de PHP...
 
Y'a 3 grandes familles de curseurs avec ADO :
 
- Les statics locaux : fonctionne comme j'ai décrit plus haut (readonly, forward only)
- Les statics serveur : idem, mais il n'y a pas de pagination sur le client (readonly, random access)
- Les dynamiques : idem, sauf que le client reçoit des notifications de mises à jours depuis le serveur et vice versa (updateable, random access, mais nombre de lignes inconnu)
 
Mise à part le dernier, aucun lock n'est en aucun cas fait sur la base. Et même dans le cas d'un dynamique, c'est le système de lock du serveur qui est utilisé (donc par ligne, page ou table, selon les cas). Les dynamiques sont évidement transactionnels.

Reply

Marsh Posté le 29-07-2005 à 15:08:45    

:jap:
 
'tain, je préfère encore manipuler des recordset sous Access... C'est 10 fois mieux, surtout les dynamiques comme tu dit, tu vois pas une seule instruction SQL, tout est fait tout seul

Reply

Marsh Posté le 29-07-2005 à 16:29:32    

FlorentG a écrit :

J'en sais rien [:spamafote]. Y'a plein de bizarreries :

  • include au lieu de require pour des fichiers indispensables
  • include au lieu de readfile pour les fichiers statiques
  • echo avec de la concaténation

Bref, plein de trucs à la con relayés par tout le monde...


Euh non, faut pas exagérer, le unbuffered a énormément de contrainte contrairement aux exemples que tu cites.
Faire un script qui affiche le résultat d'une requête sans même savoir le nombre de retour que va générer la requête, c'est super dangereux.
La requête unbuffered a été fait pour des utilisations bien spécifiques mais de la à dire qu'elle est préférable à utiliser à une buffered, non, pas d'accord.
Et si tu regardes la majorité des projets web/php, tu verras que l'unbuffered ne tiens pas la route, c'est pour cela qu'on en fait très peu la pub.
 

Reply

Marsh Posté le 29-07-2005 à 16:35:18    

Ben si, au contraire, sur le web, on utilise très généralement des LIMIT, donc le unbuffered ne représente aucun danger.

Reply

Marsh Posté le 29-07-2005 à 16:42:31    

ça ne te donne pas pour autant le nombre de résultat retourné.
Bon, disons que "Dangereux" est un terme un brin excessif.
Disons que JE trouve mais je peux me tromper, que réaliser la majorité des requêtes sans connaitre le retour du résultat serait une abération.
Quelques unes, oui okey, mais de la à sous-entendre que la méthode devrait être généralisé plutot que la buffered, non, je ne suis pas d'accord.

Reply

Marsh Posté le 29-07-2005 à 16:46:47    

The-Shadow a écrit :

Et si tu regardes la majorité des projets web/php, tu verras que l'unbuffered ne tiens pas la route, c'est pour cela qu'on en fait très peu la pub.


La majorité des projets webs, c'est un mysql_query suivi d'un fetch_array et rien d'autre ;)

Reply

Marsh Posté le 29-07-2005 à 16:51:08    

FlorentG a écrit :

La majorité des projets webs, c'est un mysql_query suivi d'un fetch_array et rien d'autre ;)


Bah c'est bien ce que je dis. :D
C'est toi qui dit qu'il faudrait que les gens utilisent en priorité la commande unbuffered, en tout cas, c'est la conclusion que j'en tire vu les exemples que tu donnes.

Reply

Marsh Posté le 29-07-2005 à 16:56:06    

Nanan, pas en priorité, mais pour un truc ultra-simpliste, autant y aller en unbuffered

Reply

Marsh Posté le 29-07-2005 à 16:57:39    

Ha okey, on est d'accord alors. :D

Reply

Marsh Posté le 29-07-2005 à 16:58:22    

Oui. Champagne.
 
 
 
 
:D

Reply

Marsh Posté le 30-07-2005 à 15:02:24    

En fait plus haut j'ai mal exposé mon problème.
 
Je ne veux pas faire SELECT name FROM t WHERE id IN (1,2,5,6)
 
Car je ne veux pas n'importe lesquels, mais je veux bien la valeur name associé à un id
 
Donc je suis bien obligé de faire 50 requetes pour sélectionner 50 name associés à 50 id que je connais avant la requete.
Car on ne peux pas sélectionner 50 valeurs qui chacun respectent un critère différent me semble t'il ?

Reply

Marsh Posté le 30-07-2005 à 19:06:13    

SELECT id, name FROM t WHERE id IN (1,2,5,6);
 
Non ? :D

Reply

Marsh Posté le 30-07-2005 à 19:16:47    

c'est pas indispensable si tu ne souhaites pas récupérer l'id par la suite.

Reply

Marsh Posté le 30-07-2005 à 19:51:28    

oula beegee je crois que tu as raison, merci
 
et je crois que je vais aller me reposer...
 
:D

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed