Поскольку быстрым поиском готовый ответ на Тостере не находится, стоит написать канонический.
Сначала общая информация:
Транзакция служит для обеспечения принципа "всё или ничего", гарантируя, что либо все запросы выполнились без ошибок, либо, если в каком-то из запросов произошла ошибка, то все предыдущие будут отменены, как будто их и не было вовсе. Из чего можно сделать следующие выводы:
- транзакция не нужна для любого количества запросов на выборку данных, поскольку там нечего откатывать
- транзакция не нужна для одного запроса на изменение данных (вставка, обновление, удаление) - такой запрос представляет из себя мини-транзакцию, которая сама автоматом откатывается при ошибке
- не следует путать транзакции с блокировками. Хотя при определённых параметрах транзакции могут выполнять и блокировку, в общем случае это два разных механизма, которые могут выполняться как вместе, так и по отдельности. По умолчанию транзакция НЕ обеспечивает блокировку таблиц, участвующих в запросе
Самым простым вариантом будет заключить запросы между вызовами beginTransaction() и commit(), как показано например
в документации к последнему.
$db->beginTransaction();
$db->prepare("UPDATE `tab1` SET `col` = ?")->execute($data1);
$db->prepare("UPDATE `tab2` SET `col` = ?")->execute($data2);
$db->prepare("UPDATE `tab3` SET `col` = ?")->execute($data3);
$db->commit();
Для современных версий РНР этого должно быть достаточно: начиная с РНР 8.0 ошибочный запрос по умолчанию выбрасывает исключение. Не пойманное исключение прерывает выполнение РНР скрипта. При прерывании выполнения скрипта РНР закрывает соединение с Mysql, а при закрытии соединения Mysql откатывает все открытые в нём транзакции.
Соответственно, при ошибке в любом из запросов транзакция автоматически откатится. А при успешном выполнении всех запросов транзакция, соответственно, закоммитится.
В редких случаях, когда после отката транзакции предполагается дальнейшее выполнение кода, можно заключить код транзакции в try-catch и откатить её вручную.
Важно помнить некоторые особенности.
- в mysql не все движки таблиц поддерживают транзакции. впрочем ,учитывая что innodb является движком по умолчанию уже лет 20, это вряд ли будет проблемой
- запросы на изменение страктуры таблиц автоматически коммитят стартовавшую транзакцию, то есть их следует избегать.
Кажется, в новых версиях какие-то уже не коммитят, но я предпочитаю избегать всё равно.
Также желательно помнить, что в любом более-менее сложном коде очень быстро появляются вложенные транзакции, а PDO при попытке стартовать транзакцию при уже открытой, выбросит исключение, что, соответственно, приведёт к откату родительской (и это гораздо лучше поведения MySQL по умолчанию, которая автоматически коммитит старую). И имеет смысл накидать простой кодик, который считает вложенные транзакции, и не стартует, если уже был старт, а при коммите вычитает вложенность, а реально коммитит только если вложенности не осталось. Что-то вроде кода из комментариев к beginTransaction(), подравняв его напильником
class \MyPDO extends \PDO
{
protected $transactionCounter = 0;
public function beginTransaction()
{
if($this->transactionCounter++ === 0) {
return parent::beginTransaction();
}
}
public function commit()
{
$this->transactionCounter--;
if($this->transactionCounter === 0) {
return parent::commit();
}
}
public function rollback()
{
$this->transactionCounter = 0;
return parent::rollback();
}
}
разместив его либо прямо в PDO, либо в своем враппере.