После подключения к базе данных средствами модуля PDO потребуется понять, как PDO управляет транзакциями, прежде чем отправлять запросы. Тем, кто прежде не сталкивались с транзакциями, полезно знать 4 главных характеристики транзакций: атомарность, согласованность, изолированность и долговечность (англ. Atomicity, Consistency, Isolation and Durability, или ACID). Говоря простым языком, транзакция гарантирует, что каждая операция с базой данных выполнится безопасно и без помех со стороны других подключений, даже если операция выполняется поэтапно. Транзакционные операции автоматически отменяются по запросу, если транзакцию ещё не зафиксировали, что упрощает обработку ошибок в скриптах.
Работа механизма транзакций часто состоит в «накоплении» пакета изменений, которые затем выполняются как одно целое; приятный побочный эффект такой работы состоит в резком увеличении эффективности этих обновлений. Другими словами, транзакции ускоряют скрипты и потенциально повышают их надёжность, хотя для этого и потребуется правильно работать с транзакциями, чтобы получить эти преимущества.
Не каждая база данных поддерживает транзакции, поэтому при первом подключении модуль PDO вынужден работать в так называемом режиме «автоматической фиксации». В режиме автофиксации модуль оборачивает каждый запрос к базе данных в неявную транзакцию, если СУБД поддерживает транзакции, или выполняет отдельные запросы, если база данных не поддерживает механизм транзакций. Явное начало транзакции обозначают вызовом метода PDO::beginTransaction(). Независимо от настроек обработки ошибок модуль выбросит исключение PDOException, если нижележащий драйвер не поддерживает механизм транзакций, поскольку попытка начать транзакцию без поддержки драйвера — серьёзная ошибка. Внутри транзакции изменения фиксируют методом PDO::commit(), если код выполнился успешно, или откатывают транзакцию методом PDO::rollBack(), если при запуске кода в течение транзакции возникла ошибка.
Модуль PDO проверяет доступность транзакций только на уровне драйвера.
Метод PDO::beginTransaction() вернёт true
без ошибок,
если сервер базы данных примет запрос на запуск транзакции,
даже если в конкретных условиях при выполнении запроса выяснится, что транзакции недоступны.
К таким примерам относится попытка запуска транзакций в таблицах MyISAM базы данных MySQL.
Неявные фиксации при выполнении DDL-запросов:
Отдельные базы данных неявно выполняют команду COMMIT
и фиксируют изменения в одной транзакции при выполнении серии DDL-запросов (англ. Database Definition Language)
наподобие DROP TABLE
или CREATE TABLE
.
Поэтому изменения, которые оказались в транзакции,
автоматически фиксируются, и откатить такие изменения
невозможно.
К примерам баз данных с таким поведением относятся СУБД MySQL
и Oracle
.
Пример #1 Пример неявной фиксации
<?php
$pdo->beginTransaction();
$pdo->exec("INSERT INTO users (name) VALUES ('Rasmus')");
$pdo->exec("CREATE TABLE test (id INT PRIMARY KEY)"); // Неявная фиксация командой COMMIT выполняется в этом месте
$pdo->rollBack(); // Невозможно отменить серию команд INSERT/CREATE в БД MySQL или Oracle
?>
Лучшая практика: При работе с базами данных с таким поведением рекомендуют избегать выполнения DDL-запросов внутри транзакций. DDL-операции исключают из транзакционной логики, когда требуется.
Модуль PDO автоматически откатит невыполненные транзакции при завершении работы скрипта или при закрытии соединения. Откат транзакций — мера безопасности, которая помогает избегать нарушения целостности данных, когда скрипт неожиданно прерывает работу. Модуль предполагает, что взаимодействие с базой данных нарушилось, раз изменения не зафиксировали явным образом, и откатывает изменения для безопасности данных.
Изменения откатятся автоматически, только если транзакцию открыли методом PDO::beginTransaction(). При ручной отправке запроса, который начинает транзакцию, PDO не узнает об этом, поэтому не откатит транзакцию при сбое.
Пример #2 Выполнение пакета изменений в рамках транзакции
В следующем примере предположим, что требуется создать набор записей для нового сотрудника с идентификатором 23. Кроме ввода базовой информации о сотруднике потребуется записать его зарплату. Вместо двух отдельных изменений обернём обновление таблиц методами PDO::beginTransaction() и PDO::commit(), чтобы гарантировать, что никто другой не увидит этих изменений, пока вставка данных не завершится. При сбое блок catch откатит изменения, которые внесли с момента начала транзакции, и выведет сообщение об ошибке.
<?php
try {
$dbh = new PDO(
'odbc:SAMPLE',
'db2inst1',
'ibmdb2',
array(PDO::ATTR_PERSISTENT => true)
);
echo "Подключились\n";
} catch (Exception $e) {
die("Возникла ошибка подключения: " . $e->getMessage());
}
try {
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$dbh->exec("INSERT INTO staff (id, first, last) VALUES (23, 'Joe', 'Bloggs')");
$dbh->exec("INSERT INTO salarychange (id, amount, changedate) VALUES (23, 50000, NOW())");
$dbh->commit();
} catch (Exception $e) {
$dbh->rollBack();
echo "Ошибка: " . $e->getMessage();
}
?>
Транзакции не ограничивают разработчика только изменением данных; в транзакции заворачивают сложные запросы для извлечения данных, на основе которых создают больше запросов на обновление и другие запросы; активная транзакция гарантирует, что никто не изменит данные, пока идёт работа с транзакцией. Дополнительную информацию о транзакциях содержит документация к серверу баз данных.