MySQL PDO: utilizzare PDO :: ATTR_EMULATE_PREPARES o no?

Questo è quello che ho letto finora su PDO::ATTR_EMULATE_PREPARES :

  1. L'emulazione preparata da PDO è migliore per le performance poiché la preparazione nativa di MySQL ignora la cache delle query .
  2. La preparazione nativa di MySQL è migliore per la sicurezza (impedendo SQL Injection) .
  3. La preparazione nativa di MySQL è migliore per la segnalazione degli errori .

Non so quanto siano vere queste affermazioni. La mia più grande preoccupazione nella scelta di un'interface MySQL è la prevenzione di SQL Injection. La seconda preoccupazione è la prestazione.

La mia applicazione utilizza attualmente procedurale MySQLi (senza istruzioni preparate) e utilizza piuttosto la cache di query. Raramente riutilizzerà le istruzioni preparate in una singola richiesta. Ho iniziato il passaggio a PDO per i parametri denominati e la sicurezza delle istruzioni preparate.

Sto usando MySQL 5.1.61 e PHP 5.3.2

Devo lasciare PDO::ATTR_EMULATE_PREPARES abilitato o no? C'è un modo per avere sia le performance della cache delle query sia la sicurezza delle istruzioni preparate?

Per rispondere alle tue preoccupazioni:

  1. MySQL> = 5.1.17 (o> = 5.1.21 per le istruzioni PREPARE ed EXECUTE ) può utilizzare istruzioni preparate nella cache della query . Quindi la tua versione di MySQL + PHP può usare istruzioni preparate con la cache delle query. Tuttavia, annotare attentamente le avvertenze per i risultati della query nella cache nella documentazione MySQL. Esistono molti tipi di query che non possono essere memorizzati nella cache o che sono inutili anche se sono memorizzati nella cache. Nella mia esperienza, la cache delle query non è spesso una vittoria molto grande comunque. Le query e gli schemi richiedono una costruzione speciale per sfruttare al massimo la cache. Spesso il caching a livello dell'applicazione finisce per essere necessario comunque a lungo termine.

  2. I preparativi nativi non fanno alcuna differenza per la sicurezza. Le istruzioni pseudo-preparate continueranno a sfuggire ai valori dei parametri di query, sarà solo eseguita nella libreria PDO con stringhe anziché sul server MySQL utilizzando il protocollo binario. In altre parole, lo stesso codice PDO sarà ugualmente vulnerabile (o non vulnerabile) agli attacchi per iniezione indipendentemente dall'impostazione di EMULATE_PREPARES . L'unica differenza è where si verifica la sostituzione del parametro – con EMULATE_PREPARES , si verifica nella libreria PDO; senza EMULATE_PREPARES , si verifica sul server MySQL.

  3. Senza EMULATE_PREPARES potresti ricevere errori di syntax in fase di preparazione anziché in fase di esecuzione; con EMULATE_PREPARES riceverai solo errori di syntax al momento dell'esecuzione perché PDO non ha una query da fornire a MySQL fino al momento dell'esecuzione. Nota che questo influenza il codice che scrivi ! Soprattutto se stai usando PDO::ERRMODE_EXCEPTION !

Un'ulteriore considerazione:

  • Esiste un costo fisso per un prepare() (usando istruzioni preparate native), quindi un prepare();execute() con istruzioni native preparate potrebbe essere un po 'più lento dell'emissione di una semplice query testuale usando istruzioni preparate emulate. Su molti sisthemes di database il piano di query per un prepare() viene anche memorizzato nella cache e può essere condiviso con più connessioni, ma non credo che MySQL faccia questo. Pertanto, se non riutilizzi l'object statement preparato per più query, l'esecuzione complessiva potrebbe essere più lenta.

Come ultima raccomandazione , penso con le versioni precedenti di MySQL + PHP, dovresti emulare le istruzioni preparate, ma con le tue versioni molto recenti dovresti distriggersre l'emulazione.

Dopo aver scritto alcune app che usano PDO, ho creato una function di connessione PDO che ha le impostazioni migliori. Probabilmente dovresti usare qualcosa come questo o modificare le tue impostazioni preferite:

 /** * Return PDO handle for a MySQL connection using supplied settings * * Tries to do the right thing with different php and mysql versions. * * @param arrays $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 = arrays_fill_keys(arrays('host', 'port', 'unix_socket', 'dbname', 'charset'), null); $dsnarr = arrays_intersect_key($settings, $dsndefaults); $dsnarr += $dsndefaults; // connection options I like $options = arrays( 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 = arrays(); 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; } 

PDO::ATTR_EMULATE_PREPARES attenzione alla distriggerszione di PDO::ATTR_EMULATE_PREPARES (che PDO::ATTR_EMULATE_PREPARES i preparativi nativi su) quando il tuo PHP pdo_mysql non è compilato su mysqlnd .

Poiché la vecchia libmysql non è completamente compatibile con alcune funzioni, può portre a strani bug, ad esempio:

  1. Perdere i bit più significativi per gli interi a 64 bit quando si PDO::PARAM_INT binding come PDO::PARAM_INT (0x12345678AB verrà ritagliato a 0x345678AB su computer a 64 bit)
  2. Impossibilità di eseguire query semplici come LOCK TABLES (genera SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet exception)
  3. È necessario recuperare tutte le righe dal risultato o chiudere il cursore prima della prossima query (con mysqlnd o emulato lo prepara automaticamente questo funziona per te e non va fuori sincrono con il server mysql)

Questi bug ho capito nel mio semplice progetto quando ho migrato ad un altro server che usava libmysql per il module pdo_mysql . Forse ci sono molti più bug, non lo so. Inoltre ho provato su jessie debian a 64 bit, tutti i bug elencati si verificano quando apro apt-get install php5-mysql e scompaiono quando apt-get install php5-mysqlnd .

Quando PDO::ATTR_EMULATE_PREPARES è impostato su true (come predefinito) – questi errori non avvengono comunque, perché PDO non usa affatto le istruzioni preparate in questa modalità. Quindi, se usi pdo_mysql basato su libmysql (la sottostring "mysqlnd" non appare nel field "Versione API client" della sezione pdo_mysql in phpinfo) – non dovresti distriggersre PDO::ATTR_EMULATE_PREPARES .

Disattiverei emuli i preparativi mentre esegui 5.1, il che significa che PDO sfrutterà la funzionalità di istruzione preparata in modo nativo.

PDO_MYSQL trarrà vantaggio dal supporto di istruzioni nativo preparato presente in MySQL 4.1 e versioni successive. Se stai usando una versione precedente delle librerie client mysql, PDO le emulerà per te.

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

Ho abbandonato MySQLi per PDO per le dichiarazioni con nome preparate e l'API migliore.

Tuttavia, per essere equilibrato, la PDO si comport in modo trascurabile più lentamente di MySQLi, ma è qualcosa da tenere a mente. Lo sapevo quando ho fatto la scelta, e ho deciso che una migliore API e l'utilizzo dello standard del settore era più importnte dell'uso di una libreria trascurabilmente più veloce che ti lega a un particolare motore. FWIW Penso che anche il team di PHP stia guardando favorevolmente a PDO su MySQLi anche per il futuro.

Ti consiglio di abilitare le chiamate PREPARE database reale in quanto l'emulazione non cattura tutto .., ad esempio, preparerà INSERT; !

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

Il risultato

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

Prenderò volentieri un successo in termini di performance per il codice che funziona davvero.

FWIW

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

Versione MySQL: 5.5.34-0ubuntu0