Home / Artikel / Web Development
Web Development

Data Tidak Setengah Jadi: Memahami Transaction di PHP dan MySQL

Satu proses di website sering mengubah beberapa tabel sekaligus. Dengan transaction, perubahan itu bisa disimpan sebagai satu kesatuan atau dibatalkan seluruhnya ketika salah satu langkah gagal.

Data Tidak Setengah Jadi: Memahami Transaction di PHP dan MySQL

Bayangkan proses checkout sederhana. Sistem mengurangi stok, membuat pesanan, mencatat pembayaran, lalu mengirim status ke pengguna. Jika stok sudah berkurang tetapi pembuatan pesanan gagal, data langsung berada dalam kondisi yang membingungkan: barang terlihat habis, tetapi pesanan tidak pernah muncul.

Masalah seperti ini bukan selalu disebabkan oleh query yang salah. Sering kali, aplikasinya memang tidak memberi tahu database bahwa beberapa perubahan tersebut sebenarnya adalah satu pekerjaan yang harus berhasil bersama-sama. Di sinilah transaction dibutuhkan.

Apa itu transaction?

Transaction adalah cara mengelompokkan beberapa operasi database menjadi satu unit kerja. Unit ini memiliki dua kemungkinan: semua perubahan disimpan, atau semua perubahan dibatalkan.

Dalam istilah yang lebih sederhana, transaction bekerja seperti tombol “simpan” untuk satu rangkaian proses. Selama belum diakhiri dengan COMMIT, perubahan belum dianggap final. Jika terjadi masalah, aplikasi dapat menjalankan ROLLBACK untuk mengembalikan database ke kondisi sebelum proses dimulai.

MySQL menjalankan setiap perintah secara otomatis dalam mode autocommit secara default. Artinya, satu query biasanya langsung dianggap selesai setelah berhasil. Untuk menggabungkan beberapa query, aplikasi perlu memulai transaction secara eksplisit dengan START TRANSACTION atau melalui API seperti PDO::beginTransaction(). Dokumentasi MySQL dan dokumentasi PHP PDO menjelaskan perilaku ini secara lebih rinci.

Kapan transaction benar-benar diperlukan?

Tidak semua query perlu dibungkus transaction. Untuk mengambil daftar artikel atau mengubah satu kolom sederhana, mekanismenya sering tidak perlu dibuat rumit. Transaction menjadi penting ketika satu aksi pengguna menyebabkan beberapa perubahan yang saling bergantung.

  • Checkout: membuat pesanan, menyimpan detail barang, dan mengurangi stok.
  • Transfer saldo: mengurangi saldo dari satu akun dan menambah saldo ke akun lain.
  • Pendaftaran pengguna: membuat akun utama sekaligus profil, preferensi, atau data organisasi.
  • Penghapusan data: menghapus data utama beserta relasi yang memang harus ikut dihapus.
  • Proses pembayaran: menyimpan transaksi, memperbarui status invoice, dan mencatat riwayat.

Patokannya sederhana: jika perubahan pertama tidak boleh tersimpan ketika perubahan berikutnya gagal, gunakan transaction.

Pola dasar transaction dengan PDO

Berikut contoh sederhana saat aplikasi membuat pesanan dan detail pesanan. Contoh ini mengasumsikan tabel menggunakan storage engine InnoDB agar mendukung transaction.

<?php
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

try {
    $pdo->beginTransaction();

    $stmt = $pdo->prepare(
        'INSERT INTO orders (user_id, total) VALUES (?, ?)'
    );
    $stmt->execute([$userId, $total]);

    $orderId = $pdo->lastInsertId();

    $stmt = $pdo->prepare(
        'INSERT INTO order_items (order_id, product_id, quantity)
         VALUES (?, ?, ?)'
    );
    $stmt->execute([$orderId, $productId, $quantity]);

    $pdo->commit();
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    throw $e;
}
?>

Urutannya penting. Aplikasi memulai transaction, menjalankan seluruh operasi, lalu memanggil commit() hanya setelah semua langkah selesai. Jika salah satu operasi melempar error, blok catch menjalankan rollBack().

Pengaturan PDO::ATTR_ERRMODE ke PDO::ERRMODE_EXCEPTION membuat kegagalan query lebih mudah ditangani. Tanpa exception, kode bisa saja terus berjalan setelah query gagal dan akhirnya tetap menganggap proses berhasil.

Transaction bukan pengganti validasi

Transaction menjaga konsistensi perubahan, tetapi tidak otomatis memastikan bahwa data yang masuk benar. Jika jumlah barang bernilai negatif, email tidak valid, atau harga berasal dari input pengguna tanpa pemeriksaan, transaction tidak akan menyelesaikan masalah tersebut.

Validasi tetap harus dilakukan sebelum atau selama proses. Selain itu, gunakan prepared statement untuk nilai dari pengguna. Transaction dan prepared statement menyelesaikan dua persoalan berbeda: transaction mengatur kesatuan perubahan, sedangkan prepared statement membantu memisahkan data dari struktur SQL.

Jangan membuat transaction terlalu panjang

Transaction yang terlalu lama dapat menahan lock dan membuat proses lain menunggu. Pada aplikasi ramai, ini dapat terlihat sebagai halaman yang kadang lambat atau error karena batas waktu koneksi.

Karena itu, masukkan hanya operasi database yang memang harus atomik. Jangan melakukan hal-hal lambat di dalam transaction, seperti:

  • memanggil API pembayaran atau layanan pihak ketiga;
  • mengunggah file berukuran besar;
  • mengirim email;
  • menunggu input pengguna;
  • menjalankan proses yang tidak perlu mengubah database.

Pola yang lebih aman adalah menyelesaikan perubahan penting di database terlebih dahulu, melakukan commit, lalu memproses pekerjaan tambahan melalui antrean atau mekanisme terpisah. Namun, desain ini perlu disesuaikan dengan kebutuhan. Jika pengiriman email gagal setelah pesanan tersimpan, aplikasi mungkin membutuhkan status “email belum terkirim” dan mekanisme pengiriman ulang.

Gunakan tabel InnoDB dan pahami batasannya

Di MySQL, dukungan transaction sangat bergantung pada storage engine. InnoDB menyediakan fitur transaction dan model ACID, yaitu prinsip yang membantu menjaga atomicity, consistency, isolation, dan durability. Dokumentasi MySQL tentang InnoDB dan ACID menjelaskan peran fitur tersebut.

Pastikan tabel yang terlibat memakai InnoDB. Selain itu, jangan menganggap semua perintah dapat dibatalkan. Beberapa perintah definisi struktur tabel, seperti operasi DDL tertentu, dapat memicu implicit commit pada MySQL. Karena itu, perubahan skema sebaiknya dipisahkan dari transaction bisnis.

Kesalahan yang sering terjadi

Memanggil commit terlalu cepat

Jika aplikasi menjalankan commit() setelah query pertama, query berikutnya tidak lagi menjadi bagian dari transaction yang sama. Tentukan dulu batas satu proses bisnis sebelum menempatkan commit.

Lupa melakukan rollback

Ketika exception terjadi, koneksi dapat tetap memiliki transaction yang belum selesai. Gunakan struktur try-catch dan periksa inTransaction() sebelum melakukan rollback.

Menganggap rollback memperbaiki semua hal

Rollback hanya membatalkan perubahan pada database dalam transaction tersebut. File yang sudah diunggah, email yang sudah terkirim, atau request ke layanan eksternal tidak otomatis ikut kembali seperti semula.

Yang bisa dilakukan sekarang

  1. Daftar proses di aplikasi yang mengubah lebih dari satu tabel.
  2. Tandai proses yang tidak boleh menghasilkan data setengah jadi.
  3. Bungkus proses tersebut dengan beginTransaction(), commit(), dan rollBack().
  4. Uji skenario gagal di tengah proses, bukan hanya skenario berhasil.
  5. Periksa apakah tabel yang terlibat menggunakan InnoDB.
  6. Catat error dengan cukup detail tanpa menyimpan data sensitif ke log.

Transaction bukan fitur yang hanya dibutuhkan aplikasi besar. Pada website kecil pun, satu proses yang menyentuh beberapa tabel sudah cukup menjadi alasan untuk menggunakannya. Tujuannya bukan membuat kode terlihat lebih canggih, melainkan memastikan database tidak menyimpan separuh cerita ketika aplikasi gagal menyelesaikan pekerjaannya.

Sumber & bacaan lebih lanjut

– Rio Yotto @rioyotto