PHP PDO Transaction
Summary: in this tutorial, you will learn how to perform a database transaction from PHP by using PDO API.
Introduction to PHP PDO transaction
To start a transaction in PDO, you use the PDO::beginTransaction() method:
$pdo->beginTransaction();
Code language: PHP (php)
The beginTransaction() method turns off the autocommit mode. It means that the changes made to the database via the PDO object won’t take effect until you call the PDO::commit() method.
To commit a transaction, you call the PDO::commit() method:
$pdo->commit();
Code language: PHP (php)
If you want to roll back the transaction, you can call the PDO::rollback() method:
$pdo->rollback();
Code language: PHP (php)
The PDO::rollback() method rolls back all changes made to the database. Also, it returns the connection to the autocommit mode.
The PDO::beginTransaction() method throws an exception if the database doesn’t support transactions.
PDO transaction example
Suppose that you need to insert data into three tables: books , authors , and book_authors .
- Get the author id if the author exists; otherwise, insert the author into the authors table.
- Insert the book into the books table.
- Insert the link between book and author into the book_authors table.
To organize the code, we’ll place all functions in the functions.php file and include it in the mains script.
The following get_author_id() function finds author by first name and last name and returns the author id if the author exists in the authors table:
function get_author_id(\PDO $pdo, string $first_name, string $last_name) < $sql = 'SELECT author_id FROM authors WHERE first_name = :first_name AND last_name = :last_name'; $statement = $pdo->prepare($sql); $statement->bindParam(':first_name', $first_name, PDO::PARAM_STR); $statement->bindParam(':last_name', $last_name, PDO::PARAM_STR); if ($statement->execute()) < $row = $statement->fetch(PDO::FETCH_ASSOC); return $row !== false ? $row['author_id'] : false; > return false; >
Code language: HTML, XML (xml)
The following insert_author() function inserts a new author into the authors table and returns the author id:
function insert_author(\PDO $pdo, string $first_name, string $last_name): int < $sql = 'INSERT INTO authors(first_name, last_name) VALUES(:first_name, :last_name)'; $statement = $pdo->prepare($sql); $statement->bindParam(':first_name', $first_name, PDO::PARAM_STR); $statement->bindParam(':last_name', $last_name, PDO::PARAM_STR); $statement->execute(); return $pdo->lastInsertId(); >
Code language: HTML, XML (xml)
The following insert_book() function inserts a new book into the books table:
function insert_book(\PDO $pdo, string $title, string $isbn, string $published_date, int $publisher_id): int < $sql = 'INSERT INTO books(title, isbn, published_date, publisher_id) VALUES(:title, :isbn, :published_date, :publisher_id)'; $statement = $pdo->prepare($sql); $statement->bindParam(':title', $title, PDO::PARAM_STR); $statement->bindParam(':isbn', $isbn, PDO::PARAM_STR); $statement->bindParam(':published_date', $published_date, PDO::PARAM_STR); $statement->bindParam(':publisher_id', $publisher_id, PDO::PARAM_INT); $statement->execute(); return $pdo->lastInsertId(); >
Code language: HTML, XML (xml)
The following function inserts a new row into the book_authors table:
function insert_book_author(\PDO $pdo, int $book_id, int $author_id) < $sql = 'INSERT INTO book_authors(book_id, author_id) VALUES(:book_id, :author_id)'; $statement = $pdo->prepare($sql); $statement->bindParam(':book_id', $book_id, PDO::PARAM_INT); $statement->bindParam(':author_id', $author_id, PDO::PARAM_INT); $statement->execute(); >
Code language: HTML, XML (xml)
The following script uses those functions above to perform a transaction:
require 'functions.php'; $pdo = require 'connect.php'; $book = [ 'title' => 'Eternal', 'isbn' => '9780525539766', 'published_date' => '2021-03-23', 'publisher_id' => 2, ]; $author = [ 'first_name' => 'Lisa', 'last_name' => 'Scottoline', ]; try < $pdo->beginTransaction(); // find the author by first name and last name $author_id = get_author_id( $pdo, $author['first_name'], $author['last_name'] ); // if author not found, insert a new author if (!$author_id) < $author_id = insert_author( $pdo, $author['first_name'], $author['last_name'] ); > $book_id = insert_book( $pdo, $book['title'], $book['isbn'], $book['published_date'], $book['publisher_id'] ); // insert the link between book and author insert_book_author($pdo, $book_id, $author_id); // commit the transaction $pdo->commit(); > catch (\PDOException $e) < // rollback the transaction $pdo->rollBack(); // show the error message die($e->getMessage()); >
Code language: HTML, XML (xml)
Summary
- Use the PDO::beginTransaction() method to start a new transaction.
- Use the PDO::commit() method to commit a transaction and PDO::rollback() to roll back a transaction.
Транзакции и автоматическая фиксация изменений
Теперь вы знаете, как подключаться к базам данных посредством PDO. Но перед тем как выполнять запросы, вам необходимо понять, как PDO управляет транзакциями. Если вы прежде не сталкивались с транзакциями, они обладают четырьмя главными свойствами, это Атомарность, Согласованность, Изолированность и Долговечность (ACID). Говоря простым языком, любые действия, совершенные в рамках транзакции, гарантированно будут выполнены безопасно для базы данных, и на них не повлияют другие подключения к этой базе, даже если эти действия совершаются в несколько этапов. Транзакционные операции можно отменять по запросу (если транзакция ещё не зафиксирована), что упрощает обработку ошибок в скриптах.
Механизм транзакций реализован путём «временного сохранения» всех изменений и дальнейшего применения этих изменений, как единого целого. Это позволяет добиться резкого увеличения эффективности подобных изменений. Другими словами, транзакции могут сделать ваши скрипты более быстрыми и потенциально более стабильными (но для этого необходимо корректно использовать этот механизм).
К сожалению, не все базы данных поддерживают транзакции, поэтому PDO при создании подключения работает в режиме так называемой «автоматической фиксации». Режим автофиксации означает, что каждый запрос к базе данных, который вы выполняете, неявно заключается в транзакцию, если СУБД их поддерживает. Если база данных не поддерживает этот механизм, запрос обрабатывается без транзакции. Чтобы явно обозначить начало транзакции, вы должны использовать метод PDO::beginTransaction() . Если драйвер не поддерживает механизм транзакций, будет выброшено исключение PDOException (вне зависимости от выбранного способа обработки ошибок: подобные ситуации — это всегда серьёзная недоработка). После того, как вы совершите транзакцию, вы можете зафиксировать её методом PDO::commit() или откатить методом PDO::rollBack() , в зависимости от того, успешно выполнен ваш код внутри транзакции или нет.
PDO проверяет возможность использования транзакций только на уровне драйвера. Если по каким-то причинам механизм транзакций недоступен, но сервер баз данных принял запрос на открытие транзакции, PDO::beginTransaction() вернёт true без ошибок.
В качестве примера может быть попытка использования транзакций в таблицах MyISAM базы данных MySQL.
При завершении работы скрипта или при закрытии соединения, PDO автоматически откатывает все незавершённые транзакции. Это делается, чтобы предотвратить нарушения целостности базы данных в случаях, когда скрипт неожиданно прерывает работу. Если вы явно не зафиксировали изменения, предполагается, что что-то пошло не так. Поэтому откат изменений — наиболее безопасный выход из ситуации.
Изменения будут откачены автоматически, только если транзакция открыта методом PDO::beginTransaction() . Если транзакцию открыть вручную в тексте запроса, PDO об этом никак не узнает, и, соответственно, не сможет принять мер, если произойдёт что-то плохое.
Пример #1 Выполнение пакета изменений в рамках транзакции
В следующем примере предположим, что мы создаём несколько записей для нового сотрудника с номером ID 23. Помимо ввода основной информации необходимо записать его зарплату. Довольно просто сделать два отдельных обновления таблиц, однако путём заключения этих запросов в рамки PDO::beginTransaction() и PDO::commit() мы сможем гарантировать, что никто не увидит этих изменений, пока все они не будут завершены. Если что-то пойдёт не так, catch-блок откатит все изменения с начала транзакции и напечатает сообщение об ошибке.
try $dbh = new PDO ( ‘odbc:SAMPLE’ , ‘db2inst1’ , ‘ibmdb2’ ,
array( PDO :: ATTR_PERSISTENT => true ));
echo «Подключились\n» ;
> catch ( Exception $e ) die( «Не удалось подключиться: » . $e -> getMessage ());
>
?php
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 ();
>
?>
Вы никак не ограничены в количестве запросов в рамках транзакции; вы также можете выполнять сложные запросы, чтобы извлечь данные, а затем использовать их для создания других запросов на обновление и извлечение данных; если транзакция активна, вы можете быть уверены, что никто не сможет изменить ваши данные, пока вы с ними работаете. За дополнительной информацией о транзакциях обращайтесь к документации к вашему серверу баз данных.