PDO MySQL: використовувати PDO :: ATTR_EMULATE_PREPARES чи ні?


117

Про це я читав досі PDO::ATTR_EMULATE_PREPARES:

  1. Емуляція підготовки PDO краще для роботи, оскільки вбудована підготовка MySQL обходить кеш запитів .
  2. Натурна підготовка MySQL краща для безпеки (запобігання ін'єкції SQL) .
  3. Натурна підготовка MySQL краще для повідомлення про помилки .

Я вже не знаю, наскільки правдиві будь-які з цих тверджень. Моє найбільше занепокоєння у виборі інтерфейсу MySQL - це запобігання ін'єкції SQL. Друге питання - це продуктивність.

Наразі моя програма використовує процедурні MySQLi (без підготовлених висловлювань) і досить часто використовує кеш запитів. Він рідко повторно використовувати підготовлені заяви в одному запиті. Я розпочав перехід до PDO для названих параметрів та безпеки підготовлених операторів.

Я використовую MySQL 5.1.61іPHP 5.3.2

Потрібно залишити PDO::ATTR_EMULATE_PREPARESввімкнено чи ні? Чи існує спосіб виконання кешу запитів і безпеки підготовлених операторів?


3
Чесно? Просто продовжуйте використовувати MySQLi. Якщо він вже працює з використанням підготовлених тверджень під цим, PDO - це в основному безглуздий шар абстракції. EDIT : PDO дуже корисний для додатків із зеленим полем, де ви не впевнені, яка база даних переходить у бек-енд.
jmkeyes

1
Вибачте, раніше моє запитання було незрозумілим. Я це відредагував. Наразі програма не використовує підготовлені оператори у MySQLi; просто mysqli_run_query (). З того, що я прочитав, MySQLi підготовлені заяви також обходять кеш запитів.
Ендрю Енслі

Відповіді:


108

Щоб відповісти на ваші проблеми:

  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;
}

26
Re # 2: звичайно , значення , які MySQL приймає в якості параметрів (в нативних підготовлених заяв) не одержати розібраний для SQL взагалі ? Тож ризик ін'єкції повинен бути нижчим, ніж використання емуляції підготовки PDO, де будь-яка вада втечі (наприклад, історичні проблеми, пов’язані mysql_real_escape_stringз багатобайтовими символами) все ще залишить один відкритим для ін'єкційних атак?
eggyal

2
@eggyal, ви робите припущення щодо того, як реалізуються підготовлені заяви. PDO може мати помилку в емуляції, що готується, але в MySQL також можуть бути помилки. AFAIK, не було виявлено жодних проблем із емульованими препаратами, які можуть призвести до того, що літерали параметрів можуть пройти через нерозмірну форму.
Франциск Авіла

2
Дивовижна відповідь, але у мене питання: Якщо ви вимкнете ЕМУЛЯЦІЮ, чи не буде виконання повільнішим? PHP повинен був відправити підготовлену операцію в MySQL для перевірки і лише після цього надіслати параметри. Отже, якщо ви будете використовувати підготовлений оператор 5 разів, PHP буде говорити з MySQL 6 разів (замість 5). Хіба це не зробить це повільніше? Крім того, я думаю, є більший шанс, що PDO може мати помилки в процесі перевірки, а не MySQL ...
Radu Murzea

6
Зауважте, що пункти у цій відповіді повторно підготували емуляцію оператора, використовуючи mysql_real_escape_stringпід кришкою, та наслідки, які можуть виникнути вразливості (у дуже конкретних крайніх випадках).
eggyal

6
+1 Гарна відповідь! Але для запису, якщо ви використовуєте вбудовану підготовку, параметри ніколи не уникають і не поєднуються в SQL-запит навіть на стороні сервера MySQL. На час виконання та подачі параметрів запит був проаналізований та перетворений у внутрішні структури даних у MySQL. Прочитайте цей блог інженером-оптимізатором MySQL, який пояснює цей процес: guilhembichot.blogspot.com/2014/05/… Я не кажу, що це означає, що рідна підготовка є кращою, наскільки ми довіряємо коду PDO для того, щоб правильно виконувати втечу (що я робити).
Білл Карвін

9

Остерігайтеся відключення PDO::ATTR_EMULATE_PREPARES(увімкнення нативної програми), коли ваш 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" підрядку не відображається в полі "Client API version" pdo_mysqlрозділу розділу phpinfo) - не слід PDO::ATTR_EMULATE_PREPARESвимикати.


3
чи все ж це занепокоєння діє в 2019 році ?!
oldboy

8

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


Дякую за інформацію. Як не в змозі використовувати кеш запитів вплинуло на вашу ефективність чи ви навіть раніше використовували його?
Ендрю Енслі

Я не можу сказати, що я використовую кеші на декількох рівнях. Ви завжди можете явно використовувати SELECT SQL_CACHE <остаток оператора>.
Буде Морган

Навіть не знав, що є варіант SELECT SQL_CACHE. Однак, схоже, це все одно не вийде. З документів: "Результат запиту кешується, якщо він кешований ..." dev.mysql.com/doc/refman/5.1/uk/query-cache-in-select.html
Ендрю Енслі

Так. Це залежить від характеру запиту, а не специфіки платформи.
Буде Морган

Я читав, що означає "Результат запиту кешований, якщо щось інше не заважає йому бути кешованим ", який - з того, що я читав до цього часу - включав підготовлені заяви. Однак, завдяки відповіді Френсіса Авіли, я знаю, що це вже не відповідає моїй версії MySQL.
Ендрю Енслі

6

Я рекомендую включити реальні 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


Це цікавий момент. Я думаю, емуляція відкладає розбір на стороні сервера на фазу виконання. Хоча це не велика справа (неправильний SQL в кінцевому підсумку вийде з ладу), більш чистого дозволити prepareробити роботу, яку він повинен. (Крім того, я завжди припускав, що у аналізатора параметрів на стороні клієнта обов'язково з’являться власні помилки.)
Альваро Гонсалес

1
IDK, якщо вам цікаво, але ось невеличке опис щодо іншої хибної поведінки, яку я помітив із PDO, які ведуть мене вниз по цій кролячій норі. Здається, недостатньо обробки декількох запитів.
quickshiftin

Я просто переглянув деякі бібліотеки міграції на GitHub ... Що ти знаєш, ця одна дуже точно робить те саме, що і моя публікація в блозі.
quickshiftin

5

Навіщо переключати емуляцію на 'false'?

Основна причина цього полягає в тому, що, маючи механізм бази даних робити підготовку замість PDO, це те, що запит і фактичні дані надсилаються окремо, що підвищує безпеку. Це означає, що коли параметри передаються в запит, спроби ввести SQL в них блокуються, оскільки підготовлені оператори MySQL обмежуються одним запитом. Це означає, що справжнє підготовлене твердження буде невдалим при передачі другого запиту в параметрі.

Основним аргументом проти використання двигуна бази даних для підготовки vs PDO є дві поїздки на сервер - одна для підготовки, а інша для проходження параметрів - але я думаю, що додаткова безпека того варта. Крім того, принаймні у випадку з MySQL, кешування запитів не було проблемою з версії 5.1.

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



5

Я здивований, що ніхто не згадав про одну з найбільших причин вимкнення емуляції. З увімкненою емуляцією PDO повертає всі цілі числа і плаває у вигляді рядків . Коли ви вимикаєте емуляцію, цілі числа та плаваючі елементи в MySQL стають цілими числами та плавають у PHP.

Для отримання додаткової інформації дивіться прийнятий відповідь на це запитання: PHP + PDO + MySQL: як я можу повернути цілі та числові стовпці з MySQL у вигляді цілих чисел та чисел у PHP? .


0

Для запису

PDO :: ATTR_EMULATE_PREPARES = вірно

Це може призвести до неприємного побічного ефекту. Він може повернути значення int як рядок.

PHP 7.4, pdo з mysqlnd.

Запуск запиту з PDO :: ATTR_EMULATE_PREPARES = true

Стовпець: id
Тип: ціле число
значення Значення: 1

Запуск запиту з PDO :: ATTR_EMULATE_PREPARES = false

Стовпець: id
Тип: рядок
Значення: "1"

У будь-якому випадку десяткові значення завжди повертаються рядком, незалежно від конфігурації :-(


Десяткові значення завжди повертаються рядком - це єдиний правильний спосіб
Ваше здорове значення

Так, з точки зору MySQL, але це неправильно на стороні PHP. І Java, і C # розглядають десятичну як числове значення.
magallanes

Ні, це не так. Це як все правильно для всієї інформатики. Якщо ви вважаєте, що це неправильно, то вам потрібен інший тип, довільної точності
Ваше здорове почуття
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.