Postgres 17 - Memahami Transactions dan Locking
Memahami Transactions dan Locking
Pendahuluan
Transaksi dan penguncian (locking) adalah dua konsep utama dalam pengelolaan basis data, termasuk PostgreSQL. Keduanya memastikan data tetap konsisten, aman, dan dapat diakses secara konkuren oleh banyak pengguna tanpa konflik. Artikel ini dirancang untuk menjelaskan transaksi dan penguncian (locking) secara terstruktur, jelas, dan mudah dipahami.
Daftar Isi:
Apa Itu Transaksi di PostgreSQL?
Fitur-Fitur Transaksi
Perintah Dasar: BEGIN, COMMIT, ROLLBACK
Transaction Chaining
Savepoint
DDL Transaksional
Apa Itu Penguncian (Locking) dan Mengapa Penting?
Mekanisme Penguncian di PostgreSQL
Multi-Version Concurrency Control (MVCC)
Penguncian Baris (Row-Level Locking)
Penguncian Tabel (Table-Level Locking)
SELECT FOR UPDATE dan FOR SHARE
SKIP LOCKED
Advisory Locks
Tingkat Isolasi Transaksi
Read Committed
Repeatable Read
Serializable
Menangani Masalah: Deadlock dan Konflik Serialisasi
Optimasi Penyimpanan dan Pembersihan
VACUUM dan Autovacuum
Transaction Wraparound
VACUUM FULL
Kesimpulan
1. Apa Itu Transaksi di PostgreSQL?
Transaksi adalah sekumpulan perintah SQL yang dijalankan sebagai satu unit kerja. Semua perintah di dalamnya harus berhasil (commit) atau dibatalkan (rollback) jika ada yang gagal. Ini memastikan data tetap konsisten.
Di PostgreSQL, setiap perintah SQL adalah transaksi, bahkan perintah sederhana seperti SELECT. Contoh:
Perintah ini dijalankan sebagai transaksi tunggal. Fungsi now() mengembalikan waktu transaksi, bukan waktu saat ini. Jika Anda ingin waktu nyata, gunakan clock_timestamp().
Untuk mengelompokkan beberapa perintah dalam satu transaksi, gunakan BEGIN, COMMIT, atau ROLLBACK. Contoh:
Jika salah satu perintah gagal, Anda bisa menggunakan ROLLBACK untuk membatalkan semua perubahan.
2. Fitur-Fitur Transaksi
a. Perintah Dasar: BEGIN, COMMIT, ROLLBACK
BEGIN: Memulai transaksi, memungkinkan beberapa perintah dijalankan sebagai satu unit.
COMMIT: Menyimpan semua perubahan dalam transaksi ke basis data.
ROLLBACK: Membatalkan semua perubahan dalam transaksi, mengembalikan data ke keadaan sebelumnya.
Contoh:
Alternatif untuk COMMIT adalah END, dan untuk ROLLBACK adalah ABORT. Keduanya memiliki fungsi yang sama.
b. Transaction Chaining
Fitur transaction chaining memungkinkan transaksi baru dimulai otomatis dengan properti yang sama setelah transaksi sebelumnya di-commit. Ini berguna untuk mengurangi jumlah perintah yang dikirim ke server, terutama dalam sistem dengan latensi tinggi.
Contoh:
Perintah COMMIT AND CHAIN mempertahankan pengaturan seperti READ ONLY, sehingga Anda tidak perlu mengatur ulang.
c. Savepoint
Savepoint memungkinkan Anda membuat "titik simpan" dalam transaksi, sehingga Anda bisa kembali ke titik tersebut jika terjadi kesalahan tanpa membatalkan seluruh transaksi.
Contoh:
Setelah ROLLBACK TO SAVEPOINT, transaksi kembali ke keadaan sebelum kesalahan, dan Anda bisa melanjutkan. Anda juga bisa menghapus savepoint dengan RELEASE SAVEPOINT.
d. DDL Transaksional
PostgreSQL mendukung Data Definition Language (DDL) dalam transaksi, seperti CREATE TABLE atau ALTER TABLE. Ini berarti Anda bisa membatalkan perubahan struktur data jika diperlukan.
Contoh:
Fitur ini sangat berguna untuk deployment perangkat lunak, memastikan perubahan struktur dilakukan secara atomik.
3. Apa Itu Penguncian (Locking) dan Mengapa Penting?
Penguncian adalah mekanisme untuk mengatur akses konkuren ke data, mencegah konflik antara transaksi. Tanpa penguncian yang tepat, aplikasi bisa mengalami masalah seperti:
Data tidak konsisten: Dua transaksi mengubah data yang sama secara bersamaan.
Performa buruk: Transaksi menunggu terlalu lama (waiting adalah bentuk eksekusi paling lambat).
Deadlock: Dua transaksi saling mengunci, menyebabkan kebuntuan.
Penguncian yang baik memastikan konkurensi tinggi, performa optimal, dan keandalan data.
4. Mekanisme Penguncian di PostgreSQL
a. Multi-Version Concurrency Control (MVCC)
PostgreSQL menggunakan MVCC untuk memungkinkan pembacaan dan penulisan data secara konkuren tanpa pemblokiran. Dalam MVCC:
Setiap transaksi melihat snapshot data pada saat transaksi dimulai.
Perubahan oleh transaksi lain tidak terlihat hingga di-commit.
Contoh:
BEGIN;
BEGIN;
UPDATE t_test SET id = id + 1;
SELECT * FROM t_test;
Melihat data lama (sebelum UPDATE)
COMMIT;
COMMIT;
MVCC memastikan transaksi baca tidak terblokir oleh transaksi tulis.
b. Penguncian Baris (Row-Level Locking)
PostgreSQL hanya mengunci baris yang diubah oleh transaksi, bukan seluruh tabel. Ini memungkinkan konkurensi tinggi. Contoh:
BEGIN;
BEGIN;
UPDATE t_test SET id = id + 1 WHERE id = 1;
UPDATE t_test SET id = id + 1 WHERE id = 2;
Berhasil (id = 2)
Berhasil (id = 3)
COMMIT;
COMMIT;
Karena baris yang berbeda dikunci, kedua transaksi dapat berjalan bersamaan.
c. Penguncian Tabel (Table-Level Locking)
Untuk kasus tertentu, Anda bisa mengunci seluruh tabel menggunakan perintah LOCK. PostgreSQL mendukung beberapa mode penguncian tabel, dari yang paling ringan hingga paling ketat:
ACCESS SHARE: Diambil oleh
SELECT, hanya konflik denganACCESS EXCLUSIVE.ROW EXCLUSIVE: Diambil oleh
INSERT,UPDATE,DELETE.ACCESS EXCLUSIVE: Mengunci tabel sepenuhnya, digunakan oleh
DROP TABLE.
Contoh:
Namun, ACCESS EXCLUSIVE sangat ketat dan harus dihindari karena memblokir semua akses lain.
d. SELECT FOR UPDATE dan FOR SHARE
SELECT FOR UPDATE: Mengunci baris yang dipilih, mencegah perubahan oleh transaksi lain hingga transaksi selesai. Contoh:
SELECT FOR SHARE: Mengunci baris untuk dibaca, memungkinkan beberapa transaksi mengunci baris yang sama.
NOWAIT: Jika baris sudah terkunci, perintah gagal seketika tanpa menunggu.
SKIP LOCKED: Melewati baris yang terkunci, berguna untuk skenario seperti pemesanan tiket:
e. Advisory Locks
Advisory locks adalah kunci khusus yang tidak terkait dengan baris atau tabel, melainkan dengan angka tertentu. Kunci ini tidak dilepaskan otomatis saat COMMIT, sehingga harus dilepaskan secara eksplisit.
Contoh:
Untuk melepaskan semua advisory locks:
5. Tingkat Isolasi Transaksi
Tingkat isolasi menentukan bagaimana transaksi melihat perubahan oleh transaksi lain. PostgreSQL mendukung tiga tingkat:
a. Read Committed
Default: Setiap perintah dalam transaksi melihat snapshot data terbaru yang telah di-commit.
Contoh:
BEGIN;
BEGIN;
SELECT sum(balance) FROM t_account; -- 300
INSERT INTO t_account (balance) VALUES (100);
COMMIT;
SELECT sum(balance) FROM t_account; -- 400
COMMIT;
b. Repeatable Read
Menggunakan snapshot yang sama sepanjang transaksi, sehingga perubahan oleh transaksi lain tidak terlihat.
Contoh:
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT sum(balance) FROM t_account; -- 300
INSERT INTO t_account (balance) VALUES (100);
COMMIT;
SELECT sum(balance) FROM t_account; -- 300
COMMIT;
c. Serializable
Memastikan transaksi berjalan seolah-olah tidak ada konkurensi, tetapi transaksi dapat gagal jika ada konflik.
Contoh:
6. Menangani Masalah: Deadlock dan Konflik Serialisasi
a. Deadlock
Deadlock terjadi ketika dua transaksi saling menunggu kunci yang dipegang satu sama lain:
BEGIN;
BEGIN;
UPDATE t_deadlock SET id = id * 10 WHERE id = 1;
UPDATE t_deadlock SET id = id * 10 WHERE id = 2;
UPDATE t_deadlock SET id = id * 10 WHERE id = 2;
UPDATE t_deadlock SET id = id * 10 WHERE id = 1;
Deadlock terdeteksi, ROLLBACK
PostgreSQL mendeteksi deadlock setelah deadlock_timeout (default 1 detik) dan membatalkan salah satu transaksi.
b. Konflik Serialisasi
Dalam mode Repeatable Read atau Serializable, transaksi dapat gagal jika mencoba mengubah data yang telah diubah oleh transaksi lain. Aplikasi harus menangani kegagalan ini dengan mencoba ulang transaksi.
7. Optimasi Penyimpanan dan Pembersihan
a. VACUUM dan Autovacuum
Karena MVCC, PostgreSQL menyimpan versi lama data hingga transaksi selesai. Proses VACUUM membersihkan dead tuples (baris yang tidak lagi dibutuhkan) dan memperbarui free space map (FSM).
Contoh:
Autovacuum menjalankan VACUUM secara otomatis berdasarkan pengaturan seperti:
autovacuum_vacuum_scale_factor = 0.2: Berjalan jika 20% baris berubah.autovacuum_vacuum_threshold = 50: Minimal 50 baris berubah.
b. Transaction Wraparound
ID transaksi di PostgreSQL terbatas. Untuk mencegah wraparound, VACUUM menyesuaikan watermark transaksi. Pengaturan penting:
autovacuum_freeze_max_age = 200000000autovacuum_multixact_freeze_max_age = 400000000
c. VACUUM FULL
VACUUM FULL menulis ulang tabel dan mengembalikan ruang ke sistem file, tetapi memblokir semua akses ke tabel, sehingga harus digunakan dengan hati-hati.
8. Kesimpulan
Transaksi dan penguncian di PostgreSQL dirancang untuk mendukung konkurensi tinggi dan keandalan data. Dengan fitur seperti MVCC, savepoint, DDL transaksional, dan advisory locks, Anda dapat mengelola data secara efisien. Tingkat isolasi seperti Read Committed dan Serializable memberikan fleksibilitas untuk berbagai kebutuhan aplikasi. Dengan memahami dan mengkonfigurasi VACUUM serta autovacuum, Anda dapat menjaga performa basis data tetap optimal. Dengan pengetahuan ini, Anda dapat membangun aplikasi yang skalabel dan bebas dari masalah seperti deadlock atau konflik data.
8. Contoh Kode
Penjelasan Kode:
Kode di atas menggunakan
pgxpooluntuk membuat koneksi ke database PostgreSQL.Query
SELECT now()dijalankan sebagai transaksi tunggal, mengembalikan waktu transaksi.Skenario ini menunjukkan bahwa bahkan query sederhana dianggap sebagai transaksi.
2. Fitur-Fitur Transaksi
a. Perintah Dasar: BEGIN, COMMIT, ROLLBACK
BEGIN: Memulai transaksi.
COMMIT: Menyimpan perubahan.
ROLLBACK: Membatalkan perubahan.
Contoh Skenario: Aplikasi mentransfer saldo antar akun, memastikan kedua operasi berhasil atau dibatalkan.
Penjelasan Kode:
Kode ini mensimulasikan transfer saldo antar dua akun.
Transaksi dimulai dengan
pool.Begin(ctx).Jika salah satu
UPDATEgagal,tx.Rollback(ctx)membatalkan semua perubahan.Jika berhasil,
tx.Commit(ctx)menyimpan perubahan.
b. Transaction Chaining
Transaction chaining memungkinkan transaksi baru dimulai otomatis dengan properti yang sama setelah COMMIT.
Contoh Skenario: Aplikasi mencatat log aktivitas pengguna dalam beberapa transaksi berurutan dengan mode READ ONLY.
Penjelasan Kode:
Kode ini menggunakan
BeginTxdengan opsiReadOnlyuntuk memulai transaksi.Perintah
COMMIT AND CHAINmemulai transaksi baru dengan properti yang sama.Skenario ini mensimulasikan aplikasi yang memeriksa jumlah log berulang kali dalam mode
READ ONLY.
c. Savepoint
Savepoint memungkinkan Anda membuat titik simpan dalam transaksi untuk kembali jika terjadi kesalahan.
Contoh Skenario: Aplikasi mencoba memperbarui data pengguna, tetapi jika gagal, kembali ke titik sebelumnya tanpa membatalkan seluruh transaksi.
Penjelasan Kode:
Kode ini mensimulasikan aplikasi yang mencoba menyimpan data pengguna.
Jika operasi gagal (misalnya, pembagian dengan nol), transaksi kembali ke
SAVEPOINTdan melanjutkan dengan operasi lain.Data
AlicedanBobtetap tersimpan setelahCOMMIT.
d. DDL Transaksional
PostgreSQL mendukung perintah DDL (seperti CREATE TABLE) dalam transaksi, yang dapat dibatalkan dengan ROLLBACK.
Contoh Skenario: Aplikasi mencoba membuat tabel sementara untuk pengujian, tetapi membatalkannya jika tidak diperlukan.
Penjelasan Kode:
Kode ini mensimulasikan pembuatan tabel sementara dalam transaksi.
Jika aplikasi memutuskan untuk tidak menggunakan tabel,
ROLLBACKmenghapus tabel tersebut.Query verifikasi memastikan tabel tidak ada setelah
ROLLBACK.
3. Apa Itu Penguncian (Locking) dan Mengapa Penting?
Penguncian mengatur akses konkuren ke data untuk mencegah konflik, seperti data tidak konsisten atau deadlock. Penguncian yang tepat meningkatkan performa dan keandalan aplikasi.
4. Mekanisme Penguncian di PostgreSQL
a. Multi-Version Concurrency Control (MVCC)
MVCC memungkinkan pembacaan dan penulisan data secara konkuren dengan menyimpan snapshot data untuk setiap transaksi.
Contoh Skenario: Aplikasi membaca saldo akun sementara transaksi lain memperbarui saldo.
Penjelasan Kode:
Dua goroutine mensimulasikan dua transaksi konkuren.
Transaksi 1 memperbarui saldo, sedangkan Transaksi 2 membaca saldo.
Karena MVCC, Transaksi 2 melihat data lama hingga Transaksi 1 di-commit.
b. Penguncian Baris (Row-Level Locking)
PostgreSQL hanya mengunci baris yang diubah, memungkinkan konkurensi tinggi.
Contoh Skenario: Dua transaksi memperbarui baris berbeda dalam tabel yang sama.
Penjelasan Kode:
Dua transaksi memperbarui baris berbeda (
id = 1danid = 2) secara konkuren.Karena hanya baris yang diubah yang dikunci, kedua transaksi dapat berjalan tanpa konflik.
c. Penguncian Tabel (Table-Level Locking)
Perintah LOCK mengunci seluruh tabel, berguna untuk operasi kritis seperti pembuatan ID unik.
Contoh Skenario: Aplikasi menghasilkan nomor faktur unik tanpa duplikasi.
Penjelasan Kode:
Kode ini mengunci tabel
invoicedenganACCESS EXCLUSIVE MODEuntuk mencegah duplikasi ID.ID maksimum diambil, lalu nomor faktur baru dimasukkan.
Penguncian tabel memastikan tidak ada transaksi lain yang mengganggu.
d. SELECT FOR UPDATE dan FOR SHARE
SELECT FOR UPDATE mengunci baris untuk mencegah perubahan oleh transaksi lain. FOR SHARE memungkinkan pembacaan konkuren.
Contoh Skenario: Aplikasi memproses pesanan yang belum diproses, memastikan tidak ada duplikasi pemrosesan.
Penjelasan Kode:
Kode ini mengambil pesanan yang belum diproses dengan
FOR UPDATE, mengunci baris tersebut.Baris diperbarui untuk menandakan pemrosesan selesai.
FOR UPDATEmencegah transaksi lain memproses pesanan yang sama.
e. SKIP LOCKED
SKIP LOCKED memungkinkan transaksi melewati baris yang sudah terkunci.
Contoh Skenario: Aplikasi pemesanan tiket mengambil kursi yang tersedia tanpa menunggu kursi yang terkunci.
Penjelasan Kode:
Dua transaksi mencoba mengambil kursi dengan
FOR UPDATE SKIP LOCKED.SKIP LOCKEDmemastikan transaksi kedua mengambil kursi yang berbeda tanpa menunggu.
f. Advisory Locks
Advisory locks mengunci angka tertentu, bukan baris atau tabel, dan tidak dilepaskan otomatis saat COMMIT.
Contoh Skenario: Aplikasi mengunci proses pembuatan laporan untuk mencegah duplikasi.
Penjelasan Kode:
Transaksi 1 mengambil advisory lock pada angka 15, mensimulasikan proses laporan.
Transaksi 2 menunggu hingga lock dilepaskan sebelum melanjutkan.
pg_advisory_unlockmemastikan lock dilepaskan secara eksplisit.
5. Tingkat Isolasi Transaksi
a. Read Committed
Mode default di mana setiap perintah melihat snapshot data terbaru yang telah di-commit.
Contoh Skenario: Aplikasi memeriksa saldo akun selama transaksi lain menambahkan dana.
Penjelasan Kode:
Transaksi 1 membaca saldo dua kali dalam mode Read Committed.
Transaksi 2 menambahkan saldo baru di tengah transaksi 1.
Transaksi 1 melihat saldo baru pada pembacaan kedua karena mode Read Committed.
b. Repeatable Read
Menggunakan snapshot yang sama sepanjang transaksi, mengabaikan perubahan oleh transaksi lain.
Contoh Skenario: Aplikasi membuat laporan saldo yang konsisten.
Penjelasan Kode:
Transaksi 1 menggunakan
RepeatableReaduntuk memastikan saldo yang dibaca konsisten.Meskipun Transaksi 2 menambahkan saldo, Transaksi 1 tidak melihat perubahan tersebut.
c. Serializable
Memastikan transaksi berjalan seolah-olah tidak ada konkurensi, tetapi dapat gagal jika ada konflik.
Contoh Skenario: Aplikasi mencoba menghapus data dengan isolasi Serializable.
Penjelasan Kode:
Transaksi 1 menggunakan mode Serializable dan mencoba menghapus data.
Jika Transaksi 2 menghapus data terlebih dahulu, Transaksi 1 akan gagal karena konflik serialisasi.
Aplikasi harus menangani kegagalan ini dengan mencoba ulang.
6. Menangani Masalah: Deadlock dan Konflik Serialisasi
a. Deadlock
Deadlock terjadi ketika dua transaksi saling menunggu kunci.
Contoh Skenario: Dua transaksi mencoba memperbarui dua baris dalam urutan berbeda.
Penjelasan Kode:
Dua transaksi mencoba memperbarui baris dalam urutan berbeda, menyebabkan deadlock.
PostgreSQL akan membatalkan salah satu transaksi setelah
deadlock_timeout.
b. Konflik Serialisasi
Konflik serialisasi terjadi di mode Serializable ketika transaksi tidak dapat diserialkan.
Catatan: Contoh untuk konflik serialisasi sudah disertakan pada bagian Serializable di atas.
7. Optimasi Penyimpanan dan Pembersihan
a. VACUUM dan Autovacuum
VACUUM membersihkan dead tuples yang dihasilkan oleh MVCC. Autovacuum menjalankannya secara otomatis.
Contoh Skenario: Aplikasi menjalankan VACUUM secara manual setelah pembaruan besar.
Penjelasan Kode:
Kode ini mensimulasikan pembaruan besar pada tabel, diikuti oleh
VACUUMuntuk membersihkan dead tuples.VACUUMmemastikan ruang kosong dapat digunakan kembali.
b. Transaction Wraparound
Autovacuum mencegah wraparound ID transaksi dengan menyesuaikan watermark.
Catatan: Tidak ada kode Golang spesifik untuk wraparound karena dikelola oleh autovacuum. Konfigurasi dilakukan di postgresql.conf.
c. VACUUM FULL
VACUUM FULL menulis ulang tabel untuk mengembalikan ruang ke sistem file, tetapi memblokir akses.
Contoh Skenario: Aplikasi menjalankan VACUUM FULL pada tabel kecil untuk pengujian.
Penjelasan Kode:
Kode ini menjalankan
VACUUM FULLuntuk menulis ulang tabel.Digunakan untuk tabel kecil karena mengunci tabel sepenuhnya.
8. Kesimpulan
Artikel ini telah menjelaskan transaksi dan penguncian di PostgreSQL dengan contoh kode Golang menggunakan pgxpool. Fitur seperti MVCC, savepoint, DDL transaksional, dan advisory locks memungkinkan pengelolaan data yang efisien dan konkuren. Tingkat isolasi (Read Committed, Repeatable Read, Serializable) memberikan fleksibilitas untuk berbagai kebutuhan aplikasi. Dengan VACUUM dan autovacuum, Anda dapat menjaga performa basis data. Kode-kode di atas mensimulasikan skenario nyata seperti transfer saldo, pemesanan tiket, dan pembuatan laporan, membantu Anda memahami penerapan praktis dari konsep ini.
Catatan Tambahan:
Pastikan Anda mengganti string koneksi (
postgres://user:password@localhost:5432/testdb) dengan kredensial database Anda.Tabel seperti
accounts,t_test,invoice,orders,seats, dant_deadlockharus dibuat terlebih dahulu di database untuk menjalankan kode.Kode menggunakan
sync.WaitGroupuntuk mensimulasikan konkurensi dalam skenario seperti MVCC, deadlock, dan SKIP LOCKED.Library
pgxpooldipilih karena mendukung koneksi pool yang efisien untuk aplikasi produksi.
Jika Anda memerlukan bantuan untuk membuat tabel atau menjalankan kode, beri tahu saya!
Last updated