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

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

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

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

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

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

Ответ 1

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

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

  • Родные препараты не имеют никакого значения для безопасности. Псевдоподготовленные операторы будут по-прежнему избегать значений параметров запроса, это будет сделано только в библиотеке PDO со строками, а не на сервере MySQL, используя двоичный протокол. Другими словами, тот же код PDO будет одинаково уязвим (или не уязвим) к инъекционным атакам независимо от вашего параметра EMULATE_PREPARES. Единственное различие заключается в том, где происходит замена параметра - с EMULATE_PREPARES, это происходит в библиотеке PDO; без EMULATE_PREPARES, это происходит на сервере MySQL.

  • Без 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;
}

Ответ 2

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

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

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

Ответ 3

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

Ответ 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

Ответ 5

Зачем переключать эмуляцию на "ложь"?

Основная причина этого заключается в том, что механизм подготовки базы данных вместо PDO выполняет подготовку вместо того, чтобы запрос и фактические данные отправлялись отдельно, что повышает безопасность. Это означает, что когда параметры передаются в запрос, попытки вставить SQL-код в них блокируются, поскольку подготовленные операторы MySQL ограничиваются одним запросом. Это означает, что истинно подготовленный оператор потерпит неудачу при передаче второго запроса в параметре.

Основным аргументом против использования механизма базы данных для подготовки против PDO являются две поездки на сервер - одна для подготовки, а другая для передачи параметров - но я думаю, что дополнительная безопасность того стоит. Кроме того, по крайней мере, в случае MySQL, кэширование запросов не было проблемой с версии 5.1.

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