php-experts.org - développement php et internet » MySQL https://www.php-experts.org Ressources sur le développement internet, PHP/MySQL, Ajax, marketing online, référencement... Sat, 19 Jun 2010 14:23:03 +0000 http://wordpress.org/?v=2.9.2 en hourly 1 Les bases de données épaisses https://www.php-experts.org/bases-de-donnees/mysql/les-bases-de-donnees-epaisses-297 https://www.php-experts.org/bases-de-donnees/mysql/les-bases-de-donnees-epaisses-297#comments Mon, 13 Jul 2009 01:16:47 +0000 Didier https://www.php-experts.org/?p=297 Frédéric Brouard a lancé un pavé dans la marre du développement en expliquant le concept de “bases de données épaisses“. Il va même plus loin en affirmant que ce mode de développement peut assassiner les ORM et les FrameWorks.

Pour rappel, l’ORM (pour “Object-Relational Mapping“) vise à faire correspondre un objet de la couche applicative aux données qu’il exploite dans la base de données. En quelque sorte, on a donc une “base de données orientée objet” puisque, pour faire très simple, chaque champ de la base peut être une propriété d’un objet. Mais selon Frédéric Brouard, le concept mérite qu’on s’y attarde et qu’on le développe.

Dans un applicatif exploitant une base de données épaisse, ce n’est ni le client ni le serveur web qui doivent réaliser le maximum des opérations nécessaires, mais bel et bien la base de données. Outre le fait qu’on obtient une application plus facilement portable (puisque le PHP ne ferait plus ou moins que de l’affichage), on supprime aussi un goulot d’étranglement important en éliminant les aller/retour incessants entre l’applicatif et la base.

Pour prendre un exemple simple, quand on veut calculer une moyenne, on ne rapatrie pas l’ensemble des lignes à prendre en compte avant de faire un calcul à la main, on utilise plutot la fonction AVG() qui exécute le traitement nécessaire et ne renvoie qu’un résultat final.

Avec une base de données épaisse, en utilisant notamment des requêtes SQL avancées, les procédures stockées, et pourquoi pas des User-Defined Functions (UDF) sous MySQL, on peut arriver au même résultat avec la majorité des traitements que nécessitent une application.

En déportant son code métier vers la base de donnée quand cela est possible, non seulement on décharge les serveurs frontaux, mais on améliore aussi la circulation des données dans l’ensemble de l’architecture, puisqu’on ne rapatrie à aucun moment de gros resultsets uniquement pour les traiter. Les liens entre base de donnée et applicatifs sont ainsi préservés.

La base se retrouve donc avec plus d’opérations à réaliser, mais il faut bien le reconnaître: sur un site à fort trafic, à moins qu’on aie beaucoup d’écritures à réaliser, c’est rarement la base – même avec un certain volume d’informations – qui représente le goulot d’étranglement le plus étroit. C’est souvent plutôt le serveur Web et la couche réseau en général qui saturent (J’ai plus souvent vu 1 BDD + 3 Apache en front que le contraire…).

Du coup, le principe d’ORM peut être tué, mais aussi sublimé: une base de données épaisse pourra ne pas embarquer que la partie “stockage” d’un objet (propriétés) mais aussi sa partie “métier” (méthodes). Et si possible, tout le reste, et en restant basé sur du relationnel, donc sans avoir à quitter son SGBDR favori ;)

Liens

Le Monde Informatique : Les ORM et les frameworks survivront-ils au concept de développement en base de données épaisse ?
Frédéric Brouard, alias SQLPro sur Développez.com

]]>
https://www.php-experts.org/bases-de-donnees/mysql/les-bases-de-donnees-epaisses-297/feed 7
Procédures et fonctions sous MySQL: les bases https://www.php-experts.org/bases-de-donnees/mysql/procedures-et-fonctions-sous-mysql-les-bases-231 https://www.php-experts.org/bases-de-donnees/mysql/procedures-et-fonctions-sous-mysql-les-bases-231#comments Sun, 10 May 2009 21:37:21 +0000 Didier https://www.php-experts.org/?p=231 Oracle dispose d’un langage appelé PL/SQL pour compiler des procédures et des fonctions sur le serveur. Ces procédures et fonctions peuvent être appelées directement en SQL. Quand elles sont écrites correctement, elles permettent en général un gain de performances non négligeable, en plus d’être pratiques et agréables à utiliser. En gros, les procédures et fonctions sont un excellent moyen d’apporter une couche d’intelligence supplémentaire à votre serveur de bases de données, en lui permettant d’exécuter des actions complexes sans avoir recours à des scripts extérieurs. On économise donc le protocole de communication entre base de données et application. Et bien… C’est possible aussi sous MySQL !

Mini rappel : Procédure, ou fonction ?

Si vous hésitez entre créer une procédure stockée ou une fonction, rappelez vous bien que la seule différence entre les deux est qu’une fonction va chercher un résultat (quitte à passer par des tonnes d’étapes intermédiaires), alors qu’une procédure va faire une action. En gros, si vous voulez avoir une valeur de retour, il vous faut une fonction. Dans le cas contraire, préférez une procédure.

Dans quel cas les utiliser, et comment ?

Vous pouvez vous tourner vers les ProcStock (pour “procédures stockées”, le terme étant souvent utilisé aussi pour les fonctions) partout où vous exécutez des traitements de calculs lourds et/ou sur de gros volumes de données. L’avantage énorme est que vous n’aurez pas à rapatrier des resultsets de grande taille, pour les traiter en PHP (par exemple), puis les insérer en base: tout se fera directement en une seule requête très simple, qui appellera la fonction/procédure.
Les fonctions MySQL que vous allez définir s’utilisent exactement comme les fonctions prédéfinies (bien que celles-ci soient en général écrites en C et compilées avec le serveur… c’est faisable aussi pour un gain maximal en performances MySQL, mais ceci est une autre histoire), comme par exemple AVG (qui calcule une moyenne sur les valeurs d’un champ). Sans AVG (syntaxe: SELECT AVG(champ) FROM table), il faudrait récupérer les résultats concernés, les ajouter, puis les diviser par leur nombre : (1+5+6)/3 = 4. AVG fait ça toute seule et renvoie directement 4. Bien évidemment, il n’est pas bien grave d’avoir à récupérer 3 lignes. Mais avec 20.000 enregistrements, c’est différent, et les performances seront affectées, notamment en raison de l’utilisation de RAM nécessaire à l’exécution du script.
Il peut être aussi très intéressant d’utiliser des procédures et fonctions sur le serveur de bases de données quand plusieurs applications frontend dans des langages différents peuvent avoir à réaliser les mêmes actions: plutôt que d’écrire (et maintenir…) les actions communes en plusieurs langages, autant déporter leur exécution sur le serveur SQL, et demander aux clients de seulement interagir avec les fonctions stockées.

Application concrète

(je déplore mon manque d’imagination, qui m’oblige à chaque post à sortir des exemples totalement improbables…)
Imaginons un site de vente en ligne. Chaque jour est généré un rapport, enregistré en base, qui, en fonction du détail des ventes de la journée, calcule des indicateurs comme le chiffre d’affaires global et le panier moyen.
On aurait donc une table “commandes” avec un champ “montant” et un champ “date” (je simplifie, hein).
En PHP, sans procstock, il faut :

  • Envoyer une requête qui prend les ventes de la journée passée
  • Récupérer dans un tableau le détail des transactions
  • Faire les calculs nécessaires (nombre de lignes, moyenne des montants, total des montants)
  • Stocker ces résultats en base

Au bas mot, cela représente une vingtaine de lignes de PHP, avec deux communications depuis/vers la base de données, une boucle qui parse le tableau, des variables temporaires…
Dans cette situation, c’est d’une procédure stockée que nous avons besoin. On ne récupère pas les infos (pas de valeur retournée) mais on les stocke en base. Dans le cas contraire, on aurait créé une fonction.
La procédure stockée en question, que nous appellerons “genere_rapport”, va s’occuper de tout cela pour nous. Voici son code :

  1. DELIMITER //;
  2. CREATE PROCEDURE genere_rapport()
  3.   BEGIN
  4.     DECLARE nb_commandes INTEGER(5);
  5.     DECLARE panier,chiffre_affaires FLOAT;
  6.     SELECT COUNT(*),AVG(montant),SUM(montant) INTO nb_commandes,panier,chiffre_affaires FROM commandes LIMIT 1;
  7.     INSERT INTO rapports (nb_com, panier_moyen, ca_total) VALUES (nb_commandes, panier, chiffre_affaires);
  8.   END//
  9. DELIMITER ;

MySQL devrait répondre “Query OK, 0 rows affected (0.01 sec)” pour signaler que la procstock a bien été créée.
Première remarque, on change le délimiteur de fin de commande MySQL. La création d’une procédure/fonction doit se faire en une seule instruction MySQL, même si la procstock comporte plusieurs instructions à exécuter. J’avoue que ça surprend au début mais c’est un coup à prendre. Pensez bien à remettre le délimiteur normal (le point-virgule) après la création de votre procstock.
Ensuite, on lance la création de la procédure. On déclare d’abord les variables dont on aura besoin pour stocker les données (même si ici, on aurait pu directement faire le SELECT dans une sous-requête de l’INSERT, mais ce n’est pas le but): la liste des types disponibles est la même que les types des champs. On utilise ensuite SELECT INTO avec le nom de nos variables pour dire à MySQL dans quelle variable stocker quelle valeur, variables qu’on utilise ensuite dans une requête INSERT classique pour stocker le rapport.
Pour appeler la procédure, on fera CALL genere_rapport();. Pour une fonction, ça sera SELECT nom_de_la_fonction();

Conclusion

Penchez-vous sur vos applications PHP, il y a certainement des tas d’actions que vous pourrez déporter vers des procédures stockées. Les gains de performances devraient être rapidement ressentis, pour un effort d’apprentissage minimal.
Dans un prochain post, nous irons plus loin avec les procédures stockées, en utilisant notamment des paramètres, des curseurs, des handlers, et autres joyeusetés. Le plus difficile sera de trouver un exemple intéressant… ;)

]]>
https://www.php-experts.org/bases-de-donnees/mysql/procedures-et-fonctions-sous-mysql-les-bases-231/feed 3
CSV et MySQL : SELECT INTO OUTFILE et LOAD DATA INFILE https://www.php-experts.org/bases-de-donnees/mysql/csv-mysql-select-into-outfile-load-data-infile-206 https://www.php-experts.org/bases-de-donnees/mysql/csv-mysql-select-into-outfile-load-data-infile-206#comments Mon, 27 Apr 2009 01:05:07 +0000 Didier https://www.php-experts.org/?p=206 J’ai eu à me pencher sur les imports-exports sous MySQL. Mon but était de disposer de fichiers utilisables dans un format “humain” (comprendre: que les gens du marketing pouvaient exploiter avec leur cher Excel) sans pour autant passer par des scripts de conversion hasardeux et lourds pour le serveur. Il a donc fallu que je cherche les meilleures solutions pour pouvoir générer et importer des fichiers CSV dans MySQL,mon SGBDR favori. J’ai dû me servir du couple SELECT INTO OUTFILE pour les exports, et LOAD DATA INFILE pour les imports. Petit rappel syntaxique.

Exports CSV avec MySQL : SELECT INTO OUTFILE

Le principe de SELECT INTO OUTFILE est simple: réaliser un export de données en écrivant un resultset (résultats d’exécution d’une requête) directement dans un fichier CSV sur le serveur. Pour cela, l’utilisateur avec lequel vous vous connectez à MySQL doit avoir le priilège “FILE”. Autre remarque, vous ne pourrez en aucun cas écraser un fichier déjà existant sur le serveur (ceci pour la simple et bonne raison qu’il serait assez dommageable d’écraser, par exemple, votre fichier /etc/passwd).

Voici donc la syntaxe, finalement assez simple, de la fonction SELECT INTO OUTFILE :

  1. SELECT champ
  2. FROM TABLE
  3. WHERE champ = ‘valeur cherchée’
  4. INTO OUTFILE ‘/var/dump.csv’
  5. FIELDS
  6.     TERMINATED BY ‘;’
  7.     OPTIONALLY ENCLOSED BY ‘"’

Dans le fichier exporté, les champs ne sont pas délimités, sauf si vous utilisez FIELDS ENCLOSED BY. Le OPTIONALLY spécifie que seules les chaînes de caractères doivent être encadrées.
Le délimiteur par défaut est l’espace. Pour en utiliser un autre (virgule, point-virgule, tabulation, …) il faut utiliser la directive FIELDS TERMINATED BY. Evidemment, on peut utiliser des caractères spéciaux comme la tabulation ‘t’ ou le retour à la ligne ‘n’ (éventuellement CR+LF sous Windows, donc ‘rn’). Il existe aussi LINES TERMINATED BY pour contrôler le caractère de fin de ligne.

Du coup, en lançant sur la base de mon blog cette requête :

  1. mysql> SELECT ID,post_title,comment_count
  2. FROM `wp_posts`
  3. WHERE `post_status` = ‘publish’
  4.  ORDER BY `post_date` DESC LIMIT 3
  5. INTO OUTFILE ‘/tmp/blog_posts.dump’
  6. FIELDS
  7. TERMINATED BY ‘;’
  8. OPTIONALLY ENCLOSED BY ‘"’;

j’ai pu récupérer un fichier plat qui donnait :

  1. 179;"plugin : yURL ReTwitt";1
  2. 22;"5 plugins indispensables pour coder en PHP avec l’IDE Eclipse";0
  3. 138;"plugin : wp_list_sub_pages()";0

Soit, un joli fichier CSV bien propre, directement exploitable (pourquoi pas par Excel).

Imports de CSV dans MySQL : LOAD DATA INFILE

Le LOAD DATA INFILE, qui permet de faire l’exact inverse du INTO OUTFILE, est tout aussi simple à utiliser. Dans les bonnes conditions, c’est vraiment l’un des outils d’import MySQL les plus puissants.
Déjà, bonne nouvelle, la syntaxe des commandes qui permet à LOAD DATA INFILE de repérer les débuts et fin de champs (et de ligne) est la même que pour SELECT INTO OUTFILE. On retrouve donc sans surprise les FIELDS TERMINATED BY et autres joyeusetés.
Une gestion des doublons est aussi possible grâce aux mots-clefs IGNORE et REPLACE, qui parlent d’eux-même. Déclenchés en cas de doublon dans une clé (primaire ou unique), REPLACE effacera l’ancienne ligne pour la remplacer par la nouvelle. Attention donc, vous perdrez donc la pérennité de vos ID puisque ceux-ci changeront lors de l’import de données. IGNORE permettra simplement de conserver l’ancienne ligne, les nouvelles données n’étant pas écrites: vous conservez vos ID mais perdez le bénéfice de l’import sur cette ligne.
Vous pouvez aussi spécifier la liste des champs dans lesquelles les données doivent être stockées avec la syntaxe classique “(champ1, champ2, champ3)” (sans guillemets) en fin de commande.
Pour prendre un exemple, avec un fichier de la forme :

  1. 1;120;"texte1";
  2. 2;240;"texte2";

En imaginant qu’on ne veut garder que l’id (champ 1) et le texte (champ 3), et les insérer dans les champs correspondants de la table SQL “data”, on peut utiliser le paramètre @dummy pour demander au serveur d’ignorer l’un des champs du CSV. Cela nous donne une requête de la forme :

  1. LOAD DATA INFILE ‘/tmp/data.csv’
  2. INTO TABLE `data`
  3. FIELDS
  4. TERMINATED BY ‘;’
  5. OPTIONALLY ENCLOSED BY ‘"’
  6. (id, @dummy, texte)

Et si par malheur votre fichier commence par 2 lignes d’entête, pas de souci, vous pouvez présicer IGNORE 2 LINES dans la requête pour que soient ignorées les 2 premières lignes.

Et voila. Deux commandes, finalement pas bien complexes une fois qu’on les a prises en main, qui permettent de gérer efficacement les imports/exports de fichiers CSV sous MySQL. Pour faire suite à cet article, nous verrons bientôt comment optimiser la vitesse de vos INSERT dans MySQL.

]]>
https://www.php-experts.org/bases-de-donnees/mysql/csv-mysql-select-into-outfile-load-data-infile-206/feed 3
Optimisation d’une requête SQL avec EXPLAIN et log_slow_queries https://www.php-experts.org/bases-de-donnees/mysql/autopsie-et-optimisation-dune-requete-sql-avec-explain-et-log_slow_queries-14 https://www.php-experts.org/bases-de-donnees/mysql/autopsie-et-optimisation-dune-requete-sql-avec-explain-et-log_slow_queries-14#comments Sat, 12 Jul 2008 17:22:34 +0000 Didier https://www.php-experts.org/?p=14 Depuis quelques temps, un serveur qui héberge quelques petits sites s’est mis à monter régulièrement en charge, sans augmentation de trafic, ni changements applicatifs. J’ai laissé traîner les choses, ne sachant pas d’où venait le souci.
Il aura fallu cinq minutes de travail et l’utilisation de la commande shell top, de la directive de configuration MySQL log_slow_queries et de la commande SQL EXPLAIN pour régler le problème de lenteur des sites.
Tout d’abord, un top a confirmé mes craintes : c’est bien le serveur MySQL qui était en cause, prenait beaucoup de mémoire RAM et de ressources processeur. Il y avait donc des requêtes SQL qui avaient besoin d’être optimisées.

J’ai édité le fichier de configuration de MySQL (/etc/mysql/my.cnf sous Linux Debian) pour y activer l’option log-slow-queries en modifiant les trois lignes suivantes :

  1. log_slow_queries = /var/log/mysql/mysql-slow.log
  2. long_query_time = 2
  3. log-queries-not-using-indexes

Je le répète, long_query_time = 2 signifie qu’on doit logger toutes les requêtes qui mettent plus de deux secondes à s’exécuter. Après un redémarrage du serveur (cette option ne peut malheureusement pas être changée à chaud), j’ai attendu la montée en charge suivante pour que MySQL (grâce à long_query_time) enregistre sagement toutes les requêtes mettant plus de 2 secondes à s’exécuter (log_query_time est exprimé en secondes et doit être un nombre entier compris entre 1 et 10), et celles qui n’utilisaient aucun index, dans le fichier /var/log/mysql-slow.log

Voici ce que j’y ai trouvé :

  1. # Query_time: 0  Lock_time: 0  Rows_sent: 5  Rows_examined: 17165
  2. SELECT * FROM chanson LEFT JOIN artiste ON artiste_ID = ID_artiste WHERE artiste_ID = 50 ORDER BY RAND() LIMIT 5;

Quasiment 20.000 lignes examinées (la totalité de la table) pour une requête qui en ramènera seulement 5, c’est beaucoup.

Je me suis connecté à phpMyAdmin, et j’ai utilisé la commande explain, en rajoutant simplement le mot EXPLAIN devant ma requête SQL :

  1. EXPLAIN SELECT *
  2. FROM chanson
  3. LEFT JOIN artiste ON artiste_ID = ID_artiste
  4. WHERE artiste_ID = 50
  5. ORDER BY RAND( )
  6. LIMIT 5;

La commande MySQL EXPLAIN permet de voir, notamment, les clés (index, primary, uniques…) utilisées pour aller chercher les résultats d’une requête, ainsi que le nombre de lignes parcourues.
Voici quel a été le résultat de la commande Explain :
MySQL Explain

J’ai ajouté un “ID_artiste = 50″ dans la clause WHERE, qui n’a rien changé. Explain renvoyait toujours “NULL” dans les Possible keys concernant la table Chanson. Après vérification, en effet, il n’y avait aucun index sur ce champ de la table, pourtant souvent utilisé dans mes clauses WHERE. Un ALTER TABLE `chanson` ADD INDEX(`artiste_ID`) plus tard, la création de l’index sur le champ de la base de données concernée a permis de changer le résultat de l’EXPLAIN sur la même requête SQL :
MySQL Explain
Plus que 76 lignes lues pour renvoyer la réponse, la requête n’apparaitra très certainement plus dans le slow_query_log.

En recommençant l’opération (toujours avec Explain) sur toutes les requêtes qui se trouvaient dans le fichier de log, j’ai trouvé plusieurs points à améliorer pour accroître les performances du serveur MySQL. Finis, les problèmes de montée en charge !

Pour en savoir plus :
Documentation officielle MySQL de la syntaxe de EXPLAIN

]]>
https://www.php-experts.org/bases-de-donnees/mysql/autopsie-et-optimisation-dune-requete-sql-avec-explain-et-log_slow_queries-14/feed 3