PDO vs. MySQLi: что стоит использовать?
Для соединения с базой данных можно использовать MySQLi и/или PDO
Что следует знать при выборе?
В этой статье будут показаны различия, затронуты темы поддержки баз данных, стабильности и производительности.
Общая информация — сравнение
PDO | MySQLi | |
---|---|---|
Поддержка баз данных | 12 различных баз данных | только MySQL |
API | ООП | ООП и процедурное обращение |
Соединение | всё просто | всё просто |
Именованные параметры
|
+ | — |
Маппинг | + | + |
Prepared statements
(client side) |
+ | — |
Производительность | быстро | быстро |
Хранимые процедуры
|
+ | + |
Соединение
Ниже приведены примеры подключения с использованием PDO и MySQLi
1 2 3 4 5 6 7 8 |
// PDO $pdo = new PDO("mysql:host=localhost;dbname=database", 'username', 'password'); // mysqli, procedural way $mysqli = mysqli_connect('localhost','username','password','database'); // mysqli, object oriented way $mysqli = new mysqli('localhost','username','password','database'); |
Прошу заметить, что эти объекты/ресурсы соединения будут использоваться на протяжении всей статьи.
Поддержка API
PDO и MySQLi предлагают объектно-ориентированный API, но при этом MySQLi так же предлагает процедурный доступ к функционалу, который более понятный для новичков.
Если Вы знакомы с нативным от PHP доступом к MySQL, переход на MySQLi с использованием функций будет более лёгким. С другой стороны, если Вы освоите PDO, вы сможете использовать любую СУБД, для которой имеется драйвер PDO.
Поддержка баз данных
Основной преимущество PDO перед MySQLi — это поддержка множества баз данных. На данных момент PDO поддерживает 12 различных СУБД, тогда как MySQLi только MySQL.
Для просмотра списка драйверов СУБД в PDO можно исполнить следующий код
1 |
var_dump(PDO::getAvailableDrivers()); |
Для чего это всё нужно? Например, в ситуации, когда Вы меняете в проекте СУБД. С PDO этот процесс будет лёгким. Вы меняете строку подключения и меняете некоторые запросы под новую СУБД. С MySQLi надо будет переписывает все участки кода, где встречаются вызовы функций/методов MySQLi. Ну и вдобавок так же же корректируете запросы.
Именованные параметры
Это другая важная фишка PDO. Именованное связывание понятнее числового связывания.
Например код с использованием PDO:
1 2 3 4 5 6 7 8 9 10 |
$params = array(':username' => 'test', ':email' => $mail, ':last_login' => time() - 3600); $pdo->prepare(' SELECT * FROM users WHERE username = :username AND email = :email AND last_login > :last_login'); $pdo->execute($params); |
и код с использованием MySQLi
1 2 3 4 5 6 7 8 9 |
$query = $mysqli->prepare(' SELECT * FROM users WHERE username = ? AND email = ? AND last_login > ?'); $query->bind_param('sss', 'test', $mail, time() - 3600); $query->execute(); |
В этих примерах в запросе перед выполнением в нужных местах вставляются значения.
Второй пример короче, но первый более гибкий и понятный. Не надо следить за порядком параметров.
К сожалению MySQLi не поддерживает именованные параметры.
Маппинг
И PDO и MySQLi поддерживают маппинг — это как бы проецирование строки таблицы в объект, который имеет одноимённые с полями таблицы свойства. Обращение к значениям строки через свойства объекта.
Маппинг удобен в том случае, если вы не хотите использовать какой нибудь абстрактный слой над СУБД, и в то же время хотите иметь интерфейс обращения как в ORM.
Допустим у нас есть класс, свойства которого соответствуют полям в таблице.
1 2 3 4 5 6 7 8 9 10 |
class User { public $id; public $first_name; public $last_name; public function info() { return '#'.$this->id.': '.$this->first_name.' '.$this->last_name; } } |
Без маппинга для корректной работы метода info надо надо было бы вручную или в конструкторе присваивать значения из полей свойствам объекта.
Но всё это делается автоматически:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$query = "SELECT id, first_name, last_name FROM users"; // PDO $result = $pdo->query($query); $result->setFetchMode(PDO::FETCH_CLASS, 'User'); while ($user = $result->fetch()) { echo $user->info()."\n"; } // MySQLI, procedural way if ($result = mysqli_query($mysqli, $query)) { while ($user = mysqli_fetch_object($result, 'User')) { echo $user->info()."\n"; } } // MySQLi, object oriented way if ($result = $mysqli->query($query)) { while ($user = $result->fetch_object('User')) { echo $user->info()."\n"; } } |
Безопасность
Обе библиотеки имеют средства для борьбы с SQL инъекциями.
Разберём пример. Например хакер использует переменную GET username для внедрения SQL инъекции.
1 |
$_GET['username'] = "'; DELETE FROM users; /*" |
Если мы не заэкранируем данную переменную, она вставиться в SQL запрос как есть и удалит все из таблицы users, так как будет выполнен множественный запрос. К примеру был запрос
1 2 3 |
$query = "SELECT * FROM user WHERE username='" . $_GET['username'] . "' LIMIT 1"; |
после подстановки значение GET переменной username в переменной $query будет следующий запрос
1 2 3 4 5 6 7 8 9 |
$query = "SELECT * FROM user WHERE username=''; DELETE FROM users; /*' LIMIT 1"; /* получается два запроса SELECT * FROM user WHERE username=''; и DELETE FROM users; и далее всё закомментировано и игнорируется */ |
Чтобы всего этого избежать надо экранировать потенциально опасные символы в значении переменной — апострофы, двойные кавычки и тд.
1 2 3 4 5 6 7 8 9 |
// PDO, "manual" escaping $username = PDO::quote($_GET['username']); $pdo->query("SELECT * FROM users WHERE username = $username"); // mysqli, "manual" escaping $username = mysqli_real_escape_string($_GET['username']); $mysqli->query("SELECT * FROM users WHERE username = '$username'"); |
Второй вариант — экранирование и подстановка значений в запрос одновременно.
1 2 3 4 5 6 7 8 |
// PDO, prepared statement $pdo->prepare('SELECT * FROM users WHERE username = :username'); $pdo->execute(array(':username' => $_GET['username'])); // mysqli, prepared statements $query = $mysqli->prepare('SELECT * FROM users WHERE username = ?'); $query->bind_param('s', $_GET['username']); $query->execute(); |
Рекомендуется использоваться второй вариант.
Производительность
PDO и MySQLi довольно быстрые. Но по тестам MySQLi быстрее на 2,5% без использования подстановок и экранирования(prepared statements) и на 6,5% с оными. Но нативное расширение MySQL быстрее обоих библиотек. В каждом случае нужно искать своё решение.
Что в итоге?
В конечном итоге PDO победил. С поддержкой различных СУБД и именованными параметрами, можно игнорировать факт небольшой потери производительности. С точки зрения безопасности все они имеют средства защиты.
Так что если Вы ещё работаете с MySQLi, возможно стоит использовать PDO?
—————
Вольный перевод http://net.tutsplus.com/tutorials/php/pdo-vs-mysqli-which-should-you-use/