1. Mengapa Database Butuh Index? (B-Tree Index)
Tanpa index, ketika Anda menjalankan `SELECT * FROM users WHERE email = 'budi@gmail.com'`, PostgreSQL harus membaca **setiap baris dari awal sampai akhir** (*Sequential Scan* / O(N)). Dengan 10 juta baris data, query bisa memakan waktu beberapa detik.
Dengan membuat **B-Tree Index**, PostgreSQL menyusun pohon pencarian biner seimbang yang memungkinkan pencarian dilakukan dalam kompleksitas waktu **O(log N)** (hanya hitungan milidetik).
| 1 | -- Membuat B-Tree index pada kolom email |
| 2 | CREATE INDEX idx_users_email ON users(email); |
| 3 | |
| 4 | -- Membuat Composite Index (Index gabungan beberapa kolom) |
| 5 | CREATE INDEX idx_orders_user_status ON orders(user_id, status); |
| 6 | |
| 7 | -- Melihat Execution Plan performa query |
| 8 | EXPLAIN ANALYZE |
| 9 | SELECT * FROM users WHERE email = 'budi@gmail.com'; |
2. Empat Pilar Transaksi ACID
Transaksi database adalah sekelompok operasi yang diperlakukan sebagai satu kesatuan unit kerja yang tidak terpisahkan. ACID menjamin keamanan data bahkan saat server mati mendadak atau terjadi mati lampu: - **A (Atomicity - All or Nothing)**: Semua query berhasil, atau tidak ada yang berubah sama sekali (Rollback otomatis jika 1 query gagal). - **C (Consistency)**: Data selalu mematuhi semua batasan skema dan aturan bisnis (*foreign keys, unique, checks*). - **I (Isolation)**: Transaksi yang berjalan bersamaan tidak saling mengintervensi atau membaca data yang belum selesai di-commit (*Race condition prevention*). - **D (Durability)**: Sekali transaksi di-COMMIT, data dijamin tersimpan permanen ke disk fisik melalui Write-Ahead Logging (WAL).
| 1 | -- Contoh Transaksi Transfer Uang Antar Rekening (ACID) |
| 2 | BEGIN TRANSACTION; |
| 3 | |
| 4 | -- 1. Kurangi saldo Rekening A |
| 5 | UPDATE accounts |
| 6 | SET balance = balance - 500000 |
| 7 | WHERE id = 'acc_A' AND balance >= 500000; |
| 8 | |
| 9 | -- 2. Tambah saldo Rekening B |
| 10 | UPDATE accounts |
| 11 | SET balance = balance + 500000 |
| 12 | WHERE id = 'acc_B'; |
| 13 | |
| 14 | -- Jika kedua langkah sukses tanpa error, simpan permanen: |
| 15 | COMMIT; |
| 16 | -- Jika salah satu langkah gagal, batalkan seluruh perubahan: |
| 17 | -- ROLLBACK; |
3. Simulasi Transaction Runner di Terminal
| 1 | class MockAccountDatabase { |
| 2 | constructor() { |
| 3 | this.balances = { alice: 1000, bob: 500 }; |
| 4 | } |
| 5 | |
| 6 | transfer(sender, receiver, amount) { |
| 7 | console.log(`[BEGIN] Transfer ${amount} dari ${sender} ke ${receiver}`); |
| 8 | const snapshot = { ...this.balances }; // Simpan restore point |
| 9 | |
| 10 | try { |
| 11 | if (this.balances[sender] < amount) { |
| 12 | throw new Error("Saldo pengirim tidak mencukupi!"); |
| 13 | } |
| 14 | this.balances[sender] -= amount; |
| 15 | this.balances[receiver] += amount; |
| 16 | |
| 17 | console.log("[COMMIT] Transfer berhasil!"); |
| 18 | return { success: true, balances: this.balances }; |
| 19 | } catch (err) { |
| 20 | console.log(`[ROLLBACK] Terjadi error: ${err.message}. Membatalkan semua perubahan.`); |
| 21 | this.balances = snapshot; // Kembalikan ke kondisi semula |
| 22 | return { success: false, error: err.message, balances: this.balances }; |
| 23 | } |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | const bank = new MockAccountDatabase(); |
| 28 | console.log(bank.transfer("alice", "bob", 300)); |
| 29 | console.log(bank.transfer("alice", "bob", 9999)); // Melebihi saldo |