AOP MySQL: Use AOP:: ATTR EMULATE PREPARES or not?

C'est ce que j'ai lu sur PDO::ATTR_EMULATE_PREPARES :

  1. l'émulation prepare de PDO est meilleure pour la performance puisque le natif de MySQL prepare contourne le cache de requête .
  2. le natif de MySQL prépare mieux pour la sécurité (empêchant L'Injection SQL) .
  3. MySQL natif de préparer le mieux pour le rapport d'erreurs .

Je ne sais pas si ces affirmations sont encore vraies. Mon plus grand souci dans le choix d'une interface MySQL est d'empêcher L'Injection SQL. La deuxième préoccupation est la performance.

mon application utilise actuellement le MySQLi procédural (sans les déclarations préparées), et utilise assez le cache de requête. Il est rare qu'elle réutilise des déclarations préparées en une seule demande. J'ai commencé le déplacement vers PDO pour les paramètres nommés et la sécurité de déclarations préparées.

j'utilise MySQL 5.1.61 et PHP 5.3.2

dois-je laisser PDO::ATTR_EMULATE_PREPARES activé ou non? Y a-t-il un moyen d'avoir à la fois la performance du cache de requête et la sécurité des déclarations préparées?

99
demandé sur Community 2012-04-12 00:37:33

5 réponses

pour répondre à vos préoccupations:

  1. MySQL >= 5.1.17 (ou >= 5.1.21 pour les déclarations PREPARE et EXECUTE ) peut utiliser des déclarations préparées dans le cache de requête . Ainsi, votre version de MySQL + PHP peut utiliser des déclarations préparées avec le cache de requête. Cependant, notez attentivement les mises en garde concernant la mise en cache des résultats des requêtes dans la documentation MySQL. Il existe de nombreux types de requêtes qui ne peuvent pas être mises en cache ou qui sont inutile, même si elles sont mises en cache. D'après mon expérience, le cache de requête n'est pas souvent une grande victoire de toute façon. Les requêtes et les schémas ont besoin d'une construction spéciale pour utiliser 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 déclarations pseudo-préparées échapperont toujours aux valeurs des paramètres de requête, elles seront simplement faites dans la bibliothèque PDO avec des chaînes de caractères au lieu de sur le serveur MySQL en utilisant le protocole binaire. En d'autres termes, le même code AOP sera tout aussi vulnérable (ou non-vulnérable) aux attaques par injection quel que soit votre réglage EMULATE_PREPARES . La seule différence est lorsque le remplacement du paramètre se produit--avec EMULATE_PREPARES , il se produit dans la bibliothèque AOP; sans EMULATE_PREPARES , il se produit sur le serveur MySQL.

  3. sans EMULATE_PREPARES vous pourriez avoir des erreurs de syntaxe au moment de la préparation plutôt qu'au moment de l'exécution; avec EMULATE_PREPARES vous n'obtiendrez des erreurs de syntaxe qu'au moment de l'exécution parce que PDO n'a pas de requête à donner à MySQL jusqu'au moment de l'exécution. Notez que affecte le code que vous écrirez ! Surtout si vous utilisez PDO::ERRMODE_EXCEPTION !

une contrepartie supplémentaire:

  • il y a un coût fixe pour un prepare() (en utilisant des déclarations préparées par les autochtones), donc un prepare();execute() avec des instructions natives préparées peut être un peu plus lent que l'émission d'une requête textuelle simple en utilisant des instructions préparées émulées. Sur de nombreux systèmes de bases de données, le plan de requête pour un prepare() est mis en cache et peut être partagé avec plusieurs connexions, mais je ne pense pas que MySQL fasse cela. Ainsi, si vous ne réutilisez pas votre objet de déclaration préparé pour des requêtes multiples, votre exécution globale peut être plus lente.

en tant que recommandation finale , je pense qu'avec les versions plus anciennes de MySQL+PHP, vous devriez émuler les déclarations préparées, mais avec vos versions très récentes vous devriez désactiver l'émulation.

après avoir écrit quelques applications qui utilisent PDO, j'ai créé une fonction de connexion PDO qui a ce que je pense être les meilleurs paramètres. Vous devriez 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;
}
97
répondu Francis Avila 2014-12-29 08:44:50

méfiez-vous de désactiver PDO::ATTR_EMULATE_PREPARES lorsque votre PHP pdo_mysql n'est pas compilé contre mysqlnd .

parce que l'ancien libmysql n'est pas entièrement compatible avec certaines fonctions, il peut conduire à des bogues étranges, par exemple:

  1. perdre les bits les plus significatifs pour les entiers 64bit lors de la liaison comme PDO::PARAM_INT ( 0x12345678AB sera recadré à 0x345678AB sur la machine 64bit )
  2. incapacité à faire des requêtes simples comme LOCK TABLES (il lance SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet exception)
  3. besoin de récupérer toutes les lignes à partir du résultat ou fermer le curseur avant la prochaine requête (avec mysqlnd ou émulé le prépare automatiquement fait ce travail pour vous et ne se désynchronise pas avec mysql Serveur)

ces bogues que j'ai compris dans mon projet simple lors de la migration vers un autre serveur qui a utilisé libmysql pour pdo_mysql module. Peut-être il ya beaucoup plus de bugs, je ne sais pas. J'ai aussi testé sur fresh 64bit debian jessie, tous les bogues listés se produisent quand je apt-get install php5-mysql , et disparaissent quand je apt-get install php5-mysqlnd .

quand PDO::ATTR_EMULATE_PREPARES est défini à true (par défaut) - ces bogues ne se produisent pas de toute façon, parce que PDO n'utilise pas du tout de déclarations préparées dans ce mode. Ainsi, si vous utilisez pdo_mysql basé sur libmysql (le substrat "mysqlnd" n'apparaît pas dans le champ "Version API Client" de la section pdo_mysql phpinfo) - vous ne devez pas éteindre PDO::ATTR_EMULATE_PREPARES .

9
répondu Sage Pointer 2016-09-29 19:42:45

Je désactiverais emulate prepare car vous lancez 5.1 ce qui signifie que PDO va profiter de la fonctionnalité native prepared statement.

PDO_MYSQL tirera parti du support de déclaration natif préparé et présent dans MySQL 4.1 et supérieur. Si vous utilisez une ancienne version des bibliothèques client mysql, PDO les imitera pour vous.

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

j'ai abandonné MySQLi pour PDO pour les déclarations nommées préparées et la meilleure API.

cependant, pour être équilibré, PDO se comporte négligemment plus lentement que MySQLi, mais c'est quelque chose à garder à l'esprit. Je le savais lorsque j'ai fait ce choix, et j'ai décidé qu'une meilleure API et l'utilisation de la norme de l'industrie étaient plus importantes que l'utilisation d'une bibliothèque beaucoup plus rapide qui vous lie à un moteur particulier. FWIW I think the PHP team is also lookingful chez AOP au-dessus de MySQLi pour l'avenir aussi.

6
répondu Will Morgan 2012-05-04 09:06:32

je recommande d'activer les appels de la base de données PREPARE car l'émulation ne capte pas tout.. par exemple, il va préparer INSERT; !

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

La sortie

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

je vais volontiers prendre un coup de performance pour le code qui fonctionne réellement.

FWIW

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

Version MySQL: 5.5.34-0ubuntu0

4
répondu quickshiftin 2014-07-15 05:35:49

pourquoi changer l'émulation en "faux"? La raison principale pour cela est que le moteur de base de données fait la préparation au lieu de PDO est que la requête et les données réelles sont envoyées séparément, ce qui augmente la sécurité. Cela signifie que lorsque les paramètres sont passés à la requête, les tentatives pour y injecter SQL sont bloquées, puisque les instructions préparées par MySQL sont limitées à une seule requête. Cela signifie qu'une instruction true prepared échouerait quand on passe une seconde requête dans un paramètre.

le principal argument contre l'utilisation du moteur de base de données pour le prepare vs AOP est les deux voyages vers le serveur – un pour le prepare, et un autre pour les paramètres à passer – mais je pense que la sécurité ajoutée vaut la peine. En outre, au moins dans le cas de MySQL, la mise en cache des requêtes n'a pas été un problème depuis la version 5.1.

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

1
répondu Harry Bosh 2018-06-15 01:10:11