PHP 5.3 : mysqlnd : MySQL Native Driver

14 novembre 2008MySQL, php, php-5.3
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

Les exemples correspondant à ce point se trouvent dans le répertoire “mysqlnd”.

PHP a toujours été fortement associé à MySQL, que ce soit pour des raison historiques, philosophiques, ou pratiques.

PHP 5.3 voit apparaitre une nouveauté au niveau de la communication entre PHP et MySQL : un nouveau driver, nommé « mysqlnd », pour « MySQL Native Driver ».

Sommaire :


Qu’est-ce que mysqlnd ? pourquoi ? comment ?

Initialement prévue pour PHP 6, et finalement rendu disponible dès PHP 5.3, mysqlnd est un nouveau driver permettant de communiquer entre notre code PHP et une base de données MySQL.

mysqlnd en quelques mots

Avant tout, que signifie « mysqlnd » ?
La réponse, déjà donnée plus haut, est la suivante : « MySQL Native Driver ».

J’imagine que « MySQL « et « Driver » n’ont pas réellement besoin d’explication…
… « Native », quand à lui, signifie que mysqlnd est intégré à PHP : c’est une bibliothèque développée en C, comme l’est PHP — et comme l’était libmysql —, mais avec les avantages que peut apporter l’intégration directe à PHP, sans passer par un composant externe.

Il est à noter que mysqlnd n’est ni une nouvelle extension PHP (mysql, mysqli, et pdo_mysql sont des extensions PHP), ni d’une nouvelle API : les API exportées par les trois extensions précédemment citées, et utilisables par les développeurs pour requêter une base de données, ne sont pour ainsi dire pas modifiées !

En somme, mysqlnd est une librairie C, qui fournit globalement les mêmes fonctionnalités que l’ancienne libmysql, utilisée jusqu’à présent comme moyen de communication entre PHP et MySQL.

Le point le plus important pour les développeurs qui travailleront en PHP 5.3, probablement, est la non modification de l’API : pour quelqu’un appelant les fonctions mysql_*, mysqli_*, ou même passant par PDO, absolument rien ne change : mysqlnd se branche en-dessous de ces trois extensions, et n’est pas plus visible pour le développeur que ne l’était libmysql !


Pourquoi mysqlnd, et quels avantages ?

Ce qui vous intéressera le plus probablement est ce qui suit : qu’est-ce que mysqlnd apporte ?

Et donc, pourquoi passer de libmysql à mysqlnd ?

Voici donc une brève liste essayant de répondre à cette question[1] :

  • Avant tout, la compilation de PHP est facilitée : PHP ne dépend plus de libmysql, ce qui signifie qu’il est nécessaire d’installer un composant externe de moins pour parvenir à compiler PHP.
  • La maintenance aussi en est facilitée :
    • mysqlnd sera mise à jour en même temps que PHP, d’une part — un composant de moins à mettre à jour, donc.
    • Et mysqlnd est intégrée à PHP, et utilisée uniquement par PHP : plus de risque pour votre installation de PHP lorsque vous mettez à jour libmysql pour un autre logiciel… Et même chose pour vos autres logiciels lorsque vous mettez à jour libmysql pour PHP, d’ailleurs !
  • Cela compte souvent peu pour les utilisateurs finaux, mais mysqlnd est distribuée sous la même licence que PHP.
  • Intégration au cœur de PHP, facilitant en interne les échanges de données entre le driver et votre application.
  • En conséquence, améliorations passées et futures, par et pour PHP spécifiquement.
  • Pour ne citer que deux de ces améliorations, déjà mises en place :
    • Dans certains cas, améliorations de performance.
    • Dans bon nombre de cas, améliorations au niveau de la mémoire occupée — et quoi qu’il en soit, ce n’est jamais pire qu’avec libmysql ; au pire, autant de mémoire sera consommée, mais pas plus.
      Ce point, en particulier, est réellement dû à l’intégration de mysqlnd directement au sein de PHP : cette intégration permet d’éviter des duplications de données (en utilisant libmysql comme driver, les données devaient être stockées une fois par libmysql, et une fois par PHP ; avec mysqlnd, elle ne sont généralement plus stockées qu’une seule fois pour l’ensemble).
  • Avec mysqlnd, ext/mysqli supporte les connexions persistantes — ce n’était pas le cas auparavant.

Et on peut espérer que cette liste continue à s’allonger au fur et à mesure que de nouvelles versions de PHP et de mysqlnd apparaitront !


Activer mysqlnd ?

L’activation de mysqlnd à la place de libmysql se fait, pour chacune des extensions mysql, mysqli, et pdo_mysql, à la compilation de PHP.
Cf mon article Compilation et installation de PHP 5.3, pour plus de détails à ce sujet ;-)

Je cite l’aide de la commande configure :

  --with-mysql[=DIR]      Include MySQL support.  DIR is the MySQL base
                          directory.  If mysqlnd is passed as DIR,
                          the MySQL native driver will be used [/usr/local]
  --with-mysqli[=FILE]    Include MySQLi support.  FILE is the path
                          to mysql_config.  If mysqlnd is passed as FILE,
                          the MySQL native driver will be used [mysql_config]
  --with-pdo-mysql[=DIR]    PDO: MySQL support. DIR is the MySQL base directoy
                                 If mysqlnd is passed as DIR, the MySQL native
                                 native driver will be used [/usr/local]

Par défaut, si vous ne spécifiez pas explicitement que vous voulez compiler PHP 5.3 en utilisant mysqlnd comme driver pour les accès à des Bases de Données MySQL, ce sera libmysql qui sera utilisée — la bibliothèque utilisée par défaut reste donc la même qu’avec les versions précédentes de PHP.

Mais si vous spécifiez « mysqlnd » pour chacune des trois extensions, vous utiliserez toujours mysqlnd.

Note : Ceci est susceptible de changer d’ici la sortie de la version finale de PHP 5.3 : des discussions sont en cours, pour déterminer s’il faut activer mysqlnd par défaut ou non — et « oui » semble en bonne position face à « non » ^^.


Et pour l’utiliser ?

Une fois PHP compilé en ayant précisé qu’il fallait utiliser mysqlnd comme driver pour chacune des trois extensions (ou pour une, ou pour deux, à vous de choisir ^^ ), vous n’avez plus qu’à continuer à travailler avec les fonctions mysql_*, mysqli_*, ou avec PDO, comme vous le faisiez auparavant : rien de change, pour vous !

Pour vérifier que vous utilisez bien mysqlnd, jetez un coup d’œil à la sortie de phpinfo : un bloc « mysqlnd » devrait être apparu, d’une part ; et « mysqlnd » devrait être indiqué comme driver pour l’extension de vous utilisez — Cf un peu plus bas pour les captures d’écrans.


Préparations pour notre exemple

Pour la suite, je travaillerai sur une base de données MySQL 5.x, avec une table toute simple, qui peut être créée à partir de l’ordre create suivant :

create table villes
(
    code_insee varchar(5) primary key,
    code_postal varchar(5),
    code_departement varchar(2) not null,
    libelle varchar(64) not null,
    latitude float,
    longitude float
);

(Vous trouverez les requêtes d’insertion pour les communes de l’Isère dans le répertoire mysqlnd du fichier joint à cet article — cela suffira pour nos exemples, nous n’avons pas besoin de toutes les villes de France)

Au niveau de la configuration pour accéder à cette base de données, le même genre de choses que ce à quoi nous sommes tous habitués :

<?php
$db_host = '192.168.75.1';
$db_user = 'php';
$db_pass = 'php';
$db_name = 'php';

L’IP du serveur, un nom de base de données, un login et un mot de passe.

Juste par sécurité, pour être absolument certain que le Garbage Collector introduit en PHP 5.3 n’a pas d’effet au niveau de la consommation mémoire de nos exemples, vous pouvez vouloir le désactiver :

if (version_compare(PHP_VERSION, '5.3') > 0) {
    echo '<pre>Desactivation du Garbage Collector de PHP 5.3</pre>';
    gc_disable();    // Pour être sûr que ça ne joue pas en faveur de PHP 5.3
}

Ce n’est pas nécessaire pour les exemples simples que nous verrons ici, mais cela peut éventuellement le devenir si vous souhaitez tester mysqlnd via une application plus lourde, sans que le Garbage Collector ne vienne perturber en bien vos résultats niveau occupation mémoire.


Utiliser mysqlnd depuis du code PHP

Historiquement, PHP dispose de trois extensions, et de trois API, pour accéder à une base de données MySQL :

  • ext/mysql : la plus ancienne (depuis PHP 2 ? ), elle ne supporte pas les fonctionnalités fournies par MySQL >= 4.1 (procédures stockées, prepared statements, …).
    • Cette extension ne devrait plus être utilisée, en particulier pour de nouveaux développements !
    • (Et mysqlnd n’y est pour rien : cela fait des années que ext/mysql est dépassée ! )
  • ext/mysqli : introduite avec PHP 5, cette extension permet d’utiliser les fonctionnalités avancées de MySQL — en particulier, prepared statements, procédures stockées, …
    • Cette extension exporte à la fois une API procédurale et une API Objet.
    • Si vous n’utilisez pas PDO, vous devriez utiliser cette extension, et non ext/mysql !
  • Enfin, PDO, qui a été introduite en PHP 5.1, et dont la raison d’être est de permettre l’accès à plusieurs moteurs de base de données (MySQL via pdo_msql, SQL Server via pdo_dblib, Oracle via pdo_oci, SQLite via pdo_sqlite, …) en utilisant une API commune, facilitant le portage d’une application d’un SGBD à un autre.
    • Si vous visez au développement d’une application pouvant fonctionner sur plusieurs moteurs de base de données, vous utiliserez probablement PDO.
    • (C’est d’ailleurs l’API utilisée par bon nombre de Frameworks PHP en dessous de leurs couches DAO/ORM)


mysql

Historiquement, la première extension — et la première API — disponible pour accéder à une base de données MySQL depuis PHP est l’extension mysql, qui exporte la famille de fonctions mysql_*.

Ici, j’ai compilé PHP en indiquant que je souhaitais que mysqlnd soit utilisé comme driver pour ext/mysql, ce que l’on retrouve dans la portion de phpinfo suivante :

mysqlnd-phpinfo-mysql.png

Connectons-nous à notre base de données :

$conn = mysql_connect($db_host, $db_user, $db_pass)
    or die("Impossible de se connecter : " . mysql_error());
mysql_select_db($db_name)
    or die("Echec select db : " . mysql_error());

Puis effectuons une requête :

$departementSQL = mysql_real_escape_string('38');
$query = "select * from villes where code_departement = '{$departementSQL}' order by libelle";
$result = mysql_query($query);

Affichons le résultat :

if ($result) {
    echo '<pre>';
    while ($ville = mysql_fetch_object($result)) {
        echo htmlentities($ville->code_insee, ENT_COMPAT, 'UTF-8')
            . ' : ' . htmlentities($ville->libelle, ENT_COMPAT, 'UTF-8')
            . ' (' . htmlentities($ville->code_postal, ENT_COMPAT, 'UTF-8') . ')'
            . "\n";
    }
    echo '</pre>';
}

Et déconnectons-nous :

mysql_close($conn);

Rien de particulier à signaler : exécutez cette portion de code sous PHP 5.3 avec mysqlnd, avec PHP 5.3 avec libmysql (implique une recompilation de PHP ^^ ), ou même sous PHP 5.2, voire 4.x si vous y tenez… Le résultat sera identique pour l’utilisateur !

Notez juste que — je me répéte, mais j’y tiens — ext/mysql est complètement dépassée, et ne devrait pas être utilisée pour de nouveaux développements !


mysqli

Passons à l’équivalent utilisant l’extension ext/mysqli, cette fois-ci :

Tout d’abord, vérifions que c’est bien mysqlnd qui est utilisé comme driver :

mysqlnd-phpinfo-mysqli.png

A titre de comparaison, voici ce que nous obtenions en PHP 5.2, en utilisant donc — forcément — libmysql :

libmysql-phpinfo-mysqli.png

Puis connectons-nous à notre base de données :

$conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name)
    or die("Impossible de se connecter : " . mysqli_connect_error());

Lançons une requête :

$departementSQL = mysqli_real_escape_string($conn, '38');
$query = "select * from villes where code_departement = '{$departementSQL}' order by libelle";
$result = mysqli_query($conn, $query);

Affichons le résultat :

if ($result) {
    echo '<pre>';
    while ($ville = $result->fetch_object()) {
        echo htmlentities($ville->code_insee, ENT_COMPAT, 'UTF-8')
            . ' : ' . htmlentities($ville->libelle, ENT_COMPAT, 'UTF-8')
            . ' (' . htmlentities($ville->code_postal, ENT_COMPAT, 'UTF-8') . ')'
            . "\n";
    }
    echo '</pre>';
}

Et déconnectons-nous :

mysqli_close($conn);

Ici encore, rien à signaler : aucune modification, du point de vue de l’utilisateur comme du point de vue du développeur, par rapport à l’utilisation de libmysql en tant que driver.


PDO

Et pour finir, essayons avec PDO, l’API introduite avec PHP 5.1, qui permet d’accéder de manière unifiée à plusieurs SGBD.

Encore une fois, vérifions que nous utilisons bien mysqlnd comme driver :

mysqlnd-phpinfo-pdo.png

Et connectons-nous à notre serveur :

$pdo = new PDO('mysql:dbname=' . $db_name . ';host=' . $db_host, $db_user, $db_pass);

Puis lançons, pour la troisième fois, notre requête de chargement des communes iséroises :

$departementSQL = $pdo->quote('38');
$query = "select * from villes where code_departement = {$departementSQL} order by libelle";
$result = $pdo->query($query, PDO::FETCH_CLASS, 'stdClass');

Pour les afficher :

if ($result) {
    echo '<pre>';
    while ($ville = $result->fetch()) {
        echo htmlentities($ville->code_insee, ENT_COMPAT, 'UTF-8')
            . ' : ' . htmlentities($ville->libelle, ENT_COMPAT, 'UTF-8')
            . ' (' . htmlentities($ville->code_postal, ENT_COMPAT, 'UTF-8') . ')'
            . "\n";
    }
    echo '</pre>';
}

Vous noterez que PDO est une API orientée objet ; le destructeur sera automatiquement appelé à la fin du script, mettant fin à la connexion sans que vous n’ayiez à intervenir.

Pour la troisième fois de suite, je me retrouve sans rien à dire : le comportement avec mysqlnd est le même que celui que nous obtenions avec libmysql…


Pour aller plus loin

Occupation mémoire

Je disais plus haut qu’utiliser mysqlnd peut amener à un gain au niveau de la mémoire consommée par votre script… Pour voir ce qu’il en est sur un exemple simple, recompilons PHP 5.3 sans support de mysqlnd (en utilisant libmysql, donc), et relançons nos trois exemples…

A noter : pour compiler PHP en utilisant libmysql, il vous faudra installer la bibliothèque en question… Sous Ubuntu, par exemple :

apt-get install libmysqlclient15-dev

Puis pour la configuration de compilation, quelque chose de ce genre :

./configure --prefix=/usr/ --with-apxs2=/usr/bin/apxs2 --with-config-file-path=/etc/php/ --disable-ipv6 --with-curl --enable-ftp --with-gd --enable-intl --enable-mbstring --with-mcrypt --with-mhash --with-mysql --with-mysqli --with-pdo-mysql --enable-soap --with-pear --with-xsl --with-zlib --with-openssl

(L’intérêt de tout recompiler de la sorte, plutôt que de tester sur une autre machine — en 5.2 par exemple — est que nous aurons la même configuration, les mêmes extensions chargées, tout pareil… sauf mysqlnd ; nos comparaisons ont donc moins de risques d’être faussées)

Une fois la compilation terminée et la nouvelle version sans mysqlnd installée à la place de l’ancienne, comparons les pics mémoires de nos scripts :

  • PHP 5.3 avec mysqnld :
    • mysql : Pic : 456,772 octets
    • mysqli : Pic : 456,700 octets
    • pdo : Pic : 457,244 octets
  • PHP 5.3 avec libmysql :
    • mysql : Pic : 340,268 octets
    • mysqli : Pic : 340,336 octets
    • pdo : Pic : 340,376 octets

Arrivé là, presque grand moment de solitude :-(
Je disais que mysqlnd permettait des économies de mémoire… Et cet exemple tout simple montre que non, au contraire…

Heureusement, il reste un point à prendre en compte : la mémoire occupée par libmysql n’est pas comptée comme de la mémoire consommée par PHP, puisqu’elle est utilisée par une extension !
Par contre, la mémoire utilisée par mysqlnd, driver intégré à PHP, est comptée dans la mémoire consommée par PHP !
Donc, mon test, basé sur memory_get_peak_usage, est incorrect : il ne compte pas toute la mémoire réellement utilisée…

Ca demande de fouiller un peu plus… Mais l’idée serait de faire un test en CLI, sur un gros jeux de données, et voir la quantité mémoire utilisée par le processus dans sa globalité… A suivre, donc[2] ^^

A noter tout de même : puisque la mémoire occupée par mysqlnd compte dans la memory_limit, deux choses :

  • Peut-être vous faudra-t-il adapter cette directive de configuration ?
  • Vous ne vous retrouverez plus avec un serveur qui s’écroule parce qu’un limit a été oublié dans une requête, et que libmysql monte toutes les données d’une table énorme en mémoire — j’ai déjà vu ce genre de cas, ça ne fait jamais plaisir, surtout que ça arrive généralement trop vite pour être récupérable à temps ^^


Statistiques

Une des nouveautés fournies par mysqnld — et donc, disponible uniquement lorsque c’est ce driver qui est utilisé — est le support de statistiques avancées : utiliser mysqlnd vous permet d’obtenir des informations quant à votre serveur de base de données, au traffic réseau, aux requêtes lentes, …

Ces informations sont rendues disponibles via deux fonctions de l’extension ext/mysqli[3], qui n’existent que si PHP a été compilé avec mysqlnd comme driver !

Pour la liste des informations que vous pouvez obtenir, vous pouvez jeter un œil à la section mysqlnd de phpinfo, dont je reproduis une portion ci-dessous :

mysqlnd-phpinfo-stats.png

Et au niveau de votre application, vous pouvez récupérer ces statistiques à l’aide de la méthode mysqli_get_client_stats, pour obtenir les données correspondant au processus PHP courant :

// Stats par client
$stats = mysqli_get_client_stats();
var_dump($stats);

Et avec la fonction mysqli_get_connection_stats si vous ne souhaitez récupérer les statistiques que pour la connexion courante, que vous utilisez :

// Stats par connexion
$stats = mysqli_get_connection_stats($conn);
var_dump($stats);

Dans un cas comme dans l’autre, vous obtiendrez en sortie un tableau associatif, avec comme clefs les noms de statistiques :

array
  'bytes_sent' => string '705' (length=3)
  'bytes_received' => string '129845' (length=6)
  'packets_sent' => string '14' (length=2)
  'packets_received' => string '2720' (length=4)
  'protocol_overhead_in' => string '10880' (length=5)
  'protocol_overhead_out' => string '56' (length=2)
  'bytes_received_ok_packet' => string '55' (length=2)
  'bytes_received_eof_packet' => string '45' (length=2)
  'bytes_received_rset_header_packet' => string '25' (length=2)
  'bytes_received_rset_field_meta_packet' => string '1840' (length=4)
  'bytes_received_rset_row_packet' => string '127515' (length=6)
  'bytes_received_prepare_response_packet' => string '0' (length=1)
  'bytes_received_change_user_packet' => string '0' (length=1)
  'packets_sent_command' => string '9' (length=1)
  'packets_received_ok' => string '5' (length=1)
  'packets_received_eof' => string '5' (length=1)
  'packets_received_rset_header' => string '5' (length=1)
  'packets_received_rset_field_meta' => string '30' (length=2)
  'packets_received_rset_row' => string '2670' (length=4)
  'packets_received_prepare_response' => string '0' (length=1)
  'packets_received_change_user' => string '0' (length=1)
  'result_set_queries' => string '5' (length=1)
  'non_result_set_queries' => string '0' (length=1)
  'no_index_used' => string '0' (length=1)
  'bad_index_used' => string '0' (length=1)
  'slow_queries' => string '0' (length=1)
  'buffered_sets' => string '5' (length=1)
  'unbuffered_sets' => string '0' (length=1)
  'ps_buffered_sets' => string '0' (length=1)
  'ps_unbuffered_sets' => string '0' (length=1)
  'flushed_normal_sets' => string '0' (length=1)
  'flushed_ps_sets' => string '0' (length=1)
  'ps_prepared_never_executed' => string '0' (length=1)
  'ps_prepared_once_executed' => string '0' (length=1)
  'rows_fetched_from_server_normal' => string '2665' (length=4)
  'rows_fetched_from_server_ps' => string '0' (length=1)
  'rows_buffered_from_client_normal' => string '2665' (length=4)
  'rows_buffered_from_client_ps' => string '0' (length=1)
  'rows_fetched_from_client_normal_buffered' => string '0' (length=1)
  'rows_fetched_from_client_normal_unbuffered' => string '0' (length=1)
  'rows_fetched_from_client_ps_buffered' => string '0' (length=1)
  'rows_fetched_from_client_ps_unbuffered' => string '0' (length=1)
  'rows_fetched_from_client_ps_cursor' => string '0' (length=1)
  'rows_skipped_normal' => string '2665' (length=4)
  'rows_skipped_ps' => string '0' (length=1)
  'copy_on_write_saved' => string '0' (length=1)
  'copy_on_write_performed' => string '0' (length=1)
  'command_buffer_too_small' => string '0' (length=1)
  'connect_success' => string '5' (length=1)
  'connect_failure' => string '0' (length=1)
  'connection_reused' => string '0' (length=1)
  'reconnect' => string '0' (length=1)
  'pconnect_success' => string '0' (length=1)
  'active_connections' => string '1' (length=1)
  'active_persistent_connections' => string '0' (length=1)
  'explicit_close' => string '4' (length=1)
  'implicit_close' => string '0' (length=1)
  'disconnect_close' => string '0' (length=1)
  'in_middle_of_command_close' => string '0' (length=1)
  'explicit_free_result' => string '4' (length=1)
  'implicit_free_result' => string '0' (length=1)
  'explicit_stmt_close' => string '0' (length=1)
  'implicit_stmt_close' => string '0' (length=1)
  'mem_emalloc_count' => string '0' (length=1)
  'mem_emalloc_ammount' => string '0' (length=1)
  'mem_ecalloc_count' => string '0' (length=1)
  'mem_ecalloc_ammount' => string '0' (length=1)
  'mem_erealloc_count' => string '0' (length=1)
  'mem_erealloc_ammount' => string '0' (length=1)
  'mem_efree_count' => string '0' (length=1)
  'mem_malloc_count' => string '0' (length=1)
  'mem_malloc_ammount' => string '0' (length=1)
  'mem_calloc_count' => string '0' (length=1)
  'mem_calloc_ammount' => string '0' (length=1)
  'mem_realloc_count' => string '1335' (length=4)
  'mem_realloc_ammount' => string '0' (length=1)
  'mem_free_count' => string '0' (length=1)
  'proto_text_fetched_null' => string '0' (length=1)
  'proto_text_fetched_bit' => string '0' (length=1)
  'proto_text_fetched_tinyint' => string '0' (length=1)
  'proto_text_fetched_short' => string '0' (length=1)
  'proto_text_fetched_int24' => string '0' (length=1)
  'proto_text_fetched_int' => string '0' (length=1)
  'proto_text_fetched_bigint' => string '0' (length=1)
  'proto_text_fetched_decimal' => string '0' (length=1)
  'proto_text_fetched_float' => string '0' (length=1)
  'proto_text_fetched_double' => string '0' (length=1)
  'proto_text_fetched_date' => string '0' (length=1)
  'proto_text_fetched_year' => string '0' (length=1)
  'proto_text_fetched_time' => string '0' (length=1)
  'proto_text_fetched_datetime' => string '0' (length=1)
  'proto_text_fetched_timestamp' => string '0' (length=1)
  'proto_text_fetched_string' => string '0' (length=1)
  'proto_text_fetched_blob' => string '0' (length=1)
  'proto_text_fetched_enum' => string '0' (length=1)
  'proto_text_fetched_set' => string '0' (length=1)
  'proto_text_fetched_geometry' => string '0' (length=1)
  'proto_text_fetched_other' => string '0' (length=1)
  'proto_binary_fetched_null' => string '0' (length=1)
  'proto_binary_fetched_bit' => string '0' (length=1)
  'proto_binary_fetched_tinyint' => string '0' (length=1)
  'proto_binary_fetched_short' => string '0' (length=1)
  'proto_binary_fetched_int24' => string '0' (length=1)
  'proto_binary_fetched_int' => string '0' (length=1)
  'proto_binary_fetched_bigint' => string '0' (length=1)
  'proto_binary_fetched_decimal' => string '0' (length=1)
  'proto_binary_fetched_float' => string '0' (length=1)
  'proto_binary_fetched_double' => string '0' (length=1)
  'proto_binary_fetched_date' => string '0' (length=1)
  'proto_binary_fetched_year' => string '0' (length=1)
  'proto_binary_fetched_time' => string '0' (length=1)
  'proto_binary_fetched_datetime' => string '0' (length=1)
  'proto_binary_fetched_timestamp' => string '0' (length=1)
  'proto_binary_fetched_string' => string '0' (length=1)
  'proto_binary_fetched_blob' => string '0' (length=1)
  'proto_binary_fetched_enum' => string '0' (length=1)
  'proto_binary_fetched_set' => string '0' (length=1)
  'proto_binary_fetched_geometry' => string '0' (length=1)
  'proto_binary_fetched_other' => string '0' (length=1)

Au moment où j’écris ceci, ce sont 119 valeurs qui sont retournées !
Certaines d’entre-elles n’attirent guère le regard au premier abord… Mais pour quelques unes… %)

Un dernier point à noter : lorsque vous utilisez PHP en tant que module Apache, le même processus sert généralement pour répondre à plusieurs requêtes ; les données obtenues en retour de mysqli_get_client_stats ou à l’affichage de phpinfo peuvent donc parfois sembler se comporter de manière étrange : typiquement, si vous actualisez la page plusieurs fois, les chiffres vont monter… Et d’un coup, ils retomberont à zéro : le processus qui vous servait a été tué par Apache, qui en a lancé un autre…

Vous aurez donc probablement tendance à privilégier l’utilisation de mysqli_get_connection_stats, du moins pour une application Web !


PDO et types PHP natifs

Et maintenant, passons à une nouveauté spécifique à PDO !

Depuis PHP 5.1, vous avez sûrement déjà rencontré ce cas de figure : vous avez en base de données des colonnes de type int ou float, et lorsque vous les récupérez côté PHP, PDO vous les ramène en tant que string au sens PHP.

Par exemple, avec la portion de code suivante :

$pdo = new PDO('mysql:dbname=' . $db_name . ';host=' . $db_host, $db_user, $db_pass);
$codePostalSQL = $pdo->quote('38270');
$query = "select * from villes where code_postal = $codePostalSQL order by libelle";
$result = $pdo->query($query, PDO::FETCH_CLASS, 'stdClass');
if ($result) {
    while ($ville = $result->fetch()) {
        var_dump($ville);
    }
}

(en l’intégrant à l’exemple PDO donné plus haut, typiquement)
Vous obteniez ce genre d’affichage :

object(stdClass)[3]
  public 'code_insee' => string '38032' (length=5)
  public 'code_postal' => string '38270' (length=5)
  public 'code_departement' => string '38' (length=2)
  public 'libelle' => string 'Beaufort' (length=8)
  public 'latitude' => string '45.3599' (length=7)
  public 'longitude' => string '5.03008' (length=7)

Notez que les champs latitude et longitude, stockés en DB sur des float, sont vus par PHP comme des chaines de caractères.

A partir de PHP 5.3, en utilisant mysqlnd comme driver en-dessous de PHP, vous pouvez spécifier un attribut supplémentaire :

$pdo = new PDO('mysql:dbname=' . $db_name . ';host=' . $db_host, $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
$codePostalSQL = $pdo->quote('38270');
$query = "select * from villes where code_postal = $codePostalSQL order by libelle";
$result = $pdo->query($query, PDO::FETCH_CLASS, 'stdClass');
if ($result) {
    while ($ville = $result->fetch()) {
        var_dump($ville);
    }
}

(Notez la seconde ligne)

La sortie deviendra alors la suivante :

object(stdClass)[3]
  public 'code_insee' => string '38032' (length=5)
  public 'code_postal' => string '38270' (length=5)
  public 'code_departement' => string '38' (length=2)
  public 'libelle' => string 'Beaufort' (length=8)
  public 'latitude' => float 45.359931945801
  public 'longitude' => float 5.0300760269165

Les deux champs latitude et longitude sont maintenant correctement par PHP vu comme des flottants !

Notez que pdo_mysql continuera tout de même à retourner des chaines de caractères plutôt que des int lorsque les nombres renvoyés par MySQL sont trop grand pour tenir dans un int.
(Typiquement, pour les nombres supérieurs à 2^32 sur les machines 32 bits)


Quel est l’intérêt, me direz-vous ?

J’en vois deux :

  • Tout d’abord, vous avez des types cohérents dans votre application, ce qui facilite certaines validations de données (Je pense notamment aux cas où l’utilisation de === pouvait être cause d’erreurs ou d’échecs à cause de ce point)
    • OK, je l’admet, ce point précis peut aussi se retourner contre vous, si vous aviez l’habitude que tout remonte de la DB sous forme de chaines de caractères :-(
  • Certains types de données de PHP occupent moins de mémoire que les chaines de caractères. Par exemple, stocker un int en mémoire peut être plus intéressant que de stocker la chaine de caratères correspondant.
    • Un exemple : 12345678 est stocké sur 32 bits (4 octets) ; "12345678" sera stocké sur au minimum 8 octets (plus probablement un octet ou deux pour indiquer la longueur de la chaîne, d’ailleurs)


Quelques liens

Enfin, pour aller plus loin, que ce soit pour suivre les futures évolutions de mysqlnd, ou pour entrer plus en détails dans les fonctionnalités proposées, je vous encourage vivement à consulter le blog de Ulf Wendel, un des développeurs intervenant sur mysqlnd.
(Son blog est, au départ, en allemand — mais les articles à propos de PHP et MySQL sont généralement en anglais)

En particulier, vous pouvez par exemple déjà jeter un œil sur les articles suivants[4] :

Si vous êtes curieux et que vous voulez en savoir plus, je n’ai qu’une chose à vous dire : « Bonne lecture ! »


Notes

[1] Vous noterez que je me suis énormément basé sur l’article PHP: What is mysqlnd, do I need it? de Ulf Wendel pour rédiger cette partie

[2] Je ne garanti rien, mais si j’ai le temps de faire le test, je mettrai cet article à jour

[3] encore une preuve que ext/mysql est dépassée !

[4] Ce n’est qu’une sélection, parmi ceux que j’ai trouvé les plus intéressants pour la rédaction de cet article, mais il y a d’autres ! Naviguez !

Vous avez apprécié cet article ? Faites-le savoir !

Ce blog a récemment été migré vers un générateur de sites statiques et je n'ai pas encore eu le temps de remettre un mécanisme de commentaires en place.

Avec un peu de chance, je parviendrai à m'en occuper d'ici quelques semaines ;-)