2012-04-11 15 views
97

Das ist, was ich über bisher gelesen haben:PDO MySQL: Verwenden Sie PDO :: ATTR_EMULATE_PREPARES oder nicht?

  1. PDO's prepare emulation is better for performance since MySQL's native prepare bypasses the query cache.
  2. MySQL's native prepare is better for security (preventing SQL Injection).
  3. MySQL's native prepare is better for error reporting.

Ich weiß nicht, wie wahr irgendwelche dieser Aussagen sind. Meine größte Sorge bei der Auswahl einer MySQL-Schnittstelle ist die Vermeidung von SQL-Injection. Das zweite Problem ist die Leistung.

Meine Anwendung verwendet derzeit prozedurale MySQLi (ohne vorbereitete Anweisungen) und verwendet den Abfragecache ziemlich viel. Prepared Statements werden selten in einer einzigen Anfrage wiederverwendet. Ich begann den Umzug nach PDO für die genannten Parameter und die Sicherheit der vorbereiteten Aussagen.

Ich verwende MySQL 5.1.61 und PHP 5.3.2

Sollte ich PDO::ATTR_EMULATE_PREPARES aktiviert oder nicht verlassen? Gibt es eine Möglichkeit, sowohl die Leistung des Abfragecaches als auch die Sicherheit von vorbereiteten Anweisungen zu gewährleisten?

+3

Ehrlich? Benutze einfach MySQLi. Wenn es bereits mit vorbereiteten Anweisungen arbeitet, ist PDO im Grunde eine sinnlose Abstraktionsschicht. * EDIT *: PDO ist wirklich nützlich für Anwendungen im grünen Bereich, wo Sie nicht sicher sind, welche Datenbank in das Backend geht. – jmkeyes

+1

Sorry, meine Frage war vorher unklar. Ich habe es bearbeitet. Die Anwendung verwendet zur Zeit keine vorbereiteten Anweisungen in MySQLi. nur mysqli_run_query(). Nach dem, was ich gelesen habe, umgehen MySQLi vorbereitete Anweisungen auch den Abfrage-Cache. –

Antwort

93

Ihre Anliegen zu beantworten:

  1. MySQL> = 5.1.17 (oder> = 5.1.21 für die PREPARE und EXECUTE Aussagen) can use prepared statements in the query cache. Ihre Version von MySQL + PHP kann also vorbereitete Anweisungen mit dem Abfrage-Cache verwenden. Beachten Sie jedoch die Einschränkungen für die Zwischenspeicherung von Abfrageergebnissen in der MySQL-Dokumentation. Es gibt viele Arten von Abfragen, die nicht zwischengespeichert werden können oder die nutzlos sind, selbst wenn sie zwischengespeichert werden. Meiner Erfahrung nach ist der Abfrage-Cache sowieso nicht oft ein sehr großer Gewinn. Abfragen und Schemas benötigen eine spezielle Konstruktion, um den Cache optimal zu nutzen. Häufig ist auf lange Sicht das Caching auf Anwendungsebene ohnehin erforderlich.

  2. Native Prepare macht keinen Unterschied für die Sicherheit. Die pseudo-vorbereiteten Anweisungen werden weiterhin Abfrageparameterwerten entgehen, sie werden nur in der PDO-Bibliothek mit Zeichenfolgen statt auf dem MySQL-Server, der das Binärprotokoll verwendet, ausgeführt. Mit anderen Worten, der gleiche PDO-Code ist unabhängig von Ihrer EMULATE_PREPARES-Einstellung gleichermaßen anfällig (oder nicht anfällig) für Injektionsangriffe.Der einzige Unterschied besteht darin, wo der Parameteraustausch stattfindet - mit EMULATE_PREPARES tritt er in der PDO-Bibliothek auf; ohne EMULATE_PREPARES tritt es auf dem MySQL-Server auf.

  3. Ohne EMULATE_PREPARES können Syntaxfehler bei der Vorbereitungszeit und nicht zur Ausführungszeit auftreten. Mit EMULATE_PREPARES erhalten Sie nur Syntaxfehler zur Ausführungszeit, da PDO keine MySQL-Anfrage bis zur Ausführungszeit hat. Beachten Sie, dass dies den Code betrifft, den Sie schreiben werden! Vor allem, wenn Sie PDO::ERRMODE_EXCEPTION verwenden!

Eine weitere Überlegung:

  • Es gibt einen festen Preis für ein prepare() (unter Verwendung nativer Prepared Statements), so dass ein prepare();execute() mit nativer vorbereiteten Anweisungen können ein wenig langsamer sein als eine einfache Text Abfrage ausgeben emulierte vorbereitete Anweisungen verwenden. Auf vielen Datenbanksystemen ist der Abfrageplan für eine prepare() ebenfalls zwischengespeichert und kann mit mehreren Verbindungen geteilt werden, aber ich glaube nicht, dass MySQL dies tut. Wenn Sie das vorbereitete Anweisungsobjekt nicht für mehrere Abfragen verwenden, ist die Gesamtausführung möglicherweise langsamer.

Als abschließende Empfehlung, ich denke, mit älteren Versionen von MySQL + PHP, sollten Sie vorbereitete Anweisungen emulieren, aber mit Ihren jüngsten Versionen sollten Sie Emulation ausschalten.

Nachdem ich ein paar Apps geschrieben habe, die PDO verwenden, habe ich eine PDO-Verbindungsfunktion eingerichtet, die meiner Meinung nach die besten Einstellungen hat. Sie sollten wahrscheinlich so etwas wie diese oder zwicken, um Ihre bevorzugten Einstellungen verwenden:

/** 
* 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; 
} 
+20

Zu # 2: Werte, die MySQL als Parameter empfängt (für native Prepared Statements), werden überhaupt nicht nach SQL ** geparst **? Das Risiko einer Injektion * muss also geringer sein als bei Verwendung der PDO-Emulation, wo ein Fehler (z. B. die historischen Probleme, die mysql_real_escape_string mit Multi-Byte-Zeichen hatte) noch für Injektionsangriffe offen bleiben würde? – eggyal

+0

# 1: Super! Das sind völlig neue Informationen für mich und ich bin sehr glücklich darüber. Die App verwendet den Abfrage-Cache ziemlich oft (mehr als 50% aller Abfragen werden direkt aus dem Cache abgerufen). # 2: Ich müsste eggyal zustimmen. Das ist der entscheidende Punkt, wenn man zu vorbereiteten Anweisungen übergeht, weil MySQL dadurch den Inhalt selbst verlässt, anstatt zu erraten, wie der Inhalt maskiert werden soll. # 3: Ich möchte lieber wissen, dass ein Problem vorliegt, bevor die Abfrage ausgeführt wird, also ist das in Ordnung. Gute Antwort. Danke für Ihre Hilfe! –

+2

@eggyal, Sie machen Annahmen darüber, wie vorbereitete Anweisungen implementiert werden. PDO hat möglicherweise einen Fehler in seinen emulierten Vorbereitungs-Entwürfen, aber MySQL könnte auch Fehler enthalten.AFAIK, bei emulierten Prepares wurden keine Probleme entdeckt, die dazu führen können, dass Parameterliterale nicht-deklariert werden. –

6

Ich würde emulieren abstellen vorbereitet, wie Sie 5.1 laufen, was bedeutet, dass PDO die native vorbereitete Aussage Funktionalität nutzen wird.

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

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

ich ditched MySQLi für PDO für die vorbereitet genannten Anweisungen und die bessere API.

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

+0

Vielen Dank für diese Information. Wie hat sich die Verwendung des Abfragecaches nicht auf Ihre Leistung ausgewirkt, oder haben Sie diese überhaupt zuvor verwendet? –

+0

Ich kann nicht sagen, wie das Framework ich Caches auf mehreren Ebenen sowieso verwende. Sie können SELECT SQL_CACHE jedoch immer explizit verwenden. –

+0

Wusste nicht einmal, dass es eine SELECT SQL_CACHE-Option gab. Es scheint jedoch, dass das immer noch nicht funktionieren würde. Aus der Dokumentation: "Das Abfrageergebnis wird zwischengespeichert **, wenn es zwischengespeichert werden kann ** ..." http://dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html –

4

Ich empfehle echte Datenbank ermöglicht würde PREPARE Anrufe als die Emulation nicht alles fangen .., zum Beispiel, wird es INSERT; vorzubereiten!

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

Der Ausgang

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

ich für die Code-Performance-Einbußen gerne nehmen werde, die tatsächlich funktioniert.

FWIW

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

MySQL Version: 5.5.34-0ubuntu0

+0

Es ist ein interessanter Punkt. Ich denke, die Emulation verschiebt das serverseitige Parsing auf die Ausführungsphase. Während es keine große Sache ist (falsches SQL wird schließlich scheitern), ist es sauberer, sich 'vorbereiten' zu lassen, den Job zu machen, der es soll. (Außerdem habe ich immer angenommen, dass der Parameterparser auf der Clientseite notwendigerweise eigene Fehler haben wird.) –

+1

IDK wenn Sie interessiert sind, aber [hier ist ein kleiner Artikel] (https://quickshiftin.com/blog/ 2014/07/Batching-Anfragen-Datenbank-Migrationen-PHP-PDO /) auf ein anderes unechtes Verhalten, das ich mit PDO bemerkt habe, das mich zu Beginn mit diesem Kaninchenloch führte. Scheint die Behandlung von mehreren Abfragen fehlt. – quickshiftin

+0

Ich habe mir gerade einige Migrationsbibliotheken auf GitHub angeschaut ... Was weißt du, [dieser [] (https://github.com/brtriver/dbup/blob/master/src/Dbup/Application.php) tut so ziemlich alles genau dasselbe wie mein Blogbeitrag. – quickshiftin

8

Vorsicht PDO::ATTR_EMULATE_PREPARES auf deaktivieren (Drehen nativen bereitet auf) wenn Ihr PHP pdo_mysql nicht gegen mysqlnd kompiliert wird.

Da alte libmysql nicht vollständig kompatibel mit einigen Funktionen ist, kann es zu seltsamen Fehlern führen, zum Beispiel:

  1. Losing höchstwertigen Bits für 64-Bit-Integer, wenn sie als PDO::PARAM_INT Bindung (0x12345678AB wird 0x345678AB beschnitten werden auf 64-Bit-Maschine)
  2. Unfähigkeit, einfache Abfragen wie LOCK TABLES zu machen (es wirft SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet Ausnahme)
  3. benötigen alle Zeilen aus Ergebnis oder in der Nähe Cursor vor dem nächsten Abfrage holen (mit mysqlnd oder emu lated bereitet es funktioniert automatisch für Sie diese Arbeit und geht nicht mit MySQL-Server out of sync)

Diese Fehler ich in meinem einfachen Projekt herausgefunden, wenn für libmysqlpdo_mysql Modul verwendet, um andere Server migriert, die. Vielleicht gibt es viel mehr Bugs, ich weiß es nicht. Auch habe ich auf frischen 64bit debian jessie getestet, alle aufgeführten Fehler treten auf, wenn ich apt-get install php5-mysql, und verschwinden wenn ich apt-get install php5-mysqlnd.

Wenn PDO::ATTR_EMULATE_PREPARES auf "True" (als Standard) gesetzt ist - diese Fehler treten trotzdem nicht auf, weil PDO in diesem Modus überhaupt keine vorbereiteten Anweisungen verwendet. Also, wenn Sie pdo_mysql basierend auf libmysql verwenden ("mysqlnd" Teilstring nicht im Feld "Client-API-Version" von pdo_mysql Abschnitt in phpinfo angezeigt) - sollten Sie nicht PDO::ATTR_EMULATE_PREPARES deaktivieren.