Транзакции и автоматическая фиксация изменений
После подключения к базе данных средствами модуля 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();
}
?>
Транзакции не ограничивают разработчика только изменением данных; в транзакции заворачивают
сложные запросы для извлечения данных, на основе которых
создают больше запросов на обновление и другие запросы; активная
транзакция гарантирует, что никто не изменит данные,
пока идёт работа с транзакцией. Дополнительную информацию о транзакциях
содержит документация к серверу баз данных.