Transactions#
Introduction#
DBLayer supports manual and closure transactions, including nested transaction levels where savepoints are available for the active driver.
Closure Transaction#
DB::transaction(function (): void {
DB::table('accounts')->where('id', '=', 1)->update(['balance' => 900]);
DB::table('accounts')->where('id', '=', 2)->update(['balance' => 1100]);
});
If an exception is thrown inside the callback, DBLayer rolls back and rethrows.
Manual Transaction#
DB::beginTransaction();
try {
DB::table('orders')->insert(['user_id' => 1, 'total' => 100]);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
Retry Attempts#
Use retry attempts for transient failures:
DB::transaction(function (): void {
// critical write path
}, attempts: 3);
When retries are enabled, write logic must be idempotent or safely repeatable. Prefer transaction-level retry for deadlocks/serialization failures over retrying standalone non-idempotent statements.
Read-Only Transactions#
DB::readOnlyTransaction(function ($connection): int {
return (int) $connection->scalar('select count(*) from reports');
});
Driver behavior:
PostgreSQL: best-effort
SET TRANSACTION READ ONLY.MySQL/MariaDB: best-effort
SET TRANSACTION READ ONLY.SQLite: safe no-op (no transaction-scoped read-only toggle).
Execution Budgets#
Combine transaction logic with query-level timeout/deadline wrappers:
DB::withQueryTimeout(500, function (): void {
DB::transaction(function (): void {
DB::select('select 1');
});
});
Note
Timeouts and deadlines are query-execution controls, not transaction-level lock-time guarantees. Database engine behavior still applies.
Introspection#
DB::transactionLevel()DB::transactionStats()