
Транзакции баз данных являются фундаментальной концепцией систем управления базами данных (СУБД), которые обеспечивают целостность и непротиворечивость данных в многопользовательской среде.
транзакция в MySQL – это последовательная группа операторов, запросов или операций, таких как выбор, вставка, обновление или удалить, чтобы выполнить как одну рабочую единицу, которую можно зафиксировать или отменить.
Транзакции описываются как ACID.
- Атомарность. Это свойство гарантирует, что транзакция будет рассматриваться как единая единица по принципу «все или ничего». Если какая-либо часть транзакции терпит неудачу, вся транзакция откатывается.
- Непротиворечивость. Транзакции переводят базу данных из одного согласованного состояния в другое. Это означает, что данные должны удовлетворять определенным предопределенным правилам или ограничениям как до, так и после транзакции.
- Изоляция. Транзакции выполняются изолированно друг от друга. Это означает, что операции одной транзакции не видны другим транзакциям, пока первая транзакция не будет зафиксирована.
- Долговечность. Как только транзакция зафиксирована, ее последствия становятся постоянными и сохранятся при любых последующих системных сбоях или сбоях. Зафиксированные данные хранятся таким образом, что их можно восстановить, даже если система выйдет из строя.
Теперь, если вы хотите использовать транзакции в MySQL, вам нужно хранилище движка InnoDB.
InnoDB является стандартным и наиболее широко используемым механизмом хранения в MySQL. Он обеспечивает ACID-совместимые транзакции, поддержку внешнего ключа и блокировку на уровне строк, что делает его пригодным для широкого круга приложений.
MySQL не поддерживает вложенные транзакции, но движок InnoDB поддерживает точки сохранения.
В Laravel вы можете использовать транзакции Closure DB, как показано ниже.
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
DB::insert('insert on orders');
DB::update('update users set votes = 1');
DB::delete('delete from posts');
}, 5); // <= This is used to handle Deadlocks, and is the number of tries.
Используя этот метод, вам не нужно беспокоиться о запуске транзакции, фиксации и откате, если что-то пойдет не так, он делает все это автоматически. Вы также можете указать в качестве второго аргумента количество повторных попыток, которое определяет, сколько раз транзакция должна быть повторена при возникновении взаимоблокировки.
Ручная обработка транзакций БД
Запуск транзакции
Чтобы начать транзакцию в Laravel, вы используете метод beginTransaction(), предоставляемый фасадом DB. После запуска транзакции все последующие операции с базой данных, выполняемые с использованием фасада DB или Eloquent ORM, будут включены в эту транзакцию до тех пор, пока она не будет зафиксирована или отменена.
Подтверждение транзакции.
После успешного выполнения всех операций с базой данных в рамках транзакции вы можете зафиксировать изменения в базе данных с помощью метода commit().
Откат транзакции.
Если во время какой-либо операции возникает исключение, вы можете использовать метод rollback() для отмены изменений, внесенных в транзакцию.
Полный пример транзакции БД:
use Illuminate\Support\Facades\DB;
try {
DB::beginTransaction(); // <= Starting the transaction
// Update user's balance
DB::table('users')->where('id', 1)->decrement('balance', 100);
// Insert a new order
$orderID = DB::table('orders')->insertGetId([
'user_id' => 1,
'amount' => 100,
]);
// Insert a new order history
DB::table('order_history')->insert([
'order_id' => $orderID,
'status' => 'pending',
]);
DB::commit(); // <= Commit the changes
} catch (\Exception $e) {
report($e);
DB::rollBack(); // <= Rollback in case of an exception
}
Как мы упоминали выше, Laravel использует точки сохранения (если поддерживается механизмом БД).
Черта, которая обрабатывает транзакции, находится по адресу: Illuminate/Database/Concerns/ManagesTransactions.php. В этой статье мы рассмотрим только beginTransaction(), commit() и rollBack().
/**
* Start a new database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function beginTransaction()
{
$this->createTransaction(); // <== create transaction
$this->transactions++;
$this->transactionsManager?->begin(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('beganTransaction');
}
/**
* Create a transaction within the database.
*
* @return void
*
* @throws \Throwable
*/
protected function createTransaction()
{
if ($this->transactions == 0) {
$this->reconnectIfMissingConnection();
try {
$this->getPdo()->beginTransaction();
} catch (Throwable $e) {
$this->handleBeginTransactionException($e);
}
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
$this->createSavepoint(); // <= create savepoint
}
}
/**
* Commit the active database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function commit()
{
if ($this->transactionLevel() == 1) {
$this->fireConnectionEvent('committing');
$this->getPdo()->commit(); // <= if it's last transaction commit it
}
$this->transactions = max(0, $this->transactions - 1); // <= decrement transaction
if ($this->afterCommitCallbacksShouldBeExecuted()) {
$this->transactionsManager?->commit($this->getName());
}
$this->fireConnectionEvent('committed');
}
/**
* Rollback the active database transaction.
*
* @param int|null $toLevel
* @return void
*
* @throws \Throwable
*/
public function rollBack($toLevel = null)
{
// We allow developers to rollback to a certain transaction level. We will verify
// that this given transaction level is valid before attempting to rollback to
// that level. If it's not we will just return out and not attempt anything.
$toLevel = is_null($toLevel)
? $this->transactions - 1
: $toLevel;
if ($toLevel < 0 || $toLevel >= $this->transactions) {
return;
}
// Next, we will actually perform this rollback within this database and fire the
// rollback event. We will also set the current transaction level to the given
// level that was passed into this method so it will be right from here out.
try {
$this->performRollBack($toLevel);
} catch (Throwable $e) {
$this->handleRollBackException($e);
}
$this->transactions = $toLevel;
$this->transactionsManager?->rollback(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('rollingBack');
}
Если вы посмотрите внимательно, DB::beginTransaction() создаст новую транзакцию, если ее нет или если в настоящее время есть одна существующая транзакция, увеличит ее и создаст точку сохранения (если поддерживается).
С другой стороны, commit() и rollBack() уменьшат счетчик транзакций (точка сохранения) и зафиксируют все запросы, если они будут последними.
Вот как работают вложенные транзакции в Laravel.
Использование транзакций внутри цикла.
Использование транзакций внутри цикла может быть немного сложнее, чем использование транзакций для отдельных операций.
Когда вы имеете дело с циклами и транзакциями, вам необходимо тщательно управлять границами транзакций, чтобы обеспечить согласованность данных и производительность.
Давайте рассмотрим сценарий, в котором у вас есть цикл, обрабатывающий набор элементов, и вам необходимо выполнить операцию с базой данных для каждого элемента в рамках транзакции.

Что мы делаем, так это проверяем, равно ли количество элемента нулю , и если да, мы пропускаем его обработку и переходим к следующему элементу в массиве. Если он не равен нулю, мы обновляем запас и фиксируем транзакцию.
Приведенный выше код представляет проблему 😱.
Предположим, у нас есть массив из 3 элементов.
$item = [
[
'id' => 1,
'quantity' => 8,
],
[
'id' => 2,
'quantity' => 0,
],
[
'id' => 3,
'quantity' => 2,
],
];
- При первом входе в цикл мы начинаем новую транзакцию,
DB::beginTransaction();, таким образом, счетчик транзакций увеличивается до1, поскольку количество не равно нулю (элемент с идентификатором => 1),$item[‘quantity’] === 0мы продолжаем с обновление запаса, а затем мы фиксируем эту транзакцию, используяDB::commit();. Это уменьшает счетчик транзакций до0. - Теперь переходим к следующему элементу (элемент с id =› 2). Мы начинаем транзакцию, и это увеличивает количество транзакций до
1. Теперь, поскольку количество этого элемента равно нулю,$item[‘quantity’] === 0мы пропускаем этот шаг и переходим к следующей итерации. Помните, поскольку мы не сделалиcommit()илиrollBack(), количество транзакций по-прежнему равно 1. - Начать обработку следующего элемента (элемент с id =› 3). Мы запускаем транзакцию, и счетчик транзакций увеличивается до
2(одна для предыдущего элемента и одна для текущего элемента). Это происходит потому, что на предыдущем шаге мы не завершили транзакцию вызовомDB::commit()илиDB::rollBack(). После того, как мы проверим количество, которое не равно0, мы приступим к обновлению запаса, а затем зафиксируем транзакцию, но следующая фиксация не будет выполнять фактическую фиксацию в базе данных, она просто уменьшит счетчик транзакция от2=>1.
Если количество транзакций и количество фиксаций/откатов не синхронизированы, никакие изменения в БД вноситься не будут.
Имейте в виду, что каждая открытая транзакция должна быть завершена с помощью commit() или rollback(), особенно внутри циклов.

Не стесняйтесь Подписаться, чтобы получить больше подобного контента 🔔, аплодировать 👏🏻 , комментировать💬 и поделиться статьей с кем хочешь
И, как всегда, я ценю вашу поддержку и спасибо за чтение.