PDO MySQL: используйте PDO :: ATTR_EMULATE_PREPARES или нет?

Это то, что я до сих пор читал о PDO::ATTR_EMULATE_PREPARES :

  1. Подготовительная эмуляция PDO лучше для производительности, так как встроенная подготовка MySQL обходит кеш запросов .
  2. Собственная подготовка MySQL лучше для безопасности (предотвращение SQL Injection) .
  3. Подготовка к сбору MySQL лучше для отчетов об ошибках .

Я не знаю, насколько верно любое из этих утверждений. Моей самой большой заботой в выборе интерфейса MySQL является предотвращение SQL Injection. Вторая проблема – производительность.

В настоящее время мое приложение использует процедурный MySQLi (без подготовленных операторов) и довольно часто использует кеш запросов. Он редко будет повторно использовать подготовленные заявления в одном запросе. Я начал переход к PDO для именованных параметров и безопасности подготовленных операторов.

Я использую MySQL 5.1.61 и PHP 5.3.2

Должен ли я PDO::ATTR_EMULATE_PREPARES или нет? Есть ли способ иметь как производительность кеша запросов, так и безопасность подготовленных операторов?

Solutions Collecting From Web of "PDO MySQL: используйте PDO :: ATTR_EMULATE_PREPARES или нет?"

Чтобы ответить на ваши вопросы:

  1. MySQL> = 5.1.17 (или> = 5.1.21 для операторов PREPARE и EXECUTE ) могут использовать подготовленные инструкции в кеше запросов . Таким образом, ваша версия MySQL + PHP может использовать подготовленные операторы с кешем запросов. Тем не менее, обратите внимание на предостережения для кэширования результатов запроса в документации MySQL. Существует много типов запросов, которые невозможно кэшировать или бесполезны, даже если они кэшированы. По моему опыту кеш запросов часто не является очень большой победой. Запросы и схемы требуют специальной конструкции, чтобы максимально использовать кеш. Часто кэширование на уровне приложений в любом случае оказывается необходимым в долгосрочной перспективе.

  2. Родные препараты не имеют никакого значения для безопасности. Псевдоподготовленные операторы будут по-прежнему избегать значений параметров запроса, это будет сделано только в библиотеке PDO со строками, а не на сервере MySQL, используя двоичный протокол. Другими словами, один и тот же код PDO будет одинаково уязвим (или не уязвим) к инъекционным атакам независимо от настроек EMULATE_PREPARES . Единственное различие заключается в том, где происходит замена параметра – с 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; } 

Остерегайтесь отключения PDO::ATTR_EMULATE_PREPARES (включение native готовится), когда ваш PHP pdo_mysql не скомпилирован против mysqlnd .

Поскольку старый libmysql не полностью совместим с некоторыми функциями, это может привести к появлению странных ошибок, например:

  1. Потеря наиболее значимых бит для 64-битных целых чисел при привязке как PDO::PARAM_INT (0x12345678AB будет обрезана до 0x345678AB на 64-битной машине)
  2. Неспособность делать простые запросы, такие как LOCK TABLES (он выдает SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet исключение)
  3. Нужно извлечь все строки из результата или закрыть курсор перед следующим запросом (с помощью mysqlnd или эмулированных он автоматически выполняет эту работу для вас и не синхронизируется с сервером mysql)

Эти ошибки, которые я выяснил в моем простом проекте, были перенесены на другой сервер, который использовал libmysql для модуля pdo_mysql . Может быть, есть намного больше ошибок, я не знаю. Также я тестировал новый 64-разрядный debian jessie, все перечисленные ошибки возникают, когда я apt-get install php5-mysql и исчезаю, когда я apt-get install php5-mysqlnd .

Когда для PDO::ATTR_EMULATE_PREPARES установлено значение true (по умолчанию) – эти ошибки не происходят в любом случае, поскольку PDO вообще не использует подготовленные инструкции в этом режиме. Итак, если вы используете pdo_mysql на основе libmysql («mysqlnd», подстрока не появляется в поле «Версия API клиента» раздела pdo_mysql в phpinfo) – вы не должны PDO::ATTR_EMULATE_PREPARES .

Я бы отключил эмуляцию подготавливает, когда вы используете 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 для будущего.

Я бы рекомендовал включить настоящую базу данных 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