Транзакции и автоматическая фиксация изменений

После подключения к базе данных средствами модуля 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();
}

?>

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