webew
Войти » Регистрация
 
PHP
MySQL/MariaDB

PHP: критика перехода с оригинального API MySQL на mysqli и PDO

29 марта 2013, 17:32

Оригинальный API MySQL (функции mysql_*() — например, mysql_query() и пр.) с версии PHP 5.5.0 объявлен устаревшим. Вместо него разработчики PHP рекомендуют использовать модуль mysqli или объекты данных PHP (PDO). Эти средства обладают расширенным по сравнению с традиционным API функционалом, но действительно ли они удобнее в повседневной практике?

mysqli

mysqli — "MySQL improved extension" ("улучшенный модуль MySQL") — прямой наследник оригинального API MySQL, обладающий более широкими возможностями. Чтобы почувствовать разницу, достаточно посмотреть на список его методов и сравнить с таковым оригинального модуля. Однако нужно отметить, что отличие в реальных возможностях на самом деле только одно: mysqli имеет возможность отправки множественных запросов1. Хотя иметь такую возможность и удобно, на практике её наличие ощутимой роли не играет2.

Другими словами, разницы в реальных возможностях обычного и улучшенного модулей практически нет. Зато есть разница в интерфейсе. Рассмотрим, как в простейшем случае выглядит самая обычная операция — установление соединения с сервером MySQL и выполнение запроса:

// оригинальный модуль
mysql_connect('host', 'user', 'password');
mysql_query("SELECT 1");

// улучшенный модуль, процедурный интерфейс
$mysqli = mysqli_connect('host', 'user', 'password');
mysqli_query($mysqli, "SELECT 1");

// улучшенный модуль, объектный интерфейс
$mysqli = new mysqli($host, $user, $password);
$mysqli->query("SELECT 1");    

Здесь важно заметить, что функция mysql_query() не требует передавать в явном виде указатель на соединение (хотя и позволяет это делать). В подавляющем большинстве случаев для работы требуется только одно соединение с MySQL, и, однажды установив его, вполне удобно и естественно просто вызывать mysql-функции, не ссылаясь на соединение в явном виде каждый раз.

Функция же mysqli_query(), напротив, требует передавать объект, указывающий на соединение. Этот объект потребуется во всех местах, где нужно будет делать запрос. Это означает, что в коде с локальной областью видимости — функциях и классах — придется либо заводить глобальную переменную, либо передавать этот объект явно.

Это, казалось бы, небольшое отличие на практике не только приводит к необходимости писать более длинный код, но и осложняет миграцию с оригинального модуля на улучшенный из-за необходимости вносить в код изменения.

Резюмируя сказанное, приходится констатировать, что на практике модуль mysqli не обладает какой-то дополнительной полезностью по сравнению с оригинальным. Пользоваться же им менее удобно.

PDO

PHP Data Object (Объекты данных PHP) — расширение языка, определяющее абстрактный интерфейс доступа к базам данных (это означает, что одни и те же методы PDO могут использоваться для разных СУБД).

На практике реально ощутимым отличием PDO от других интерфейсов к MySQL является возможность легко делать следующие две операции:

  • вставку в запрос параметров с экранированием
  • получение результата запроса в виде ассоциативного массива

Работа с MySQL, в основном, и заключается именно в этих двух вещах, поэтому многие разработчики останавливают свой выбор на PDO.

Однако есть один нюанс. Рассмотрим, как в самом простом случае средствами PDO выполняется запрос:

$DBH = new PDO('mysql:host=...;dbname=...', 'user', 'password'); // соединяемся с БД
$query = $DBH->prepare("SELECT 1"); // готовим запрос
$query->execute(); // выполняем запрос

Важно отметить, что для отправки запроса требуется вызывать не один метод, а два — prepare(), а затем execute(). На уровне механизма СУБД в данном случае задействуются т.н. prepared statements — специальный инструмент СУБД, позволяющий ускорить последовательное выполнение повторяющихся запросов, построенных по одному и тому же шаблону.

Вместе с тем, на практике идущие подряд однотипные запросы встречаются довольно редко. Напротив, обычно запросы разные, и это полностью нивелирует положительный эффект от предварительного разбора. Более того, в конечном итоге вместо одного запроса к MySQL делается два, в результате чего схема с prepare() и execute() оказывается даже медленнее обычного одиночного запроса.

PDO позволяет выполнить запрос и напрямую — для этого предназначен метод query(). Однако при использовании этого метода вставку в запрос параметров и их экранирование приходится проводить вручную, поэтому query() не пользуется популярностью и разработчики предпочитают связку из prepare() и execute() в любом случае, потому что это удобнее.

Следует также подчеркнуть, что при использовании PDO (как и в случае с mysqli) есть необходимость заводить объект, который в областях видимости функций и классов недоступен.

Резюмируя сказанное, нужно отметить, что по сравнению с другими модулями PDO предоставляет несколько более удобный функционал, однако реализация его выполнена неоптимально. Кроме того, высокий уровень абстракции интерфейса плохо сказывается на осведомлённости разработчиков о происходящем на стороне MySQL-сервера.

Что делать?

Одним из решений всех вышеперечисленных проблем является библиотека удобных функций MySQL, при разработке которой основной целью было сделать программирование самых распространенных операций при работе с MySQL (таких как сохранение результата запроса в виде ассоциативного массива, обработка ошибок, подстановка массива в запрос) максимально удобным. Вот несколько примеров:

// если в глобальной области видимости есть переменная $mysqli,
// библиотека будет пользоваться инструментами mysqli,
// в противном случае - оригинального модуля MySQL

// включаем mysqli
$mysqli = mysqli_connect(...);

// простая отправка запроса
mysql_q("TRUNCATE sometable");


// получение результата скалярного (один столбец, одна строка) запроса
$now = mysql_getcell("SELECT NOW()");

// получение строки таблицы:
$row = mysql_getrow("
    SELECT *
    FROM watches
    WHERE id = 1052
    "
);
   
// получение столбца в виде одномерного массива:
$ids = mysql_getcolumn("
    SELECT id
    FROM watches
    WHERE mark = 'Edox' AND price > 5000
    "
);

// получение записей таблицы с подстановкой в запрос параметров:
$sql = "
    SELECT *
    FROM watches
    WHERE mark = :mark AND price > :price
    "
;
$params = array( 'mark' => 'Edox', 'price' => 5000 );
$data = mysql_gettable($sql, FALSE, $params);

// подстановку можно делать во всех вышеперечисленных функциях

Подробнее о библиотеке можно прочитать в соответствующей статье.

1. Работу с транзакциями, хранимыми процедурами и прочим, заявленным в документации как отличия между модулями, на самом деле можно реализовывать с помощью собственно SQL-запросов: например, вместо $mysqli->commit() писать mysql_query("COMMIT"), и т.п.

2. Разница в производительности множественного запроса и соответствующего количества одиночных сводится к затратам на отправку запроса из PHP, которые по сравнению с затратами на собственно их выполнение механизмом СУБД как правило ничтожно малы. Кроме того, ситуация, когда запросы следуют подряд без необходимости промежуточной обработки результатов, является довольно редкой.


© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
NO USERPIC

marapper

Вы пишите это, на минуточку, в 2103 году. Ведь все "претензии" к "новому" интерфейсу общения с БД, выглядят далеким приветом из далеких 90-ых.

Одно соединение на БД требуется не в подавляющем числе случаев, а в случаях, когда вы работаете с малым количеством данных, - это может быть бложек, сайтик-визитка или что-то подобное. В полноценном проекте рано или поздно приходит понимание, что данные необходимо размещать в нескольких БД с разными правами - хотя бы ради скеюрности, я уж не говорю о мастабируемости.

Если уж приходится заводить новую переменную в глобальной области видимости, прости, у меня для вас известие - с вашим кодом что-то не так, и ему в любом случае требуется аудит безопасности.

И самое главное, в mysqli - конечно, безопасность, о которой так пренебрежительно замечается в абзаце об оверхеде prepare. Который, кстати, позволяет не только выполнять запросы несколько раз повторно, но и позволяет самой СУБД кешировать стейтмент для того, чтобы в конкуретном потоке пхп выполнить запрос быстрее.

Ну и, конечно, в скрипте для парсинга данных нам достаточно и mysqli, но в любом проекте, который чуть больше, масштабируемость играет куда большую роль, поэтому не обойтись без ActiveRecord или ORM.

А ведь пхп растет именно в сторону энтерпрайза, чтобы его перестали считать языком быдлокодеров и школьников. Чему, к сожалению, не способствуют подобные статьи.
29.03.2013, 20:16
Ответить
NO USERPIC

rgbeast

На мой взгляд, критика, данная в статье, вполне уместна. Использование одного или нескольких подключений - выбор пользователя, не связанный с безопасностью. Напротив, в высоконагруженных проектах, лучше не использовать несколько поключений, чтобы усложнять архитектуру, которую в этом случае будет труднее масштабировать.

mysqli вообще ничем не отличается от mysql, поэтому не видно логики, которая стоит за тем, чтобы сделать интерфейс устаревшим и заставлять всех переписывать скрипты, надежно работавшие годами.

PDO неправильно реализует требования безопасности. Вместо того, чтобы самому предоставить возможность безопасной абстракции запросов к базе, он опирается на встроенный в базу данных механизм PREPARE. Это гораздо более серьезный удар по производительности, чем может показаться на первый взгляд. Масштабировать базу данных достаточно сложно - такие решения, как MySQL Cluster, Galera Cluster или репликация требуют существенного перерасхода машинных ресурсов и значительного администрирования. Масштабирование front-машин (с php), напротив, достаточно рутинная операция. Поэтому представляется логичным работу по добавлению параметров в запрос выполнять на клиенте, причем это можно было бы делать в один этап (как предложил автор статьи).

Итого: отказываемся от привычного всем работающего интерфейса, не предлагая ничего, что было бы существенно лучше старого.
29.03.2013, 22:08
Ответить

1234ru

marapper
Одно соединение на БД требуется не в подавляющем числе случаев ...

По приблизительным оценкам, для сайтов, у которых меньше полумиллиона посетителей в день, хорошо работает одно соединение (проверено на практике). По количеству сайтов это уже подавляющее большинство.
Уверен, что для многих сайтов выше этого порога одно соединение тоже подойдет.
В тех редких случаях, когда нужно больше одного соединения, можно сохранить указатели на соединения в переменные, но для этого не нужно ни mysqli, ни PDO. Оригинальный модуль MySQL тоже это позволяет.

Цитата:
Если уж приходится заводить новую переменную в глобальной области видимости, прости, у меня для вас известие - с вашим кодом что-то не так

Что-то не так с разработчиками PHP, которые в оригинальном модуле MySQL позволяли обращаться к соединению без явного указания, а в его следующей версии - mysqli - эту возможность убрали.

Цитата:
данные необходимо размещать в нескольких БД с разными правами - хотя бы ради скеюрности ...

В общем случае такой необходимости нет. Без конкретных примеров ваше утверждение беспочвенно.

Цитата:
... я уж не говорю о мастабируемости.

Масштабирование баз данных заключается в использовании нескольких идентичных серверов, права доступа к которым одинаковые. Непонятно, как вы связываете масштабирование с разделением прав.

Цитата:
И самое главное, в mysqli - конечно, безопасность, о которой так пренебрежительно замечается в абзаце об оверхеде prepare.

Абзац об оверхеде prepare вообще никак не связан с mysqli. Вы, вероятно, имели в виду PDO.
Экранирование строк доступно в оригинальном API MySQL. Оно аналогично используемому при подстановке параметров в PDO, так что никакой дополнительной безопасности PDO не дает.

Цитата:
... prepare. Который, кстати, позволяет не только выполнять запросы несколько раз повторно

Выполнять однотипные запросы повторно обычно не нужно. В тех случаях, когда нужно, медленной операцией обычно будет не повторный разбор SQL (от которого позволяет избавиться prepare) а собственно работа с данными (например, вставка их в таблицу и перестройка индекса, и т.п.).

Цитата:
... , но и позволяет самой СУБД кешировать стейтмент для того, чтобы в конкуретном потоке пхп выполнить запрос быстрее.

Нет.
Prepared statements живут только внутри одного соединения. Конкурентный поток откроет свое и prepared statement соседнего использоваться не сможет.
То, что не убивает нас, делает нас инвалидами.
31.03.2013, 04:13
Ответить
NO USERPIC

rgbeast

Не согласен относительно PREPARE: его использование безопаснее, чем mysql_real_escape_string. В случае PREPARE, исполнение другого запроса исключено по построению, а для mysql_real_escape_string можно, в принципе, представить, что что-то отработает некорректно (из-за кодировки, баги php и.т.д.) и произойдет sql-инъекция.
01.04.2013, 19:57
Ответить

1234ru

Цитата:
Не согласен относительно PREPARE: его использование безопаснее, чем mysql_real_escape_string.

Не понимаю.
Чем mysql_real_escape_string() хуже PDO::quote()?
Точно такое же предупреждение насчет кодировки по умолчанию (ну а php у них вообще один и тот же :) )
То, что не убивает нас, делает нас инвалидами.
02.04.2013, 19:09
Ответить
NO USERPIC

rgbeast

Упомянутые функции скорее всего делают одно и то же. Речь о том, что подход с PREPARE принципиально безопаснее подхода без PREPARE. При использовании PREPARE невозможно выполнение не такого запроса, как задан, независимо от того, что передано в качестве аргументов для execute()
02.04.2013, 19:19
Ответить

1234ru

Все равно не понимаю.
Схема с prepare-execute на стороне MySQL состоит из трех запросов. К примеру:

PREPARE test FROM 'SELECT ?';
SET @a = 1;
EXECUTE test USING @a;

PDO::execute(), очевидно, выполняет второй и третий. Т.е. где-то должен произойти аналог SET @a = 1.
По-моему, это и есть место для инъекции. Оно никуда не делось и не может деться - возможность инъекции будет существовать до тех пор, пока существует необходимость отправлять на сторону СУБД произвольные данные.

Или не так?
То, что не убивает нас, делает нас инвалидами.
10.04.2013, 10:27
Ответить
NO USERPIC

rgbeast

Да, пожалуй. Никогда не думал про инъекции в запрос SET, но они вполне возможны - в случае уязвимости можно вставить любой подзапрос.
10.04.2013, 21:18
Ответить
NO USERPIC

vasya

Protection against SQL injection attacks. The parameter values can contain unescaped SQL quote and delimiter characters.


Сервер при создании подготовленного выражения проводит его синтаксический разбор и кэширует частичный план выполнения. Поэтому не получится обмануть, подсунув в качестве параметра хитрый подзапрос.
В твоем примере при выполнении
EXECUTE test USING @a;
сервер уже знает, что нужно просто отдать содержимое @a
03.02.2014, 18:03
Ответить

1234ru

Это не поможет, т.к. речь про инъекцию на предыдущей стадии - когда еще только устанавливается значение @a (см. мой предыдущий комментарий).
То, что не убивает нас, делает нас инвалидами.
05.02.2014, 20:12
Ответить
NO USERPIC

vasya

Моя мысль в том, что ещё на этапе подготовки
PREPARE test FROM 'SELECT ?';
сервер поймет, что запрос представляет из себя простой select, никаких таблиц не затрагивает и нужно лишь отдать параметр подготовленного выражения. Поэтому на втором этапе, когда ты будешь определять переменную инъекции не произойдет.

MariaDB [test]> select id from test limit 1;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.06 sec)

MariaDB [test]> prepare zxc from 'select ?';
Query OK, 0 rows affected (0.01 sec)
Statement prepared

MariaDB [test]> set @a= 'id from test limit 1';
Query OK, 0 rows affected (0.00 sec)

MariaDB [test]> execute zxc using @a;
+----------------------+
| ?                    |
+----------------------+
| id from test limit 1 |
+----------------------+
1 row in set (0.03 sec)
05.02.2014, 20:41
Ответить
NO USERPIC

rgbeast

vasya, инъекция в SET будет делаться не с целью повредить следующий запрос, а чтобы добиться побочного эффекта от самого запроса SET.
set @a= '1', @a=`SELECT password FROM users WHERE id=10`;
05.02.2014, 20:49
Ответить
NO USERPIC

vasya

Понятно. Но как сделать инъекцию в set?
MariaDB [test]> set @a= '1', @a=`select id from test limit 1`;
ERROR 1054 (42S22): Unknown column 'select id from test limit 1' in 'field list'
05.02.2014, 21:16
Ответить

1234ru

SET @a = '1', @a = (SELECT id FROM test LIMIT 1);
То, что не убивает нас, делает нас инвалидами.
05.02.2014, 22:20
Ответить
NO USERPIC

vasya

Да, действительно.
05.02.2014, 22:27
Ответить
NO USERPIC

programolamer

1) mysqli быстрее mysql
2) множественные запросы сокращают пхп код, поэтому полезность их очевидна
3) масштабирование бд бывает разным
17.11.2014, 01:23
Ответить

1234ru

programolamer
1) mysqli быстрее mysql

Откуда такая информация?

programolamer
2) множественные запросы сокращают пхп код

Если писать напрямую через mysql_query() - то да, заметно. А вот если пользоваться обертками, то несущественно:
mysql_q($sql1);
mysql_q($sql2);
// или
mysql_q($sql1 . ";" . $sql2);

К тому же, обычно тело SQL-запроса гораздо длиннее, чем сам вызов PHP-функции.

programolamer
3) масштабирование бд бывает разным

Да.
Но при каком виде масштабирования появляется что-то такое, для чего нужно отказываться от библиотеки mysql_* в пользу mysqli_* или PDO, остается неясным.
То, что не убивает нас, делает нас инвалидами.
17.11.2014, 19:52
Ответить
NO USERPIC

полудух

по поводу передачи в функцию, лично я пользуюсь ссылкой...
у меня один главный массив со всеми переменными:
$s{'ini'} = array(0,1,0,0,1);
$s{'page'} = $default_page;
$s{'ajax'} = $ajax_what;
$s{'mysqli'} = new mysqli(...); // это в функции выполняется есессно подключение, потом ещё $mysqli->set_charset("utf8");

а потом просто во все функции передаю этот $s первым параметром, а принимаю так:
function func(&$s)
{
$s{'mysqli'}->query();
}

вроде норм, как думаете?
через него также удобно из функции в функцию данные перекидывать
16.07.2015, 16:40
Ответить

1234ru

Вы сделали некий аналог глобальных переменных.

Их недостатком является общее на всех пространство имен (в вашем случае - ключей массива; в одном месте можно забыть про другое и переписать элемент массива, не отдав себе в этом отчет).
Если вам так удобно - то дело хозяйское.
То, что не убивает нас, делает нас инвалидами.
16.07.2015, 20:45
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2008—2024 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100

Реклама: