PDO MySQL: Verwenden Sie PDO :: ATTR_EMULATE_PREPARES oder nicht?

117

Folgendes habe ich bisher gelesen PDO::ATTR_EMULATE_PREPARES:

  1. Die Vorbereitungsemulation von PDO ist für die Leistung besser, da die native Vorbereitung von MySQL den Abfragecache umgeht .
  2. Die native Vorbereitung von MySQL ist aus Sicherheitsgründen besser (verhindert SQL Injection) .
  3. Die native Vorbereitung von MySQL eignet sich besser für die Fehlerberichterstattung .

Ich weiß nicht mehr, wie wahr eine dieser Aussagen ist. Mein größtes Anliegen bei der Auswahl einer MySQL-Schnittstelle ist die Verhinderung von SQL Injection. Das zweite Problem ist die Leistung.

Meine Anwendung verwendet derzeit prozedurales MySQLi (ohne vorbereitete Anweisungen) und verwendet den Abfrage-Cache ziemlich oft. Vorbereitete Anweisungen werden selten in einer einzigen Anforderung wiederverwendet. Ich habe die Umstellung auf PDO für die genannten Parameter und die Sicherheit der vorbereiteten Anweisungen gestartet.

Ich benutze MySQL 5.1.61undPHP 5.3.2

Soll ich PDO::ATTR_EMULATE_PREPARESaktiviert lassen oder nicht? Gibt es eine Möglichkeit, sowohl die Leistung des Abfragecaches als auch die Sicherheit vorbereiteter Anweisungen zu gewährleisten?

Andrew Ensley
quelle
3
Ehrlich? Verwenden Sie einfach weiterhin MySQLi. Wenn es bereits mit vorbereiteten Anweisungen funktioniert, ist PDO im Grunde eine sinnlose Abstraktionsebene. BEARBEITEN : PDO ist sehr nützlich für Green-Field-Anwendungen, bei denen Sie nicht sicher sind, welche Datenbank in das Back-End aufgenommen wird.
jmkeyes
1
Entschuldigung, meine Frage war vorher unklar. Ich habe es bearbeitet. Die Anwendung verwendet derzeit keine vorbereiteten Anweisungen in MySQLi. nur mysqli_run_query (). Nach dem, was ich gelesen habe, umgehen von MySQLi vorbereitete Anweisungen auch den Abfrage-Cache.
Andrew Ensley

Antworten:

108

Um Ihre Bedenken zu beantworten:

  1. MySQL> = 5.1.17 (oder> = 5.1.21 für die Anweisungen PREPAREund EXECUTE) kann vorbereitete Anweisungen im Abfragecache verwenden . Ihre Version von MySQL + PHP kann also vorbereitete Anweisungen mit dem Abfragecache verwenden. Beachten Sie jedoch die Einschränkungen beim Zwischenspeichern von Abfrageergebnissen in der MySQL-Dokumentation. Es gibt viele Arten von Abfragen, die nicht zwischengespeichert werden können oder die nutzlos sind, obwohl sie zwischengespeichert sind. Nach meiner Erfahrung ist der Abfrage-Cache sowieso nicht oft ein großer Gewinn. Abfragen und Schemata erfordern eine spezielle Konstruktion, um den Cache maximal zu nutzen. Oft ist Caching auf Anwendungsebene auf lange Sicht ohnehin notwendig.

  2. Native Vorbereitungen machen keinen Unterschied für die Sicherheit. Die pseudo-vorbereiteten Anweisungen entziehen sich weiterhin den Abfrageparameterwerten. Sie werden nur in der PDO-Bibliothek mit Zeichenfolgen anstatt auf dem MySQL-Server unter Verwendung des Binärprotokolls ausgeführt. Mit anderen Worten, derselbe PDO-Code ist unabhängig von Ihrer EMULATE_PREPARESEinstellung gleichermaßen anfällig (oder nicht anfällig) für Injektionsangriffe . Der einzige Unterschied besteht darin, wo die Parameterersetzung erfolgt - mit EMULATE_PREPARES, in der PDO-Bibliothek; ohne EMULATE_PREPAREStritt es auf dem MySQL-Server auf.

  3. Ohne EMULATE_PREPARESkönnen Syntaxfehler eher zur Vorbereitungszeit als zur Ausführungszeit auftreten. Mit erhalten EMULATE_PREPARESSie nur zur Ausführungszeit Syntaxfehler, da PDO bis zur Ausführungszeit keine Abfrage an MySQL senden muss. Beachten Sie, dass dies Auswirkungen auf den Code hat, den Sie schreiben werden ! Besonders wenn Sie verwenden PDO::ERRMODE_EXCEPTION!

Eine zusätzliche Überlegung:

  • Es gibt feste Kosten für a prepare()(unter Verwendung nativer vorbereiteter Anweisungen), daher kann a prepare();execute()mit nativen vorbereiteten Anweisungen etwas langsamer sein als die Ausgabe einer einfachen Textabfrage unter Verwendung emulierter vorbereiteter Anweisungen. Auf vielen Datenbanksystemen wird der Abfrageplan für a ebenfalls prepare()zwischengespeichert und kann für mehrere Verbindungen freigegeben werden, aber ich glaube nicht, dass MySQL dies tut. Wenn Sie Ihr vorbereitetes Anweisungsobjekt also nicht für mehrere Abfragen wiederverwenden, ist Ihre Gesamtausführung möglicherweise langsamer.

Als letzte Empfehlung denke ich, dass Sie mit älteren Versionen von MySQL + PHP vorbereitete Anweisungen emulieren sollten, aber mit Ihren neuesten Versionen sollten Sie die Emulation deaktivieren.

Nachdem ich einige Apps geschrieben habe, die PDO verwenden, habe ich eine PDO-Verbindungsfunktion erstellt, die meiner Meinung nach die besten Einstellungen aufweist. Sie sollten wahrscheinlich so etwas verwenden oder Ihre bevorzugten Einstellungen anpassen:

/**
 * 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;
}
Francis Avila
quelle
26
Re # 2: sicherlich Werte , die MySQL erhält als Parameter (auf native Prepared Statements) nicht für SQL analysiert bekommen überhaupt ? Das Injektionsrisiko muss also geringer sein als bei der Verwendung der PDO-Vorbereitungsemulation, bei der ein Escape-Fehler (z. B. die historischen Probleme mysql_real_escape_stringmit Multi-Byte-Zeichen) einen für Injektionsangriffe offen lässt.
Eggyal
2
@eggyal, Sie machen Annahmen darüber, wie vorbereitete Anweisungen implementiert werden. PDO hat möglicherweise einen Fehler in den emulierten Vorbereitungen, aber MySQL hat möglicherweise auch Fehler. AFAIK, es wurden keine Probleme mit emulierten Präparaten entdeckt, die dazu führen könnten, dass Parameterliterale ungehindert durchlaufen werden.
Francis Avila
2
Tolle Antwort, aber ich habe eine Frage: Wenn Sie EMULATION ausschalten, wird die Ausführung dann nicht langsamer sein? PHP müsste die vorbereitete Anweisung zur Validierung an MySQL senden und erst dann die Parameter senden. Wenn Sie die vorbereitete Anweisung also fünfmal verwenden, spricht PHP sechsmal mit MySQL (statt fünfmal). Wird es das nicht langsamer machen? Außerdem denke ich, dass es eine größere Chance gibt, dass PDO Fehler im Validierungsprozess haben könnte, als MySQL ...
Radu Murzea
6
Beachten Sie die in dieser Antwort gemachten Punkte zur vorbereiteten Anweisungsemulation mysql_real_escape_stringunter der Haube und die daraus resultierenden Schwachstellen, die auftreten können (in ganz bestimmten Randfällen).
Eggyal
6
+1 Gute Antwort! Wenn Sie jedoch native Vorbereitung verwenden, werden Parameter selbst auf der MySQL-Serverseite niemals maskiert oder in der SQL-Abfrage kombiniert. Zum Zeitpunkt der Ausführung und Angabe von Parametern wurde die Abfrage analysiert und in interne Datenstrukturen in MySQL umgewandelt. Lesen Sie diesen Blog von einem MySQL-Optimierer, der diesen Prozess erklärt: guilhembichot.blogspot.com/2014/05/… Ich sage nicht, dass dies bedeutet, dass die native Vorbereitung besser ist, sofern wir darauf vertrauen, dass der PDO-Code korrekt maskiert (was ich auch tue) machen).
Bill Karwin
9

Achten Sie beim Deaktivieren PDO::ATTR_EMULATE_PREPARES(Aktivieren der nativen Vorbereitungen) darauf, dass Ihr PHP pdo_mysqlnicht kompiliert wird mysqlnd.

Da old libmysqlmit einigen Funktionen nicht vollständig kompatibel ist, kann es zu seltsamen Fehlern kommen, zum Beispiel:

  1. Verlust der wichtigsten Bits für 64-Bit-Ganzzahlen beim Binden als PDO::PARAM_INT(0x12345678AB wird auf einem 64-Bit-Computer auf 0x345678AB zugeschnitten)
  2. Unfähigkeit, einfache Abfragen wie zu machen LOCK TABLES(es löst eine SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yetAusnahme aus)
  3. Sie müssen vor der nächsten Abfrage alle Zeilen aus dem Ergebnis abrufen oder den Cursor schließen (mit mysqlndoder emuliert wird dies automatisch für Sie erledigt und nicht nicht mehr mit dem MySQL-Server synchronisiert).

Diese Fehler habe ich in meinem einfachen Projekt herausgefunden, als ich auf einen anderen Server migriert habe, der libmysqlfür das pdo_mysqlModul verwendet wurde. Vielleicht gibt es noch viel mehr Fehler, ich weiß es nicht. Auch ich habe auf frischem 64bit Debian Jessie getestet, alle aufgelisteten Bugs treten auf, wenn ich apt-get install php5-mysql, und verschwinden, wenn ich apt-get install php5-mysqlnd.

Wann PDO::ATTR_EMULATE_PREPARESist auf true gesetzt (standardmäßig) - diese Fehler treten ohnehin nicht auf, da PDO in diesem Modus überhaupt keine vorbereiteten Anweisungen verwendet. Wenn Sie also pdo_mysqlbasierend auf verwenden libmysql("mysqlnd" Teilzeichenfolge erscheint nicht im Feld "Client API Version" des pdo_mysqlAbschnitts in phpinfo) - sollten Sie nicht deaktivieren PDO::ATTR_EMULATE_PREPARES.

Salbei Zeiger
quelle
3
Ist dieses Anliegen 2019 noch gültig?!
Oldboy
8

Ich würde Emulate-Vorbereitungen deaktivieren, während Sie 5.1 ausführen, was bedeutet, dass PDO die native Funktionalität für vorbereitete Anweisungen nutzt.

PDO_MYSQL nutzt die native Unterstützung für vorbereitete Anweisungen in MySQL 4.1 und höher. Wenn Sie eine ältere Version der MySQL-Client-Bibliotheken verwenden, emuliert PDO diese für Sie.

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

Ich habe MySQLi für PDO wegen der vorbereiteten benannten Anweisungen und der besseren API über Bord geworfen.

Um ausgeglichen zu sein, arbeitet PDO vernachlässigbar langsamer als MySQLi, aber es ist etwas zu beachten. Ich wusste das, als ich die Wahl traf, und entschied, dass eine bessere API und die Verwendung des Industriestandards wichtiger sind als die Verwendung einer vernachlässigbar schnelleren Bibliothek, die Sie an eine bestimmte Engine bindet. FWIW Ich denke, das PHP-Team sieht PDO gegenüber MySQLi auch für die Zukunft positiv.

Will Morgan
quelle
Danke für diese Information. Wie hat sich die Nichtverwendung des Abfragecaches auf Ihre Leistung ausgewirkt oder haben Sie ihn bereits verwendet?
Andrew Ensley
Ich kann nicht sagen, dass ich als Framework Caches auf mehreren Ebenen verwende. Sie können jedoch immer explizit SELECT SQL_CACHE <Rest der Anweisung> verwenden.
Will Morgan
Wusste nicht einmal, dass es eine SELECT SQL_CACHE-Option gibt. Es scheint jedoch, dass das immer noch nicht funktionieren würde. Aus den Dokumenten: "Das Abfrageergebnis wird zwischengespeichert, wenn es zwischengespeichert werden kann ..." dev.mysql.com/doc/refman/5.1/de/query-cache-in-select.html
Andrew Ensley
Ja. Dies hängt eher von der Art der Abfrage als von den Plattformspezifikationen ab.
Will Morgan
Ich habe das so gelesen, dass es bedeutet: "Das Abfrageergebnis wird zwischengespeichert, es sei denn, etwas anderes verhindert, dass es zwischengespeichert werden kann", was - nach dem, was ich bis dahin gelesen hatte - vorbereitete Anweisungen enthielt. Dank der Antwort von Francis Avila weiß ich jedoch, dass dies für meine Version von MySQL nicht mehr gilt.
Andrew Ensley
6

Ich würde empfehlen, echte Datenbankaufrufe zu aktivieren, PREPAREda die Emulation nicht alles abfängt. Zum Beispiel wird sie vorbereitet INSERT;!

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

Die Ausgabe

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

Ich nehme gerne einen Performance-Hit für Code, der tatsächlich funktioniert.

FWIW

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

MySQL Version: 5.5.34-0ubuntu0

Quickshiftin
quelle
Das ist ein interessanter Punkt. Ich denke, die Emulation verschiebt das serverseitige Parsen auf die Ausführungsphase. Es ist zwar keine große Sache (falsches SQL wird irgendwann scheitern), aber es ist sauberer, prepareden Job machen zu lassen , den es soll. (Außerdem habe ich immer angenommen, dass der clientseitige Parameterparser notwendigerweise eigene Fehler aufweist.)
Álvaro González
1
IDK, wenn Sie interessiert sind, aber hier ist ein kleiner Bericht über ein anderes falsches Verhalten, das ich bei PDO bemerkt habe und das mich zunächst in dieses Kaninchenloch geführt hat. Es scheint, dass die Bearbeitung mehrerer Abfragen fehlt.
Quickshiftin
Ich habe mir gerade einige Migrationsbibliotheken auf GitHub angesehen ... Was weißt du, diese macht genau das Gleiche wie mein Blog-Beitrag.
Quickshiftin
5

Warum die Emulation auf "falsch" stellen?

Der Hauptgrund dafür ist, dass das Datenbankmodul die Vorbereitung anstelle des PDO durchführt, da die Abfrage und die tatsächlichen Daten getrennt gesendet werden, was die Sicherheit erhöht. Das heißt, wenn die Parameter an die Abfrage übergeben werden, werden Versuche, SQL in sie einzufügen, blockiert, da von MySQL vorbereitete Anweisungen auf eine einzelne Abfrage beschränkt sind. Dies bedeutet, dass eine wirklich vorbereitete Anweisung fehlschlägt, wenn eine zweite Abfrage in einem Parameter übergeben wird.

Das Hauptargument gegen die Verwendung des Datenbankmoduls für die Vorbereitung und das PDO sind die zwei Fahrten zum Server - eine für die Vorbereitung und eine für die Übergabe der Parameter -, aber ich denke, die zusätzliche Sicherheit ist es wert. Zumindest im Fall von MySQL war das Zwischenspeichern von Abfragen seit Version 5.1 kein Problem mehr.

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

Harry Bosh
quelle
1
Das Abfrage-Caching ist ohnehin weg : Der Abfrage-Cache ist ab MySQL 5.7.20 veraltet und wird in MySQL 8.0 entfernt.
Álvaro González
5

Ich bin überrascht, dass niemand einen der Hauptgründe für das Deaktivieren der Emulation genannt hat. Bei aktivierter Emulation gibt PDO alle Ganzzahlen zurück und schwebt als Zeichenfolgen . Wenn Sie die Emulation deaktivieren, werden Ganzzahlen und Gleitkommazahlen in MySQL zu Ganzzahlen und Gleitkommazahlen in PHP.

Weitere Informationen finden Sie in der akzeptierten Antwort auf diese Frage: PHP + PDO + MySQL: Wie gebe ich in PHP ganzzahlige und numerische Spalten aus MySQL als Ganzzahlen und Zahlen zurück? .

dallin
quelle
0

Für die Aufzeichnung

PDO :: ATTR_EMULATE_PREPARES = true

Es könnte eine böse Nebenwirkung erzeugen. Es könnte int-Werte als Zeichenfolge zurückgeben.

PHP 7.4, pdo mit mysqlnd.

Ausführen einer Abfrage mit PDO :: ATTR_EMULATE_PREPARES = true

Spalte: id
Typ: Ganzzahl
Wert: 1

Ausführen einer Abfrage mit PDO :: ATTR_EMULATE_PREPARES = false

Spalte: id
Typ: Zeichenfolge
Wert: "1"

In jedem Fall werden Dezimalwerte unabhängig von der Konfiguration immer als Zeichenfolge zurückgegeben :-(

Magallane
quelle
Dezimalwerte werden immer zurückgegeben. Eine Zeichenfolge ist der einzig richtige Weg
Ihr gesunder Menschenverstand
Ja, aus Sicht von MySQL, aber auf der PHP-Seite ist es falsch. Sowohl Java als auch C # betrachten Decimal als numerischen Wert.
Magallanes
Nein, das ist es nicht. Es ist alles richtig für die gesamte Informatik. Wenn Sie denken, dass es falsch ist, dann brauchen Sie einen anderen Typ von willkürlicher Präzision
Ihren gesunden Menschenverstand