Laravel Database Transaction: Kapan Harus Menggunakan DB::transaction()?
Dalam aplikasi Laravel, satu proses bisnis sering kali tidak hanya melakukan satu query database. Sebuah proses dapat membuat data user, menyimpan order, mengurangi stok, mencatat pembayaran, dan membuat log dalam waktu yang hampir bersamaan.
Masalah muncul ketika salah satu query berhasil tetapi query berikutnya gagal. Jika tidak ditangani dengan baik, database dapat berada dalam kondisi data yang tidak konsisten.
Di sinilah database transaction diperlukan.
Laravel menyediakan DB::transaction() untuk menjalankan beberapa operasi database sebagai satu kesatuan. Jika seluruh proses berhasil, perubahan dapat di-commit. Jika terjadi exception, transaction dapat di-rollback sehingga perubahan yang belum di-commit dibatalkan.
Artikel ini membahas konsep Laravel Database Transaction, cara menggunakan DB::transaction(), kapan transaction wajib digunakan, kapan tidak diperlukan, contoh Eloquent, rollback, deadlock retry, hingga praktik terbaik untuk aplikasi production.
Apa Itu Database Transaction?
Database transaction adalah sekumpulan operasi database yang diperlakukan sebagai satu unit pekerjaan.
Tujuannya adalah menjaga agar beberapa perubahan data yang saling berkaitan tidak menghasilkan kondisi setengah selesai.
Contoh sederhana:
Mulai Transaction
|
v
Buat Order
|
v
Kurangi Stok
|
v
Buat Order Item
|
v
Simpan Payment
|
v
Semua berhasil?
| |
Ya Tidak
| |
v v
COMMIT ROLLBACK
Jika semua operasi berhasil, transaction di-commit.
Jika salah satu operasi gagal, transaction di-rollback.
Masalah Tanpa Transaction
Bayangkan sebuah proses checkout melakukan tiga operasi:
- Membuat order.
- Mengurangi stok produk.
- Menyimpan pembayaran.
Kode tanpa transaction mungkin terlihat seperti berikut:
$order = Order::create([
'user_id' => $user->id,
'total' => $total,
]);
$product->decrement('stock', $quantity);
Payment::create([
'order_id' => $order->id,
'amount' => $total,
]);
Bayangkan query pembayaran gagal setelah order berhasil dibuat dan stok sudah dikurangi.
Hasilnya dapat menjadi seperti:
Order -> berhasil
Stock -> berkurang
Payment -> gagal
Database sekarang berada dalam kondisi yang tidak diinginkan.
Order mungkin sudah ada, stok sudah berkurang, tetapi payment belum tercatat.
ACID pada Database Transaction
Database transaction umumnya dikaitkan dengan empat karakteristik utama yang dikenal sebagai ACID:
- Atomicity — seluruh operasi berhasil atau dibatalkan sebagai satu unit.
- Consistency — database berpindah dari satu kondisi valid ke kondisi valid lainnya.
- Isolation — transaction yang berjalan secara bersamaan memiliki aturan isolasi tertentu.
- Durability — perubahan yang sudah di-commit tetap tersimpan sesuai jaminan database.
Laravel tidak menciptakan konsep ACID tersebut. Laravel menggunakan kemampuan transaction yang disediakan oleh database connection yang digunakan aplikasi.
Apa Fungsi DB::transaction()?
DB::transaction() digunakan untuk menjalankan beberapa operasi database di dalam sebuah transaction.
Contoh paling sederhana:
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
User::create([
'name' => 'Budi',
'email' => '[email protected]',
]);
Profile::create([
'bio' => 'Developer',
]);
});
Jika callback selesai tanpa exception, transaction akan di-commit.
Jika terjadi exception, transaction akan di-rollback.
Bagaimana DB::transaction() Bekerja?
Secara konseptual, prosesnya seperti berikut:
DB::transaction()
|
v
BEGIN TRANSACTION
|
v
Query 1
|
v
Query 2
|
v
Query 3
|
v
Exception?
| |
Tidak Ya
| |
v v
COMMIT ROLLBACK
Dengan pendekatan tersebut, developer tidak perlu menulis beginTransaction(), commit(), dan rollBack() secara manual untuk kasus umum.
Contoh Transaction untuk Checkout
Misalnya proses checkout melakukan beberapa perubahan:
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($user, $product, $quantity) {
$order = Order::create([
'user_id' => $user->id,
'total' => $product->price * $quantity,
]);
$order->items()->create([
'product_id' => $product->id,
'quantity' => $quantity,
'price' => $product->price,
]);
$product->decrement('stock', $quantity);
Payment::create([
'order_id' => $order->id,
'amount' => $product->price * $quantity,
'status' => 'pending',
]);
});
Jika salah satu operasi database di dalam callback menghasilkan exception, transaction dapat di-rollback.
Kapan Harus Menggunakan DB::transaction()?
Aturan praktisnya cukup sederhana:
Gunakan database transaction ketika beberapa perubahan database harus berhasil atau gagal bersama-sama.
Beberapa contoh kasus penting adalah sebagai berikut.
1. Checkout E-Commerce
Checkout biasanya mengubah beberapa tabel sekaligus:
- orders
- order_items
- products
- payments
- inventory
Jika perubahan tersebut merupakan satu unit proses bisnis, transaction sangat relevan.
2. Transfer Saldo
Transfer saldo adalah contoh klasik database transaction.
Misalnya:
Account A
|
| - Rp100.000
v
Database
|
| + Rp100.000
v
Account B
Jika saldo A berkurang tetapi saldo B gagal bertambah, data menjadi tidak konsisten.
Karena itu, kedua perubahan tersebut sebaiknya berada dalam satu transaction.
DB::transaction(function () use ($from, $to, $amount) {
$from->decrement('balance', $amount);
$to->increment('balance', $amount);
Transfer::create([
'from_account_id' => $from->id,
'to_account_id' => $to->id,
'amount' => $amount,
]);
});
3. Membuat Parent dan Child Data
Misalnya membuat invoice dan invoice items:
DB::transaction(function () use ($data) {
$invoice = Invoice::create([
'customer_id' => $data['customer_id'],
'total' => $data['total'],
]);
foreach ($data['items'] as $item) {
$invoice->items()->create([
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
]);
}
});
Jika pembuatan salah satu item gagal, transaction dapat membatalkan perubahan invoice dan item yang sudah dibuat di dalam transaction tersebut.
4. Update Beberapa Tabel yang Saling Berkaitan
Contohnya ketika mengubah status order sekaligus mencatat history:
DB::transaction(function () use ($order) {
$order->update([
'status' => 'completed',
]);
OrderHistory::create([
'order_id' => $order->id,
'status' => 'completed',
]);
});
Jika history wajib ada ketika status berubah, kedua operasi tersebut sebaiknya dikelola sebagai satu unit.
Kapan Tidak Harus Menggunakan Transaction?
Tidak semua query database membutuhkan transaction eksplisit.
Misalnya query sederhana:
$user = User::find($id);
atau:
$user->update([
'name' => 'Andi',
]);
Jika hanya ada satu operasi database dan tidak ada ketergantungan dengan perubahan database lainnya, transaction biasanya tidak memberikan manfaat yang berarti.
Contoh lain:
Product::where('id', $id)->update([
'is_active' => false,
]);
Untuk satu statement sederhana seperti ini, transaction eksplisit biasanya tidak diperlukan.
Transaction Bukan untuk Semua Jenis Operasi
Database transaction bekerja pada operasi yang berada dalam database transaction tersebut. Jangan menganggap transaction dapat otomatis membatalkan semua efek eksternal.
Contohnya:
DB::transaction(function () use ($order) {
$order->update([
'status' => 'paid',
]);
Mail::to($order->user)->send(
new PaymentSuccessMail($order)
);
});
Jika email berhasil dikirim tetapi kemudian terjadi exception pada operasi database berikutnya, database dapat di-rollback, tetapi email yang sudah dikirim tidak dapat “di-rollback”.
Ini adalah salah satu alasan mengapa side effect eksternal perlu dirancang dengan hati-hati.
Transaction dan Email
Jika email harus dikirim setelah transaction berhasil, jangan menganggap pengiriman email tersebut bagian dari atomic transaction database.
Lebih aman memisahkan perubahan database dan pekerjaan asynchronous.
DB::transaction(function () use ($order) {
$order->update([
'status' => 'paid',
]);
});
SendPaymentEmail::dispatch($order);
Untuk kasus production, dispatch job dapat diatur agar berjalan setelah transaction berhasil di-commit, sesuai kebutuhan aplikasi dan versi Laravel yang digunakan.
Manual Transaction
Selain DB::transaction(), Laravel juga menyediakan API manual untuk transaction.
DB::beginTransaction();
try {
// Query database
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
Pendekatan ini berguna ketika developer membutuhkan kontrol lebih detail terhadap lifecycle transaction.
Namun, untuk kebanyakan kasus aplikasi, DB::transaction() lebih ringkas dan mudah dibaca.
DB::transaction() vs Manual Transaction
| Pendekatan | Kelebihan | Kapan Digunakan |
|---|---|---|
DB::transaction() | Ringkas dan otomatis menangani commit/rollback pada exception. | Kasus transaction umum. |
| Manual transaction | Kontrol lifecycle lebih detail. | Kasus khusus yang membutuhkan kontrol eksplisit. |
Rollback Otomatis
Salah satu keuntungan utama DB::transaction() adalah rollback otomatis ketika callback menghasilkan exception.
DB::transaction(function () {
Order::create([
'user_id' => 1,
'total' => 100000,
]);
throw new RuntimeException('Simulasi error');
Payment::create([
'order_id' => 1,
'amount' => 100000,
]);
});
Karena exception terjadi, transaction tidak berhasil diselesaikan dan perubahan yang berada dalam transaction dapat di-rollback.
Jangan Menelan Exception
Salah satu kesalahan yang sering terjadi adalah menangkap exception tetapi tidak melemparkannya kembali.
Contoh yang perlu dihindari:
DB::transaction(function () {
try {
Order::create([
'user_id' => 1,
]);
throw new RuntimeException('Error');
} catch (\Throwable $e) {
Log::error($e->getMessage());
}
});
Dengan menangkap exception di dalam callback dan tidak melemparkannya kembali, callback dapat terlihat selesai secara normal dari sudut pandang transaction.
Jika memang perlu melakukan logging, exception dapat dicatat lalu dilempar kembali:
DB::transaction(function () {
try {
Order::create([
'user_id' => 1,
]);
throw new RuntimeException('Error');
} catch (\Throwable $e) {
Log::error($e->getMessage());
throw $e;
}
});
Menangani Exception di Luar Transaction
Pola yang sering lebih bersih adalah membiarkan exception keluar dari callback dan menanganinya pada layer di luar transaction.
try {
DB::transaction(function () use ($data) {
$order = Order::create([
'user_id' => $data['user_id'],
'total' => $data['total'],
]);
$order->items()->createMany($data['items']);
});
} catch (\Throwable $e) {
Log::error('Gagal membuat order', [
'error' => $e->getMessage(),
]);
throw $e;
}
Pendekatan ini memisahkan tanggung jawab transaction dan error handling.
Return Value dari DB::transaction()
Callback transaction dapat mengembalikan nilai.
$order = DB::transaction(function () use ($data) {
return Order::create([
'user_id' => $data['user_id'],
'total' => $data['total'],
]);
});
Nilai yang dikembalikan callback akan menjadi hasil dari pemanggilan DB::transaction().
Hal ini berguna ketika transaction menghasilkan model yang akan digunakan pada proses berikutnya.
Transaction dengan Eloquent
Transaction dapat digunakan dengan Eloquent tanpa masalah.
DB::transaction(function () {
$user = User::create([
'name' => 'Budi',
'email' => '[email protected]',
]);
$user->profile()->create([
'bio' => 'Laravel Developer',
]);
});
Query Eloquent tersebut tetap menggunakan database connection yang berada dalam transaction.
Transaction dengan Query Builder
Query Builder juga dapat digunakan:
DB::transaction(function () {
DB::table('orders')->insert([
'user_id' => 1,
'total' => 150000,
]);
DB::table('inventory')
->where('product_id', 10)
->decrement('stock', 1);
});
Eloquent dan Query Builder dapat digunakan dalam transaction yang sama selama menggunakan connection database yang sesuai.
Transaction dengan Database Connection Tertentu
Jika aplikasi memiliki beberapa database connection, transaction harus dilakukan pada connection yang benar.
DB::connection('mysql')->transaction(function () {
DB::connection('mysql')
->table('orders')
->insert([
'user_id' => 1,
]);
});
Perlu diperhatikan bahwa transaction pada satu database connection tidak otomatis mencakup operasi pada database connection lain.
Cross-Database Transaction
Misalnya aplikasi memiliki:
MySQL
|
+-- orders
PostgreSQL
|
+-- analytics
Jika satu proses menulis ke MySQL dan PostgreSQL, transaction biasa pada masing-masing connection tidak otomatis membuat keduanya menjadi satu atomic transaction global.
Kasus seperti ini membutuhkan desain arsitektur yang lebih hati-hati, misalnya outbox pattern, event-driven architecture, atau mekanisme distributed transaction jika memang benar-benar diperlukan.
Nested Transaction
Aplikasi yang kompleks terkadang memiliki method yang memanggil service lain, dan masing-masing service mungkin menggunakan transaction.
Contoh:
DB::transaction(function () {
$this->createOrder();
$this->createPayment();
});
Lalu method di dalamnya mungkin juga menggunakan transaction.
public function createOrder()
{
return DB::transaction(function () {
// ...
});
}
Laravel menangani nested transaction sesuai kemampuan transaction management pada connection yang digunakan, termasuk penggunaan savepoint pada driver yang mendukungnya.
Namun, nested transaction sebaiknya tidak digunakan secara sembarangan karena dapat membuat alur transaction sulit dipahami.
Transaction dan Deadlock
Pada aplikasi dengan banyak concurrent request, transaction dapat mengalami deadlock.
Contoh konseptual:
Transaction A
|
+-- Lock Row 1
|
+-- Menunggu Row 2
Transaction B
|
+-- Lock Row 2
|
+-- Menunggu Row 1
Keduanya saling menunggu sehingga database dapat mendeteksi deadlock.
Deadlock bukan berarti transaction tidak boleh digunakan. Justru transaction merupakan bagian penting dalam operasi concurrent yang membutuhkan konsistensi. Namun, kode perlu dirancang agar risiko deadlock dapat diminimalkan dan error tertentu dapat ditangani.
Retry Transaction
Laravel menyediakan dukungan untuk mencoba kembali transaction ketika terjadi deadlock melalui parameter attempts pada API transaction.
DB::transaction(function () {
// Operasi database
}, 5);
Angka tersebut menentukan jumlah percobaan transaction sesuai mekanisme yang disediakan Laravel.
Retry bukan solusi untuk semua masalah database. Jika deadlock terjadi karena desain query atau urutan locking yang buruk, akar masalah tetap perlu diperbaiki.
Mengurangi Risiko Deadlock
Beberapa praktik yang dapat membantu:
- Jaga transaction tetap singkat.
- Gunakan urutan akses resource yang konsisten.
- Hindari query yang tidak diperlukan di dalam transaction.
- Pastikan index database sesuai kebutuhan query.
- Hindari menunggu operasi eksternal di dalam transaction.
- Analisis deadlock pada database log jika terjadi secara berulang.
Jangan Memanggil API Eksternal di Dalam Transaction
Contoh yang berisiko:
DB::transaction(function () use ($order) {
$order->update([
'status' => 'processing',
]);
$response = Http::post('https://payment.example.com/charge', [
'amount' => $order->total,
]);
$order->update([
'status' => 'paid',
]);
});
Masalahnya adalah HTTP request dapat membutuhkan waktu lama atau mengalami timeout. Selama proses tersebut, transaction database tetap terbuka.
Transaction yang terlalu lama dapat mempertahankan lock lebih lama dan meningkatkan contention.
Lebih baik memisahkan proses database dan komunikasi dengan sistem eksternal menggunakan pola yang sesuai.
Transaction Harus Singkat
Idealnya, transaction hanya berisi operasi database yang benar-benar harus atomic.
Hindari:
DB::transaction(function () {
// Query database
sleep(10);
// HTTP request
// Proses file besar
// Query database lagi
});
Semakin lama transaction berjalan, semakin lama resource database dapat terkunci dan semakin besar kemungkinan terjadi contention.
Transaction dan File Upload
File system tidak mengikuti transaction database secara otomatis.
Misalnya:
DB::transaction(function () use ($request) {
$path = $request->file('document')->store('documents');
Document::create([
'path' => $path,
]);
});
Jika insert database gagal setelah file berhasil disimpan, rollback database tidak otomatis menghapus file tersebut.
Karena itu, database transaction dan file storage perlu memiliki mekanisme kompensasi atau cleanup tersendiri.
Transaction dan Event
Event yang dipicu di tengah transaction perlu diperhatikan dengan hati-hati.
Jika listener melakukan side effect eksternal sebelum transaction berhasil di-commit, side effect tersebut tidak otomatis dibatalkan ketika transaction rollback.
Untuk proses penting, pertimbangkan event atau job yang dieksekusi setelah transaction berhasil.
Transaction untuk Menjaga Invariant Data
Salah satu cara terbaik memahami kebutuhan transaction adalah melihat invariant atau kondisi yang harus selalu benar.
Misalnya sistem saldo memiliki aturan:
Total debit = Total credit
Jika sebuah transfer mengubah dua sisi transaksi, perubahan tersebut perlu dirancang agar invariant tidak rusak di tengah proses yang terlihat oleh transaction lain.
Transaction membantu database menjaga perubahan tersebut sebagai satu unit sesuai isolation level yang berlaku.
Transaction Bukan Pengganti Database Constraint
Transaction sangat penting, tetapi tidak berarti semua validasi harus dilakukan di application layer.
Database constraint tetap penting.
Contohnya:
- Primary key.
- Unique constraint.
- Foreign key.
- Not null constraint.
- Check constraint jika didukung dan sesuai kebutuhan.
Transaction dan constraint memiliki fungsi yang berbeda dan dapat saling melengkapi.
Contoh Kasus: Transfer Saldo yang Lebih Aman
Untuk operasi saldo, jangan hanya menggunakan transaction. Pastikan juga ada validasi saldo dan mekanisme concurrency yang sesuai.
DB::transaction(function () use ($fromId, $toId, $amount) {
$from = Account::whereKey($fromId)
->lockForUpdate()
->firstOrFail();
$to = Account::whereKey($toId)
->lockForUpdate()
->firstOrFail();
if ($from->balance < $amount) { throw new RuntimeException('Saldo tidak mencukupi.'); } $from->decrement('balance', $amount);
$to->increment('balance', $amount);
Transfer::create([
'from_account_id' => $from->id,
'to_account_id' => $to->id,
'amount' => $amount,
]);
});
lockForUpdate() digunakan untuk meminta row lock pada database yang mendukung mekanisme tersebut, sehingga concurrent transaction tidak dapat dengan bebas memodifikasi row yang sama selama lock tersebut aktif.
Detail perilaku locking tetap bergantung pada database engine dan isolation level yang digunakan.
Transaction dan Concurrency
Concurrency menjadi alasan penting lainnya mengapa transaction perlu dipahami.
Bayangkan dua request melakukan pembelian produk terakhir secara bersamaan.
Stock awal = 1
Request A -> Beli 1
Request B -> Beli 1
Tanpa locking/concurrency control
|
v
Keduanya dapat membaca stock = 1
|
v
Risiko overselling
Transaction saja belum tentu cukup untuk menyelesaikan seluruh masalah concurrency. Pada kasus tertentu, diperlukan locking, constraint, atomic update, atau strategi concurrency control lainnya.
Transaction dan Atomic Update
Untuk operasi sederhana, atomic update dapat menjadi pilihan yang lebih baik daripada membaca lalu menulis secara terpisah.
Contoh:
Product::whereKey($productId)
->where('stock', '>', 0)
->decrement('stock');
Hasil affected rows dapat diperiksa untuk mengetahui apakah stok berhasil dikurangi.
$updated = Product::whereKey($productId)
->where('stock', '>', 0)
->decrement('stock');
if ($updated === 0) {
throw new RuntimeException('Stok habis.');
}
Pendekatan seperti ini dapat mengurangi race condition untuk operasi tertentu, meskipun kebutuhan keseluruhan tetap harus dianalisis berdasarkan alur bisnis.
Contoh Service Laravel dengan Transaction
Untuk aplikasi yang lebih besar, transaction sering kali ditempatkan pada service layer.
namespace App\Services;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderService
{
public function create(array $data): Order
{
return DB::transaction(function () use ($data) {
$order = Order::create([
'user_id' => $data['user_id'],
'total' => $data['total'],
]);
$order->items()->createMany($data['items']);
return $order;
});
}
}
Controller kemudian menjadi lebih sederhana:
public function store(Request $request, OrderService $orderService)
{
$order = $orderService->create(
$request->validated()
);
return response()->json($order, 201);
}
Pendekatan ini membantu memisahkan HTTP layer dari business logic.
Transaction di Controller atau Service?
Keduanya memungkinkan, tetapi service layer sering lebih mudah dirawat ketika business process semakin kompleks.
| Lokasi | Kelebihan |
|---|---|
| Controller | Sederhana untuk proses kecil. |
| Service | Lebih mudah digunakan ulang dan diuji untuk business process kompleks. |
| Repository | Dapat digunakan jika arsitektur aplikasi memang membutuhkan abstraction tersebut. |
Kesalahan Umum Database Transaction
1. Menggunakan Transaction untuk Semua Query
Transaction tidak perlu membungkus setiap query database. Gunakan ketika ada alasan atomicity atau consistency yang jelas.
2. Transaction Terlalu Panjang
Jangan memasukkan proses HTTP, upload file besar, atau operasi eksternal yang lambat ke dalam transaction tanpa alasan kuat.
3. Menganggap Rollback Mengembalikan Semua Efek
Rollback hanya berlaku pada perubahan yang berada dalam transaction database tersebut. Email, API call, file, dan sistem eksternal tidak otomatis ikut rollback.
4. Tidak Memikirkan Concurrency
Transaction bukan berarti otomatis bebas dari race condition. Locking dan strategi concurrency mungkin tetap diperlukan.
5. Mengabaikan Database Engine
Perilaku transaction, locking, dan isolation bergantung pada database engine dan konfigurasi connection.
Best Practice Laravel Database Transaction
- Gunakan
DB::transaction()untuk proses multi-query yang harus atomic. - Jaga transaction tetap singkat.
- Jangan memasukkan HTTP request eksternal ke dalam transaction jika tidak diperlukan.
- Jangan mengandalkan transaction untuk membatalkan email atau file upload.
- Gunakan database constraint sebagai lapisan integritas tambahan.
- Perhatikan race condition pada operasi concurrent.
- Gunakan locking jika memang diperlukan.
- Gunakan retry untuk deadlock sesuai kebutuhan.
- Gunakan shared database connection yang benar.
- Letakkan transaction pada service layer jika business process cukup kompleks.
- Monitor deadlock dan slow transaction di production.
Checklist: Apakah Saya Membutuhkan Transaction?
Gunakan pertanyaan berikut sebelum menambahkan DB::transaction():
- Apakah proses melakukan lebih dari satu perubahan database?
- Apakah perubahan tersebut saling bergantung?
- Apakah semuanya harus berhasil atau semuanya harus dibatalkan?
- Apakah ada risiko data menjadi tidak konsisten jika salah satu query gagal?
- Apakah operasi tersebut dilakukan secara concurrent oleh banyak request?
Jika jawabannya “ya” untuk beberapa pertanyaan tersebut, database transaction kemungkinan besar diperlukan.
Contoh Alur Pengambilan Keputusan
Ada beberapa operasi database?
|
Tidak
|
v
Transaction biasanya
tidak diperlukan
|
Ya
|
v
Apakah operasi harus berhasil
sebagai satu unit?
|
+---+---+
| |
Tidak Ya
| |
v v
Mungkin Gunakan
tidak DB::transaction()
perlu
transaction
Ringkasan
| Kondisi | Rekomendasi |
|---|---|
| Satu query sederhana | Biasanya tidak perlu transaction eksplisit. |
| Insert parent + child | Gunakan transaction jika harus atomic. |
| Checkout | Gunakan transaction untuk perubahan database yang saling terkait. |
| Transfer saldo | Gunakan transaction dan pertimbangkan locking/concurrency control. |
| API eksternal | Jangan menganggap API call dapat di-rollback oleh database transaction. |
| File upload | Perlu mekanisme cleanup terpisah dari database transaction. |
| Deadlock | Perbaiki desain concurrency dan gunakan retry bila sesuai. |
Kesimpulan
Laravel Database Transaction digunakan ketika beberapa operasi database harus dianggap sebagai satu kesatuan. Jika seluruh operasi berhasil, perubahan di-commit. Jika terjadi exception, perubahan dalam transaction dapat di-rollback.
Untuk kebanyakan kasus aplikasi Laravel, DB::transaction() adalah pilihan yang praktis dan mudah dibaca dibandingkan mengelola beginTransaction(), commit(), dan rollBack() secara manual.
Contoh kasus yang sangat cocok menggunakan transaction adalah checkout, transfer saldo, pembuatan invoice beserta item, perubahan beberapa tabel yang saling berkaitan, dan proses bisnis lain yang tidak boleh meninggalkan database dalam kondisi setengah selesai.
Namun, transaction bukan solusi untuk semua masalah. Transaction tidak otomatis membatalkan email, file, HTTP request, atau perubahan pada sistem eksternal. Selain itu, transaction juga perlu dipadukan dengan database constraint, locking, atomic update, dan strategi concurrency ketika aplikasi menangani banyak request secara bersamaan.
Prinsip sederhananya adalah: jika beberapa perubahan database harus berhasil atau gagal bersama-sama, pertimbangkan menggunakan DB::transaction().

