Выполнение запросов к базам данных в PHP: PDO против MySQLi
Основные подходы к выполнению запросов в PHP
Взаимодействие с базами данных - ключевая задача веб-разработки. В PHP существуют два основных расширения: MySQLi и PDO. Оба поддерживают подготовленные запросы, однако PDO считается более гибким, так как работает с разными СУБД. Далее рассмотрено основное решение с использованием PDO.
Рекомендуемый способ: PDO с подготовленными запросами
Как безопасно выполнить запрос к базе данных с помощью PDO?
Для начала необходимо установить соединение с базой данных через DSN, указав имя хоста, имя базы и кодировку. Затем создаётся объект PDO. Для выполнения запросов с параметрами следует использовать метод prepare и последующий вызов execute. Это защищает от SQL-инъекций.
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$user = 'root';
$pass = '';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => 'test@example.com']);
$user = $stmt->fetch();
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}Php бд mysql (база данных mysql в php)
В этом примере используется именованный плейсхолдер :email. Можно также применять позиционные параметры (знаки вопроса).
Типичные проблемы:
- Неверный DSN - ошибка «could not find driver» или «connection refused». Решение - проверить наличие драйвера pdo_mysql и правильность хоста.
- Отсутствие обработки исключений - если не установлен режим исключений, ошибки игнорируются. Рекомендуется всегда включать
ERRMODE_EXCEPTION. - Использование устаревшего
queryс прямым встраиванием переменных - приводит к SQL injection. Выход - только подготовленные запросы.
Как выполнить запрос с помощью MySQLi в процедурном стиле?
Процедурный стиль MySQLi предполагает использование функций mysqli_connect, mysqli_query, mysqli_fetch_assoc и т.д. Этот способ прост для понимания, но требует ручного освобождения ресурсов и не поддерживает исключения по умолчанию.
$link = mysqli_connect('localhost', 'root', '', 'test');
if (!$link) {
die('Ошибка подключения: ' . mysqli_connect_error());
}
$result = mysqli_query($link, 'SELECT * FROM users WHERE id = ' . (int)$_GET['id']);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'];
}
mysqli_free_result($result);
mysqli_close($link);Php база данных пользователей (база данных пользователей в php)
Распространённые ошибки:
- SQL-инъекция при встраивании переменных без экранирования. Приведённый код использует приведение к int, но для строк требуется
mysqli_real_escape_string. - Утечка памяти, если не вызвать
mysqli_free_result. - Отсутствие проверки ошибок после
mysqli_query- может привести к неожиданному поведению.
Как использовать объектно-ориентированный MySQLi для запросов?
Объектно-ориентированный интерфейс MySQLi предоставляет классы mysqli, mysqli_stmt и mysqli_result. Подготовленные запросы реализуются через метод prepare, что повышает безопасность.
$mysqli = new mysqli('localhost', 'root', '', 'test');
if ($mysqli->connect_error) {
die('Ошибка: ' . $mysqli->connect_error);
}
$stmt = $mysqli->prepare('SELECT name, email FROM users WHERE status = ?');
$status = 'active';
$stmt->bind_param('s', $status);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row['name'] . ' - ' . $row['email'];
}
$stmt->close();
$mysqli->close();
Php база данных сайт (база данных сайта на php)
Возможные трудности:
- Необходимость указывать тип параметра в
bind_param(s, i, d, b). Ошибка типа может привести к некорректной работе. - Неправильный порядок вызовов: сначала prepare, потом bind_param, затем execute.
- При использовании
get_resultтребуется драйвер mysqlnd. Без него можно использоватьbind_resultиfetch.
Как выполнить несколько запросов в одной транзакции?
Транзакции позволяют гарантировать, что все изменения будут применены атомарно. В PDO используется методы beginTransaction, commit и rollback.
try {
$pdo->beginTransaction();
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
echo 'Транзакция отменена: ' . $e->getMessage();
}Ошибки при работе с транзакциями:
- Забыли вызвать
beginTransaction- каждый запрос выполняется в авто-коммите. - Отсутствие обработки исключений в транзакции - при ошибке изменения могут частично сохраниться.
- Использование
execс непроверенными данными - риск SQL injection.
Расширенные примеры запросов
Пример 1: Вставка записи с получением ID
Добавление нового пользователя и получение его идентификатора.
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt->execute(['name' => 'Иван Петров', 'email' => 'ivan@example.com']);
$id = $pdo->lastInsertId();
echo 'Добавлен пользователь с ID: ' . $id;Добавлен пользователь с ID: 42
Пример 2: Выборка с JOIN и пагинацией
Получение списка заказов с именем клиента и суммой, с постраничным выводом.
$page = 1;
$perPage = 10;
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare('
SELECT o.id, c.name, o.total
FROM orders o
JOIN customers c ON o.customer_id = c.id
ORDER BY o.created_at DESC
LIMIT :limit OFFSET :offset
');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$orders = $stmt->fetchAll();
print_r($orders);Array
(
[0] => Array
(
[id] => 101
[name] => Анна Смирнова
[total] => 2500.00
)
[1] => Array
(
[id] => 100
[name] => Борис Иванов
[total] => 1200.00
)
...
)Пример 3: Использование точек сохранения в транзакции
При необходимости частичного отката можно установить точку сохранения.
$pdo->beginTransaction();
$pdo->exec('UPDATE accounts SET balance = balance - 200 WHERE id = 1');
$pdo->exec('SAVEPOINT after_debit');
$pdo->exec('UPDATE accounts SET balance = balance + 200 WHERE id = 2');
// при обнаружении ошибки откат до точки
$pdo->exec('ROLLBACK TO SAVEPOINT after_debit');
$pdo->commit(); // фиксируем только debit, credit откатилсяПример 4: Получение одного значения
Метод fetchColumn извлекает один столбец из следующей строки результата.
$stmt = $pdo->prepare('SELECT COUNT(*) FROM users WHERE status = :status');
$stmt->execute(['status' => 'active']);
$count = $stmt->fetchColumn();
echo 'Активных пользователей: ' . $count;Активных пользователей: 152
Пример 5: Массовая вставка с prepared statement
Можно многократно выполнять один и тот же подготовленный запрос с разными данными.
$stmt = $pdo->prepare('INSERT INTO logs (message, level) VALUES (:msg, :lvl)');
$logs = [
['msg' => 'User login', 'lvl' => 'info'],
['msg' => 'Page view', 'lvl' => 'debug'],
];
foreach ($logs as $log) {
$stmt->execute($log);
}
echo 'Вставлено ' . count($logs) . ' записей';Вставлено 2 записей