PDO MySQL: utilizzare PDO :: ATTR_EMULATE_PREPARES o no?


117

Questo è ciò di cui ho letto finora PDO::ATTR_EMULATE_PREPARES:

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

Non so più quanto siano vere queste affermazioni. La mia più grande preoccupazione nella scelta di un'interfaccia MySQL è impedire l'iniezione di SQL. La seconda preoccupazione è la performance.

La mia applicazione attualmente utilizza MySQLi procedurale (senza istruzioni preparate) e utilizza abbastanza la cache delle query. Raramente riutilizzerà le dichiarazioni 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.61ePHP 5.3.2

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


3
Onestamente? Continua a usare MySQLi. Se sta già funzionando utilizzando dichiarazioni preparate in base a ciò, PDO è fondamentalmente uno strato di astrazione inutile. EDIT : PDO è davvero utile per le applicazioni green field in cui non sei sicuro di quale database stia andando nel back-end.
jmkeyes

1
Scusa, la mia domanda non era chiara prima. L'ho modificato. L'applicazione non utilizza le istruzioni preparate in MySQLi al momento; solo mysqli_run_query (). Da quello che ho letto, le istruzioni preparate da MySQLi ignorano anche la cache delle query.
Andrew Ensley

Risposte:


108

Per rispondere alle tue preoccupazioni:

  1. MySQL> = 5.1.17 (o> = 5.1.21 per le istruzioni PREPAREe EXECUTE) può utilizzare istruzioni preparate nella cache delle query . Quindi la tua versione di MySQL + PHP può utilizzare istruzioni preparate con la cache delle query. Tuttavia, prendi nota delle avvertenze per la memorizzazione nella cache dei risultati delle query nella documentazione di MySQL. Esistono molti tipi di query che non possono essere memorizzate nella cache o che sono inutili anche se vengono memorizzate nella cache. Nella mia esperienza, la cache delle query non è spesso una grande vittoria comunque. Le query e gli schemi richiedono una costruzione speciale per sfruttare al massimo la cache. Spesso la memorizzazione nella cache a livello di applicazione finisce per essere necessaria comunque a lungo termine.

  2. La preparazione nativa non fa alcuna differenza per la sicurezza. Le istruzioni pseudo-preparate continueranno a sfuggire ai valori dei parametri di query, verrà semplicemente eseguita nella libreria PDO con stringhe invece che sul server MySQL utilizzando il protocollo binario. In altre parole, lo stesso codice PDO sarà ugualmente vulnerabile (o non vulnerabile) agli attacchi di iniezione indipendentemente dalle EMULATE_PREPARESimpostazioni. L'unica differenza è dove avviene la sostituzione dei parametri - con EMULATE_PREPARES, avviene nella libreria PDO; senza EMULATE_PREPARES, si verifica sul server MySQL.

  3. Senza di EMULATE_PREPARESte potresti ottenere errori di sintassi in fase di preparazione piuttosto che in fase di esecuzione; con EMULATE_PREPARESsi otterranno solo errori di sintassi al momento dell'esecuzione perché PDO non ha una query da dare a MySQL fino al momento dell'esecuzione. Nota che questo influisce sul codice che scriverai ! Soprattutto se stai usando PDO::ERRMODE_EXCEPTION!

Un'ulteriore considerazione:

  • C'è un costo fisso per a prepare()(utilizzando istruzioni preparate native), quindi a prepare();execute()con istruzioni preparate native potrebbe essere un po 'più lento rispetto al rilascio di una semplice query testuale utilizzando istruzioni preparate emulate. Su molti sistemi di database anche il piano di query per a prepare()è memorizzato nella cache e può essere condiviso con più connessioni, ma non credo che MySQL lo faccia. Quindi, se non riutilizzi il tuo oggetto istruzione preparato per più query, la tua esecuzione complessiva potrebbe essere più lenta.

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

Dopo aver scritto alcune app che utilizzano PDO, ho creato una funzione di connessione PDO che ha quelle che penso siano le impostazioni migliori. Probabilmente dovresti usare qualcosa di simile 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 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;
}

26
Re # 2: Valori sicuramente che MySQL riceve come parametri (per le istruzioni preparate nativi) non vengono analizzati per SQL a tutti ? Quindi il rischio di iniezione deve essere inferiore rispetto all'utilizzo dell'emulazione di preparazione di PDO, dove qualsiasi difetto nell'escape (es. Problemi storici mysql_real_escape_stringavuti con caratteri multibyte) lascerebbe comunque uno aperto agli attacchi di iniezione?
eggyal

2
@eggyal, stai facendo ipotesi su come vengono implementate le dichiarazioni preparate. PDO potrebbe avere un bug nella sua preparazione emulata per l'escape, ma anche MySQL potrebbe avere bug. AFAIK, non sono stati rilevati problemi con preparazioni emulate che potrebbero causare il passaggio dei valori letterali dei parametri senza caratteri di escape.
Francis Avila

2
Risposta fantastica, ma ho una domanda: se disattivi EMULAZIONE, l'esecuzione non sarà più lenta? PHP dovrebbe inviare l'istruzione preparata a MySQL per la convalida e solo successivamente inviare i parametri. Quindi, se usi l'istruzione preparata 5 volte, PHP parlerà a MySQL 6 volte (invece di 5). Questo non lo renderà più lento? Inoltre, penso che ci siano maggiori possibilità che PDO possa avere bug nel processo di convalida, piuttosto che MySQL ...
Radu Murzea

6
Notare i punti esposti in questa risposta rielaborata l'emulazione dell'istruzione utilizzando mysql_real_escape_stringsotto il cofano e le conseguenti vulnerabilità che possono sorgere (in casi limite molto particolari).
eggyal

6
+1 Buona risposta! Ma per la cronaca, se si utilizza la preparazione nativa, i parametri non vengono mai sottoposti a escape o combinati nella query SQL anche sul lato server MySQL. Quando esegui e fornisci i parametri, la query è stata analizzata e trasformata in strutture dati interne in MySQL. Leggi questo blog da un ingegnere di ottimizzazione MySQL che spiega questo processo: guilhembichot.blogspot.com/2014/05/… Non sto dicendo che questo significa che la preparazione nativa è migliore, nella misura in cui ci fidiamo del codice PDO per eseguire correttamente l'escape (cosa che io fare).
Bill Karwin

9

Fai attenzione alla disabilitazione PDO::ATTR_EMULATE_PREPARES(attivazione della preparazione nativa) quando il tuo PHP pdo_mysqlnon è compilato mysqlnd.

Poiché il vecchio libmysqlnon è completamente compatibile con alcune funzioni, può portare a strani bug, ad esempio:

  1. Perdita dei bit più significativi per interi a 64 bit durante l'associazione come PDO::PARAM_INT(0x12345678AB verrà ritagliato a 0x345678AB su computer a 64 bit)
  2. Incapacità di eseguire query semplici come LOCK TABLES(genera SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yetun'eccezione)
  3. È necessario recuperare tutte le righe dal risultato o chiudere il cursore prima della query successiva (con mysqlndo emulato si prepara automaticamente fa questo lavoro per te e non va fuori sincrono con il server mysql)

Questi bug li ho individuati nel mio semplice progetto durante la migrazione su un altro server utilizzato libmysqlper il pdo_mysqlmodulo. Forse ci sono molti più bug, non lo so. Inoltre ho provato su debian jessie a 64 bit fresco, tutti i bug elencati si verificano quando io apt-get install php5-mysqle scompaiono quando io apt-get install php5-mysqlnd.

Quando PDO::ATTR_EMULATE_PREPARESè impostato su true (come impostazione predefinita) - questi bug non si verificano comunque, perché PDO non utilizza affatto le istruzioni preparate in questa modalità. Quindi, se usi la sottostringa pdo_mysqlbasata su libmysql("mysqlnd" non appare nel campo "Versione API client" della pdo_mysqlsezione in phpinfo) - non dovresti PDO::ATTR_EMULATE_PREPARESspegnerla.


3
questa preoccupazione è ancora valida nel 2019 ?!
oldboy

8

Disattiverei i preparativi di emulazione mentre esegui 5.1, il che significa che PDO trarrà vantaggio dalla funzionalità di istruzione preparata nativa.

PDO_MYSQL sfrutterà il supporto nativo per le istruzioni preparate presente in MySQL 4.1 e versioni successive. Se stai utilizzando 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 nominate preparate e l'API migliore.

Tuttavia, per essere bilanciati, PDO si comporta in modo trascurabilmente più lento di MySQLi, ma è qualcosa da tenere a mente. Lo sapevo quando ho fatto la scelta e ho deciso che un'API migliore e l'utilizzo dello standard del settore erano più importanti rispetto all'utilizzo di una libreria trascurabilmente più veloce che ti lega a un particolare motore. FWIW Penso che il team PHP stia anche guardando favorevolmente a PDO rispetto a MySQLi anche per il futuro.


Grazie per queste informazioni. In che modo il non essere in grado di utilizzare la cache delle query ha influito sulle tue prestazioni o lo stavi persino utilizzando prima?
Andrew Ensley

Non posso comunque dire come framework che sto usando le cache su più livelli. Tuttavia, puoi sempre utilizzare esplicitamente SELECT SQL_CACHE <resto dell'istruzione>.
Will Morgan

Non sapevo nemmeno che esistesse un'opzione SELECT SQL_CACHE. Tuttavia, sembra che ancora non funzionerebbe. Dai documenti: "Il risultato della query viene memorizzato nella cache se è memorizzabile nella cache ..." dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley

Sì. Dipende dalla natura della query, piuttosto che dalle specifiche della piattaforma.
Will Morgan

Ho letto che significa "Il risultato della query viene memorizzato nella cache a meno che qualcos'altro non gli impedisca di essere memorizzato nella cache ", che - da quello che avevo letto fino ad allora - includeva dichiarazioni preparate. Tuttavia, grazie alla risposta di Francis Avila, so che non è più vero per la mia versione di MySQL.
Andrew Ensley

6

Consiglierei di abilitare le PREPAREchiamate al database reali poiché l'emulazione non cattura tutto .., ad esempio, si 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)

Accetterò volentieri un calo delle prestazioni per codice che funziona davvero.

FWIW

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

Versione MySQL: 5.5.34-0ubuntu0


È un punto interessante. Immagino che l'emulazione rimanda l'analisi lato server alla fase di esecuzione. Anche se non è un grosso problema (SQL sbagliato finirà per fallire) è più pulito lasciare che preparefaccia il lavoro che dovrebbe. (Inoltre, ho sempre pensato che il parser dei parametri lato client avrà necessariamente dei bug propri.)
Álvaro González

1
IDK se sei interessato, ma ecco un piccolo articolo su qualche altro comportamento spurio che ho notato con PDO che mi ha portato in questa tana del coniglio per cominciare. Sembra che manchi la gestione di più query.
quickshiftin

Ho appena guardato alcune librerie di migrazioni su GitHub ... Che ne sai, questa fa praticamente la stessa cosa del mio post sul blog.
quickshiftin


5

Perché cambiare l'emulazione su "false"?

La ragione principale di ciò è che avere il motore di database che esegue la preparazione invece di PDO è che la query e i dati effettivi vengono inviati separatamente, il che aumenta la sicurezza. Ciò significa che quando i parametri vengono passati alla query, i tentativi di iniettare SQL in essi vengono bloccati, poiché le istruzioni preparate da MySQL sono limitate a una singola query. Ciò significa che una vera istruzione preparata fallirà se passata una seconda query in un parametro.

L'argomento principale contro l'utilizzo del motore di database per la preparazione e il PDO sono i due viaggi al server - uno per la preparazione e un altro per i parametri da passare - ma penso che la sicurezza aggiuntiva ne valga la pena. Inoltre, almeno nel caso di MySQL, il caching delle query non è stato un problema dalla versione 5.1.

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


1
La cache delle query è comunque scomparsa : la cache delle query è deprecata a partire da MySQL 5.7.20 e viene rimossa in MySQL 8.0.
Álvaro González

0

Per il record

DOP :: ATTR_EMULATE_PREPARES = true

Potrebbe generare un brutto effetto collaterale. Potrebbe restituire valori int come stringa.

PHP 7.4, pdo con mysqlnd.

Esecuzione di una query con PDO :: ATTR_EMULATE_PREPARES = true

Colonna: id
Tipo: intero
Valore: 1

Esecuzione di una query con PDO :: ATTR_EMULATE_PREPARES = false

Colonna: id
Tipo: stringa
Valore: "1"

In ogni caso, ai valori decimali viene sempre restituita una stringa, indipendentemente dalla configurazione :-(


i valori decimali vengono sempre restituiti una stringa è l'unico modo corretto
Il tuo buon senso il

Sì dal punto di vista di MySQL ma è sbagliato dal lato PHP. Sia Java che C # considerano Decimal come valore numerico.
magallanes

No, non lo è. È tutto corretto per l'intera informatica. Se pensi che sia sbagliato, allora hai bisogno di un altro tipo, di precisione arbitraria
Il tuo buon senso il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.