web-dev-qa-db-fra.com

PDO MySQL: utilisez PDO :: ATTR_EMULATE_PREPARES ou non?

C’est ce que j’ai lu jusqu’à présent à propos de PDO::ATTR_EMULATE_PREPARES :

  1. L'émulation de préparation de PDO est meilleure pour les performances puisque la préparation native de MySQL contourne le cache de requêtes .
  2. La préparation native de MySQL est meilleure pour la sécurité (empêchant l'injection SQL) .
  3. La préparation native de MySQL est préférable pour signaler les erreurs .

Je ne sais plus à quel point ces affirmations sont vraies. Ma plus grande préoccupation en choisissant une interface MySQL est d'empêcher l'injection SQL. La deuxième préoccupation est la performance.

Mon application utilise actuellement la procédure MySQLi procédurale (sans instructions préparées) et utilise un peu le cache de requêtes. Il est rare que des instructions préparées soient réutilisées dans une seule demande. J'ai commencé le passage à PDO pour les paramètres nommés et la sécurité des instructions préparées.

J'utilise MySQL 5.1.61 et PHP 5.3.2

Devrais-je partir PDO::ATTR_EMULATE_PREPARES activé ou non? Existe-t-il un moyen d’avoir à la fois les performances du cache de requêtes et la sécurité des instructions préparées?

111
Andrew Ensley

Pour répondre à vos préoccupations:

  1. MySQL> = 5.1.17 (ou> = 5.1.21 pour les instructions PREPARE et EXECUTE) peut utiliser des instructions préparées dans le cache de requêtes . Ainsi, votre version de MySQL + PHP peut utiliser des instructions préparées avec le cache de requêtes. Cependant, notez bien les mises en garde relatives à la mise en cache des résultats de requête dans la documentation MySQL. Il existe de nombreux types de requêtes qui ne peuvent pas être mises en cache ou qui sont inutiles même si elles sont mises en cache. D'après mon expérience, le cache de requêtes n'est pas souvent une très grosse victoire. Les requêtes et les schémas nécessitent une construction spéciale pour exploiter au maximum le cache. Souvent, la mise en cache au niveau de l'application finit par être nécessaire à long terme.

  2. Native prépare ne fait aucune différence pour la sécurité. Les instructions pseudo-préparées échapperont toujours aux valeurs des paramètres de requête. Elles seront simplement effectuées dans la bibliothèque PDO avec des chaînes plutôt que sur le serveur MySQL utilisant le protocole binaire. En d'autres termes, le même code PDO sera également vulnérable (ou non vulnérable) aux attaques par injection, quel que soit votre paramètre EMULATE_PREPARES. La seule différence réside dans le remplacement du paramètre - avec EMULATE_PREPARES, Il se produit dans la bibliothèque PDO; sans EMULATE_PREPARES, cela se produit sur le serveur MySQL.

  3. Sans EMULATE_PREPARES, Vous risquez d'obtenir des erreurs de syntaxe au moment de la préparation plutôt qu'à l'exécution; avec EMULATE_PREPARES vous n'obtiendrez que des erreurs de syntaxe au moment de l'exécution car PDO n'a pas de requête à donner à MySQL jusqu'au moment de l'exécution. Notez que ceci affecte le code que vous allez écrire! Surtout si vous utilisez PDO::ERRMODE_EXCEPTION!

Une considération supplémentaire:

  • Il y a un coût fixe pour une prepare() (utilisant des instructions préparées natives). Par conséquent, une prepare();execute() avec des instructions préparées natives peut être un peu plus lente que d'émettre une requête textuelle brute à l'aide d'instructions préparées émulées. Sur de nombreux systèmes de base de données, le plan de requête pour une prepare() est également mis en cache et peut être partagé avec plusieurs connexions, mais je ne pense pas que MySQL le fasse. Par conséquent, si vous ne réutilisez pas votre objet instruction préparé pour plusieurs requêtes, l'exécution globale risque d'être plus lente.

En guise de recommandation finale , je pense qu'avec les anciennes versions de MySQL + PHP, vous devriez émuler les instructions préparées, mais avec vos versions les plus récentes, vous devriez désactiver l'émulation. .

Après avoir écrit quelques applications utilisant PDO, j'ai créé une fonction de connexion PDO qui, selon moi, constitue les meilleurs paramètres. Vous devriez probablement utiliser quelque chose comme ceci ou modifier vos paramètres préférés:

/**
 * Return PDO handle for a MySQL connection using supplied settings
 *
 * Tries to do the right thing with different php and mysql versions.
 *
 * @param array $settings with keys: Host, port, unix_socket, dbname, charset, user, pass. Some may be omitted or NULL.
 * @return PDO
 * @author Francis Avila
 */
function connect_PDO($settings)
{
    $emulate_prepares_below_version = '5.1.17';

    $dsndefaults = array_fill_keys(array('Host', 'port', 'unix_socket', 'dbname', 'charset'), null);
    $dsnarr = array_intersect_key($settings, $dsndefaults);
    $dsnarr += $dsndefaults;

    // connection options I like
    $options = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    );

    // connection charset handling for old php versions
    if ($dsnarr['charset'] and version_compare(PHP_VERSION, '5.3.6', '<')) {
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$dsnarr['charset'];
    }
    $dsnpairs = array();
    foreach ($dsnarr as $k => $v) {
        if ($v===null) continue;
        $dsnpairs[] = "{$k}={$v}";
    }

    $dsn = 'mysql:'.implode(';', $dsnpairs);
    $dbh = new PDO($dsn, $settings['user'], $settings['pass'], $options);

    // Set prepared statement emulation depending on server version
    $serverversion = $dbh->getAttribute(PDO::ATTR_SERVER_VERSION);
    $emulate_prepares = (version_compare($serverversion, $emulate_prepares_below_version, '<'));
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate_prepares);

    return $dbh;
}
104
Francis Avila

Attention à la désactivation PDO::ATTR_EMULATE_PREPARES (lorsque la préparation native est activée) lorsque votre PHP pdo_mysql n'est pas compilé avec mysqlnd.

Parce que l'ancien libmysql n'est pas totalement compatible avec certaines fonctions, cela peut entraîner des bugs étranges, par exemple:

  1. Perte des bits de poids fort pour les entiers 64 bits lors de la liaison en tant que PDO::PARAM_INT (0x12345678AB sera recadré en 0x345678AB sur un ordinateur 64 bits)
  2. Incapacité de faire des requêtes simples comme LOCK TABLES (il jette SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet exception)
  3. Nécessité d'extraire toutes les lignes du résultat ou de fermer le curseur avant la requête suivante (avec mysqlnd ou l'émulé le prépare automatiquement, cela fonctionne pour vous et ne se désynchronise pas avec le serveur mysql)

Ces bogues que j'ai découverts dans mon projet simple lors de la migration vers un autre serveur utilisant libmysql pour pdo_mysql module. Peut-être qu'il y a beaucoup plus de bugs, je ne sais pas. J'ai aussi testé sur une nouvelle Debian Jessie 64bit, tous les bugs listés se produisent quand je apt-get install php5-mysql, et disparais quand je apt-get install php5-mysqlnd.

Quand PDO::ATTR_EMULATE_PREPARES est défini sur true (par défaut) - ces bogues ne se produisent pas, car PDO n'utilise pas d'instructions préparées dans ce mode. Donc, si vous utilisez pdo_mysql basé sur libmysql (la chaîne "mysqlnd" n'apparaît pas dans le champ "Version de l'API client" de pdo_mysql section dans phpinfo) - vous ne devriez pas tourner PDO::ATTR_EMULATE_PREPARES off.

10
Sage Pointer

Je voudrais désactiver les préparations d’émulation lors de l’exécution de la version 5.1, ce qui signifie que PDO tirera parti de la fonctionnalité d’instruction préparée native.

PDO_MYSQL tirera parti du support natif des instructions préparées présent dans MySQL version 4.1 et ultérieure. Si vous utilisez une version plus ancienne des bibliothèques clientes mysql, PDO les imitera pour vous.

http://php.net/manual/en/ref.pdo-mysql.php

J'ai abandonné MySQLi pour PDO pour les instructions nommées préparées et la meilleure API.

Cependant, pour être équilibré, PDO fonctionne de manière négligeable par rapport à MySQLi, mais il faut en tenir compte. Je le savais quand j'ai fait mon choix et j'ai décidé qu'une meilleure API et l'utilisation du standard de l'industrie étaient plus importants que l'utilisation d'une bibliothèque extrêmement rapide qui vous lie à un moteur particulier. FWIW Je pense que l’équipe PHP envisage également l’avenir de PDO par rapport à MySQLi.

8
Will Morgan

Je recommanderais d'activer les vrais appels de base de données PREPARE car l'émulation ne capture pas tout. Par exemple, elle préparera INSERT;!

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));

Le résultat

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(7) "INSERT;"
}
bool(false)

Je prendrai volontiers un hit de performance pour un code qui fonctionne réellement.

FWIW

Version PHP: PHP 5.4.9-4ubuntu2.4 (cli)

Version MySQL: 5.5.34-0ubuntu0

5
quickshiftin

Pourquoi changer l’émulation en "faux"?

La raison principale en est que la préparation du moteur de base de données à la place du PDO est due au fait que la requête et les données réelles sont envoyées séparément, ce qui renforce la sécurité. Cela signifie que lorsque les paramètres sont passés à la requête, les tentatives d’injection de code SQL y sont bloquées, car les instructions préparées par MySQL sont limitées à une seule requête. Cela signifie qu'une véritable instruction préparée échouerait si une deuxième requête dans un paramètre était transmise.

Le principal argument contre l'utilisation du moteur de base de données pour la préparation vs PDO réside dans les deux allers-retours au serveur - un pour la préparation et un autre pour les paramètres à transmettre - mais je pense que la sécurité supplémentaire en vaut la peine. De plus, du moins dans le cas de MySQL, la mise en cache des requêtes n’est plus un problème depuis la version 5.1.

https://tech.michaelseiler.net/2016/07/04/dont-emulate-prepared-statements-pdo-mysql/

4
Harry Bosh