2012-04-11 125 views
97

這是我到目前爲止約閱讀:PDO MySQL:是否使用PDO :: ATTR_EMULATE_PREPARES?

  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

我不知道這些陳述如何是真實的了。在選擇MySQL接口時,我最關心的是防止SQL注入。第二個問題是性能。

我的應用程序目前使用程序MySQLi(沒有準備好的語句),並且使用查詢緩存很多。它很少會在單個請求中重複使用準備好的語句。我開始向PDO轉移已命名參數和已準備語句的安全性。

我使用MySQL 5.1.61PHP 5.3.2

我應該離開PDO::ATTR_EMULATE_PREPARES啓用與否?有沒有辦法同時具備查詢緩存的性能和預準備語句的安全性?

+3

老實說?只要繼續使用MySQLi。如果它已經在使用準備好的語句,PDO基本上是一個毫無意義的抽象層。 *編輯*:PDO對於您不確定哪些數據庫將進入後端的綠色領域應用程序非常有用。 – jmkeyes 2012-04-12 01:59:12

+1

對不起,我的問題之前還不清楚。我編輯過它。該應用程序目前不使用MySQLi中的預準備語句;只是mysqli_run_query()。從我讀過的,MySQLi準備的語句也繞過了查詢緩存。 – 2012-04-12 14:13:01

回答

93

爲了回答您的問題:

  1. 的MySQL> = 5.1.17(或> = 5.1.21爲PREPAREEXECUTE語句)can use prepared statements in the query cache。所以你的MySQL + PHP版本可以使用準備好的語句和查詢緩存。但是,請仔細記錄MySQL文檔中用於緩存查詢結果的注意事項。有許多類型的查詢不能被緩存或者即使被緩存也沒有用。在我的經驗中,查詢緩存通常不是一個非常大的勝利。查詢和模式需要特殊的構造才能最大限度地利用緩存。無論如何,從長遠來看,應用程序級高速緩存通常是必需的。

  2. 本地準備對安全性沒有任何影響。僞準備語句仍然會跳過查詢參數值,它只會在PDO庫中使用字符串完成,而不是使用二進制協議在MySQL服務器上完成。換句話說,無論您的EMULATE_PREPARES設置如何,相同的PDO代碼同樣容易受到(或不易受到)注入攻擊。唯一的區別是發生參數替換的位置 - EMULATE_PREPARES,它出現在PDO庫中;沒有EMULATE_PREPARES,它發生在MySQL服務器上。

  3. 沒有EMULATE_PREPARES你可能在準備時而不是在執行時得到語法錯誤;與EMULATE_PREPARES你只會在執行時得到語法錯誤,因爲在執行時間之前PDO沒有查詢給MySQL。請注意,這會影響您將編寫的代碼!特別是如果您使用PDO::ERRMODE_EXCEPTION

另外的考慮:

  • 有一個固定的成本prepare()(使用本機準備的語句),所以與本機準備語句的prepare();execute()可以比發出一個純文本查詢慢一點使用模擬的準備好的語句。在許多數據庫系統上,prepare()的查詢計劃也被緩存,可能會與多個連接共享,但我不認爲MySQL會這樣做。所以如果你不重複使用你準備好的語句對象進行多個查詢,你的整體執行速度可能會變慢。

作爲最後一個建議,我想與舊版本的MySQL + PHP的,你應該效仿準備語句,但你很最新版本的你應該把仿真關閉。

編寫使用PDO一些應用程序後,我做了一個PDO連接功能,具有什麼,我認爲是最好的設置。你或許應該使用這樣或調整到您的首選設置:

/** 
* 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

Re#2:當然,MySQL作爲參數(對於本地準備語句)接收的值不會被解析爲SQL **嗎?因此,注入*的風險必須低於使用PDO的準備模擬,其中任何轉義漏洞(例如,歷史問題mysql_real_escape_string具有多字節字符)仍然會使注入攻擊一開放。 – eggyal 2012-05-04 20:56:00

+0

#1:太棒了!這對我來說是全新的信息,我對此非常高興。該應用程序確實利用查詢緩存很多(超過50%的查詢直接從緩存中檢索)。 #2:我不得不同意eggyal。移動到準備好的語句的整個目的是爲了逃避內容本身的MySQL安全性,而不是PHP猜測內容應該如何逃脫。 #3:我實際上更喜歡在執行查詢之前知道有一個問題,所以沒關係。很好的答案。謝謝你的幫助! – 2012-05-04 21:35:13

+2

@eggyal,您正在假設如何執行準備好的語句。 PDO可能在仿真準備轉義時存在一個錯誤,但是MySQL也可能有錯誤。AFAIK,沒有發現模擬準備可能導致參數文字通過未轉義的問題。 – 2012-05-04 21:39:47

6

當您運行5.1時,我會關閉仿真準備,這意味着PDO將利用本地準備的語句功能。

PDO_MYSQL將充分利用MySQL 4.1及更高版本中提供的本地準備語句支持。如果您使用的是舊版本的mysql客戶端庫,PDO將爲您模擬它們。

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

我拋棄的MySQLi爲PDO的準備命名報表和更好的API。

但是,爲了保持平衡,PDO的執行速度比MySQLi慢得多,但需要牢記。當我做出選擇時,我知道這一點,並且決定使用更好的API並使用行業標準比使用將您與特定引擎聯繫起來的可以忽略的更快的庫更重要。 FWIW我認爲PHP團隊也將對PDO和MySQLi的未來表示樂觀。

+0

謝謝你的信息。如何無法使用查詢緩存會影響您的性能,或者您之前是否使用過? – 2012-05-04 14:10:41

+0

無論如何,我不能說我在多層上使用緩存的框架。儘管你可以總是明確地使用SELECT SQL_CACHE 。 – 2012-05-04 16:37:22

+0

甚至不知道有一個SELECT SQL_CACHE選項。但是,看起來這仍然行不通。從文檔:「查詢結果緩存**,如果它可緩存** ...」http://dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html – 2012-05-04 17:25:55

4

我建議你實現實時數據庫PREPARE調用作爲仿真不捕獲一切..,例如,它會準備INSERT;

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

輸出

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

我會很樂意採取的性能損失爲實際工作的代碼。

FWIW

PHP版本:PHP 5.4.9-4ubuntu2.4(CLI)

的MySQL版本:5.5.34-0ubuntu0

+0

這是一個有趣的觀點。我猜測仿真推遲了服務器端解析到執行階段。雖然這不是什麼大不了的事情(錯誤的SQL最終會失敗),但是讓'prepare'完成它應該做的工作會更清晰。 (另外,我一直認爲客戶端參數解析器必然會有它自己的錯誤。) – 2014-07-15 10:54:59

+1

IDK,如果你有興趣,但[這裏有一點點寫法](https://quickshiftin.com/blog/ 2014/07/batching-requests-database-migrations-php-pdo /)對於我注意到的PDO中的一些其他虛假行爲,我首先引導了這個兔子洞。似乎缺乏對多個查詢的處理。 – quickshiftin 2014-07-15 16:44:45

+0

我只是看着GitHub上的一些遷移庫......你知道些什麼,[這個](https://github.com/brtriver/dbup/blob/master/src/Dbup/Application.php)與我的博客文章完全一樣。 – quickshiftin 2014-07-15 20:13:00

8

謹防上禁用PDO::ATTR_EMULATE_PREPARES(打開本機上做準備)當您的PHP pdo_mysql沒有編入mysqlnd時。

因爲老libmysql與某些功能完全兼容,它可以導致奇怪的錯誤,例如:

  1. 時爲PDO::PARAM_INT(0x12345678AB將被裁剪到0x345678AB上結合失去最顯著位爲64位整數64位機)
  2. 無法作出簡單的查詢,如LOCK TABLES(它拋出SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet除外)
  3. 需要下一個查詢之前來從結果或關閉遊標的所有行(與mysqlnd或鴯鶓遲來自動準備它這樣做對你的工作和不出去的同步與MySQL服務器)

這些錯誤我在簡單的項目想通了,當遷移到使用libmysqlpdo_mysql模塊其他服務器。也許有更多的錯誤,我不知道。此外,我對新鮮的64位debian jessie進行了測試,所有列出的錯誤發生在我apt-get install php5-mysql,並且當apt-get install php5-mysqlnd時消失。

PDO::ATTR_EMULATE_PREPARES設置爲true(默認)時,這些錯誤不會發生,因爲PDO在此模式下根本不使用預處理語句。因此,如果您使用基於libmysqlpdo_mysql(「mysqlnd」子字符串不會出現在phpinfo的pdo_mysql部分的「客戶端API版本」字段中) - 您不應該關閉PDO::ATTR_EMULATE_PREPARES