03 — Database¶
3.1 Apa Itu Database?¶
Tempat penyimpanan data yang terstruktur, persisten, dan bisa dicari cepat.
Kenapa tidak simpan di file Excel/JSON saja?
| Kebutuhan | File biasa | Database |
|---|---|---|
| 1000 user menulis bersamaan | 💥 Saling timpa/korup | ✅ Diatur dengan aman (locking, transaction) |
| Cari 1 baris di antara 10 juta | Lambat (baca semua) | ✅ Milidetik (index) |
| Jaminan data konsisten | Tidak ada | ✅ Constraint, transaction |
| Hak akses berbeda per user | Manual | ✅ Built-in |
Analogi: lemari arsip raksasa dengan sistem indeks, petugas yang mengatur antrean, dan aturan pengarsipan — bukan tumpukan kertas acak.
3.2 Dua Keluarga Besar¶
| SQL (Relational) | NoSQL | |
|---|---|---|
| Contoh | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB (dokumen), Redis (key-value), DynamoDB |
| Struktur | Tabel (baris & kolom), skema ketat | Dokumen/key-value, skema fleksibel |
| Analogi | Spreadsheet yang saling terhubung dengan aturan ketat | Folder berisi file JSON bebas bentuk |
| Jaminan konsistensi | Kuat (ACID) | Bervariasi |
| Cocok untuk | Data transaksional: order, invoice, user, stok | Cache, session, log, data yang strukturnya sering berubah |
📌 Untuk 90% aplikasi bisnis, mulai dari SQL (PostgreSQL/MySQL). NoSQL dipakai sebagai pelengkap untuk kebutuhan spesifik — contoh kombinasi umum: PostgreSQL untuk data utama + Redis untuk cache & session.
⚠️ Red flag: AI memilih MongoDB "biar fleksibel" untuk data yang jelas-jelas relasional (order–produk–user). Fleksibilitas skema di data transaksional biasanya berarti inkonsistensi yang ditunda.
3.3 Tabel, Relasi & ERD¶
Contoh mini e-commerce:
users orders products
┌────┬─────────┐ ┌────┬─────────┬─────────┐ ┌────┬──────────┬────────┬───────┐
│ id │ name │ │ id │ user_id │ total │ │ id │ name │ price │ stock │
├────┼─────────┤ ├────┼─────────┼─────────┤ ├────┼──────────┼────────┼───────┤
│ 1 │ Budi │◄────────│ 1 │ 1 │ 150000 │ │ 1 │ Kaos │ 75000 │ 10 │
│ 2 │ Sari │ │ 2 │ 1 │ 120000 │ │ 2 │ Celana │ 120000 │ 5 │
└────┴─────────┘ └────┴─────────┴─────────┘ └────┴──────────┴────────┴───────┘
PK: id PK: id PK: id
FK: user_id → users.id
order_items (tabel perantara / pivot)
┌────┬──────────┬────────────┬─────┬───────────────┐
│ id │ order_id │ product_id │ qty │ price_at_time │
├────┼──────────┼────────────┼─────┼───────────────┤
│ 1 │ 1 │ 1 │ 2 │ 75000 │
│ 2 │ 2 │ 2 │ 1 │ 120000 │
└────┴──────────┴────────────┴─────┴───────────────┘
- Primary Key (PK): identitas unik tiap baris — biasanya kolom
id. - Foreign Key (FK): kolom yang "menunjuk" ke baris di tabel lain. Database bisa menolak data yang menunjuk ke baris yang tidak ada (referential integrity) — order dengan
user_id = 999ditolak kalau user 999 tidak ada. - Perhatikan kolom
price_at_timediorder_items: harga disalin saat transaksi, karena harga produk bisa berubah — tapi invoice lama tidak boleh ikut berubah. Pola penting yang sering dilupakan AI.
Jenis relasi¶
| Relasi | Contoh | Implementasi |
|---|---|---|
| One-to-Many | 1 user punya banyak order | FK di sisi "many" (orders.user_id) |
| Many-to-Many | Order berisi banyak produk; produk ada di banyak order | Tabel perantara (order_items) |
| One-to-One | 1 user punya 1 profil detail | FK unik, untuk memisahkan data yang jarang diakses |
Diagram relasi antar tabel disebut ERD (Entity Relationship Diagram) — mintalah AI membuatnya sebelum menulis kode:
💡 Prompt: "Sebelum menulis kode, buatkan ERD (dalam format Mermaid) untuk fitur ini: [deskripsi]. Jelaskan alasan setiap relasi dan kolom penting."
3.4 Tipe Data & Constraint¶
Tipe data yang umum¶
| Kebutuhan | Tipe yang tepat | Catatan |
|---|---|---|
| Teks pendek (nama, email) | VARCHAR(255) |
Beri batas panjang |
| Teks panjang (deskripsi) | TEXT |
|
| Bilangan bulat | INT / BIGINT |
BIGINT untuk ID di tabel yang akan sangat besar |
| Uang | DECIMAL(15,2) atau integer satuan terkecil |
JANGAN FLOAT/DOUBLE — error pembulatan |
| Benar/salah | BOOLEAN |
|
| Tanggal-waktu | TIMESTAMP/DATETIME |
Simpan dalam UTC, konversi timezone di aplikasi |
| Pilihan terbatas (status) | ENUM atau VARCHAR + validasi |
pending, paid, shipped, ... |
| Data fleksibel | JSON/JSONB |
Untuk atribut yang benar-benar dinamis saja |
Constraint — aturan yang dijaga database sendiri¶
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE, -- wajib diisi & tidak boleh kembar
name VARCHAR(100) NOT NULL,
balance DECIMAL(15,2) NOT NULL DEFAULT 0 CHECK (balance >= 0),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
📌 Wajib paham — validasi berlapis: validasi di aplikasi bisa terlewat (bug, endpoint baru yang lupa validasi, akses langsung ke DB). Constraint di database adalah benteng terakhir yang tidak bisa dilewati. Keduanya perlu.
- NULL = "tidak ada nilai / belum diketahui" — beda dengan
0atau string kosong.NULLpunya perilaku khusus:NULL = NULLhasilnya bukan true; harus pakaiIS NULL. Sumber bug klasik di kode AI.
3.5 SQL Dasar (Cukup untuk Membaca Kode AI)¶
-- Membaca data
SELECT name, price
FROM products
WHERE price > 50000 AND stock > 0
ORDER BY price DESC
LIMIT 10;
-- Menambah data
INSERT INTO products (name, price, stock) VALUES ('Topi', 45000, 20);
-- Mengubah data
UPDATE products SET price = 80000 WHERE id = 1;
-- Menghapus data
DELETE FROM products WHERE id = 99;
Agregasi — merangkum data¶
-- Total penjualan per user, hanya yang belanja > 500rb, urut terbesar
SELECT user_id, COUNT(*) AS jumlah_order, SUM(total) AS total_belanja
FROM orders
GROUP BY user_id
HAVING SUM(total) > 500000
ORDER BY total_belanja DESC;
Fungsi agregat: COUNT, SUM, AVG, MIN, MAX. WHERE menyaring sebelum agregasi, HAVING menyaring hasil agregasi.
JOIN — menggabungkan tabel¶
SELECT orders.id, users.name, orders.total
FROM orders
JOIN users ON orders.user_id = users.id;
| Jenis | Perilaku |
|---|---|
INNER JOIN (default) |
Hanya baris yang punya pasangan di kedua tabel |
LEFT JOIN |
Semua baris tabel kiri; yang tak berpasangan diisi NULL |
Contoh bedanya: "daftar semua user beserta jumlah ordernya, termasuk yang belum pernah order" → wajib LEFT JOIN (INNER JOIN akan menghilangkan user tanpa order).
⚠️ Red flag paling berbahaya di seluruh materi ini:
UPDATEatauDELETEtanpaWHERE→ mengubah/menghapus SELURUH ISI TABEL. Selalu cek ini di kode hasil AI, terutama di script "cleanup" atau "fix data".
3.6 Index — Kenapa Query Bisa Lambat¶
Index = daftar isi buku. Tanpa index, database membaca semua baris satu per satu (full table scan).
Tanpa index (1 juta baris): SELECT * FROM users WHERE email = 'budi@mail.com'
→ periksa 1.000.000 baris → ~detik
Dengan index di email: → lompat langsung via struktur index → ~milidetik
Panduan:
- Index kolom yang sering dipakai di
WHERE,JOIN, danORDER BY. - PK dan UNIQUE otomatis ter-index. FK sebaiknya di-index (beberapa database tidak otomatis).
- Composite index (gabungan beberapa kolom) untuk query yang selalu memfilter kombinasi yang sama:
INDEX(user_id, status). - Trade-off: setiap index memperlambat
INSERT/UPDATE/DELETE(index ikut di-update) dan memakan storage. Jangan index semua kolom.
Cara memverifikasi: perintah EXPLAIN menunjukkan rencana eksekusi query — apakah memakai index atau full scan.
💡 Prompt: "Query ini butuh 8 detik di tabel 2 juta baris: [query]. Analisis dengan pendekatan EXPLAIN — index apa yang perlu ditambahkan, dan tulis pernyataan CREATE INDEX-nya."
3.7 Masalah N+1 — Pembunuh Performa Nomor Satu¶
Bug performa paling umum di kode hasil AI (dan manusia):
// ❌ N+1: 1 query ambil orders + N query ambil user per order
$orders = Order::all(); // 1 query
foreach ($orders as $order) {
echo $order->user->name; // 1 query PER order! 1000 order = 1000 query
}
// ✅ Eager loading: cukup 2 query
$orders = Order::with('user')->get(); // query orders + query users sekaligus
foreach ($orders as $order) {
echo $order->user->name; // dari memori, tanpa query
}
Gejalanya: halaman makin lambat seiring data bertambah, padahal "kodenya tidak berubah". Di development (data 10 baris) tidak terasa; di production (10.000 baris) tumbang.
💡 Prompt: "Periksa kode ini untuk masalah N+1 query. Kalau ada, perbaiki dengan eager loading dan jelaskan berapa jumlah query sebelum vs sesudah."
3.8 Transaction — Semua atau Tidak Sama Sekali¶
Transaction memastikan serangkaian operasi berhasil semua atau gagal semua.
Contoh klasik — checkout order:
BEGIN TRANSACTION
1. INSERT ke orders ✅
2. INSERT ke order_items ✅
3. UPDATE products SET stock = stock - 2 💥 gagal (stok tinggal 1)
ROLLBACK → langkah 1 & 2 otomatis dibatalkan, seolah tidak pernah terjadi
Tanpa transaction: order tercatat, item tercatat, stok tidak berkurang → data korup yang baru ketahuan saat rekonsiliasi.
ACID — jaminan yang diberikan database¶
| Sifat | Arti |
|---|---|
| Atomicity | Semua atau tidak sama sekali |
| Consistency | Data selalu memenuhi aturan (constraint) sebelum & sesudah |
| Isolation | Transaction yang berjalan bersamaan tidak saling mengganggu |
| Durability | Sekali di-commit, data selamat walau server mati |
Race condition — bahaya operasi bersamaan¶
Dua pembeli checkout produk berstok 1 pada detik yang sama:
Request A: baca stok → 1 ✅ Request B: baca stok → 1 ✅
Request A: stok cukup, buat order Request B: stok cukup, buat order
Request A: stok = 0 Request B: stok = -1 💥 overselling!
Solusi umum: atomic update (UPDATE products SET stock = stock - 1 WHERE id = 1 AND stock >= 1 — lalu cek apakah ada baris yang ter-update) atau row locking (SELECT ... FOR UPDATE). Detailnya teknis, tapi yang wajib Anda tahu: cek-lalu-tulis dalam dua langkah terpisah itu rawan, dan Anda bisa memintanya secara eksplisit:
💡 Prompt: "Endpoint checkout ini akan diakses bersamaan oleh banyak user. Pastikan pengurangan stok aman dari race condition — pakai atomic update atau locking, dan jelaskan pilihanmu."
3.9 Migration & Seeding¶
Migration = file berisi perubahan struktur database yang di-track di Git dan dijalankan berurutan:
migrations/
2026_01_10_create_users_table.php
2026_02_03_create_orders_table.php
2026_07_01_add_phone_to_users_table.php ← perubahan baru = file baru
Aturan main:
- Semua environment (local, staging, production) menjalankan urutan migration yang sama → struktur identik & bisa direproduksi.
- Migration yang sudah jalan di production tidak diedit — buat migration baru untuk mengubahnya.
- Migration yang baik punya
down()/rollback untuk membatalkan.
Seeding = mengisi database dengan data awal (kategori default, akun admin) atau data dummy untuk development/testing.
⚠️ Red flag: mengubah struktur database production manual lewat GUI (phpMyAdmin, TablePlus), atau AI yang "memperbaiki" migration lama alih-alih membuat yang baru.
3.10 ORM vs Raw SQL¶
ORM (Object-Relational Mapping) — library yang memetakan tabel ke objek kode: Eloquent (Laravel), Prisma (Node), SQLAlchemy (Python).
// ORM (Eloquent) // SQL yang dihasilkan
User::where('active', true) // SELECT * FROM users
->orderBy('name') // WHERE active = 1
->get(); // ORDER BY name
| ORM | Raw SQL | |
|---|---|---|
| Kecepatan menulis | ✅ Cepat, aman dari injection secara default | Lebih verbose |
| Query kompleks (report, agregasi berat) | Kadang canggung/tidak efisien | ✅ Kontrol penuh |
| Risiko | Query tidak efisien yang tersembunyi (N+1!) | SQL injection kalau string concat |
Praktik sehat: ORM untuk CRUD sehari-hari, raw query (yang tetap parameterized) untuk report kompleks.
3.11 Pola Praktis Lainnya¶
- Soft delete: alih-alih
DELETE, tandai baris dengandeleted_at. Data "terhapus" bagi user, tapi bisa dipulihkan & tetap ada untuk audit. Konsekuensi: semua query harus memfilterdeleted_at IS NULL(ORM biasanya otomatis). - Timestamps standar: kolom
created_at&updated_atdi hampir semua tabel — murah, dan sangat berharga saat investigasi masalah. - Connection pooling: membuka koneksi database itu mahal; aplikasi memakai "kolam" koneksi yang dipakai bergantian. Relevan saat melihat error
too many connections. - Backup — aturan 3-2-1: 3 salinan data, 2 media berbeda, 1 off-site. Dan: backup yang tidak pernah dites restore = tidak punya backup. Jadwalkan tes restore berkala.
3.12 Prompting AI untuk Database¶
💡 Desain skema: "Rancang skema database untuk [fitur]. Sertakan: ERD Mermaid, pilihan tipe data dengan alasan (khususnya kolom uang & tanggal), constraint (NOT NULL, UNIQUE, FK), dan index awal. Gunakan soft delete untuk tabel [X]."
💡 Review query: "Review query/kode ini: cek UPDATE/DELETE tanpa WHERE, potensi N+1, operasi multi-tabel tanpa transaction, race condition pada stok/saldo, dan kolom yang butuh index."
💡 Migration: "Buat migration untuk menambah kolom
phone(nullable, VARCHAR 20) ke tabel users — sebagai migration BARU, jangan mengubah migration lama. Sertakan rollback."
3.13 🧪 TUTORIAL PRAKTIK: Merancang Skema dari Requirement Nyata¶
Estimasi 60 menit. Latihan ini melatih skill yang paling menentukan kualitas hasil vibe coding di sisi data: menerjemahkan kebutuhan bisnis → struktur data yang benar, SEBELUM menyentuh AI.
Langkah 1 — Baca requirement seperti seorang data modeler (10 menit)¶
Requirement dari "klien":
"Kami butuh sistem membership. Member punya paket (Silver/Gold/Platinum) yang bisa berubah kapan saja. Setiap bulan member ditagih sesuai paket AKTIF saat tagihan dibuat. Kami perlu tahu riwayat paket member dari waktu ke waktu, dan tagihan lama tidak boleh berubah walau harga paket naik."
Garis bawahi kata kunci yang menentukan struktur:
- "bisa berubah kapan saja" + "riwayat dari waktu ke waktu" → relasi member↔paket bukan kolom biasa, tapi tabel riwayat dengan periode berlaku
- "tagihan lama tidak boleh berubah walau harga naik" → harga disalin ke tagihan (pola
price_at_time, §3.3)
Langkah 2 — Sketsa ERD sendiri dulu (15 menit)¶
Sebelum bertanya ke AI, sketsakan (di kertas boleh):
plans members member_plans (riwayat)
┌────┬──────────┬───────┐ ┌────┬────────┐ ┌────┬───────────┬─────────┬────────────┬──────────┐
│ id │ name │ price │ │ id │ name │ │ id │ member_id │ plan_id │ started_at │ ended_at │
└────┴──────────┴───────┘ └────┴────────┘ └────┴───────────┴─────────┴────────────┴──────────┘
ended_at NULL = paket yang AKTIF sekarang
invoices
┌────┬───────────┬─────────┬─────────────────┬────────────┬─────────┐
│ id │ member_id │ plan_id │ amount │ period │ status │
└────┴───────────┴─────────┴─────────────────┴────────────┴─────────┘
↑ harga DISALIN saat tagihan dibuat
Langkah 3 — Minta AI menantang desain Anda (10 menit)¶
Ini pola prompt yang sangat berguna — AI sebagai sparring partner, bukan tukang ketik:
💡 "Ini desain skema saya untuk requirement berikut: [requirement + sketsa]. Jangan langsung setuju. Tantang desain ini: skenario apa yang akan merusaknya? Bagaimana kalau member upgrade di tengah bulan? Bagaimana mencegah dua baris member_plans aktif bersamaan untuk satu member? Constraint apa yang kurang?"
Jawaban yang bagus akan menyinggung: unique partial constraint / validasi "hanya satu ended_at NULL per member", kebijakan penagihan prorata, dan index di member_plans(member_id, ended_at).
Langkah 4 — Generate & verifikasi DDL (10 menit)¶
💡 "Sekarang tulis migration untuk skema final, dengan: tipe uang DECIMAL, semua FK + index-nya, constraint yang kita diskusikan, timestamps, dan rollback."
Checklist verifikasi hasil AI:
- [ ]
price/amountbertipe DECIMAL, bukan FLOAT - [ ] Semua FK ada dan ter-index
- [ ]
invoices.amountada (harga disalin) — bukan cumaplan_id - [ ] Ada mekanisme mencegah dobel paket aktif
- [ ] Migration punya
down()
Langkah 5 — Tulis 3 query bisnis paling penting (15 menit)¶
Minta AI menulis, lalu baca dan pahami setiap barisnya:
- "Paket aktif setiap member saat ini" → harus memakai
ended_at IS NULL(perilaku NULL, §3.4!) - "Total pendapatan per paket per bulan" →
GROUP BY+SUM(amount)dari invoices (bukan dari harga plans sekarang — kenapa? karena harga bisa sudah berubah!) - "Member yang belum bayar tagihan bulan ini" →
LEFT JOIN+IS NULLatau filter status
Kalau Anda bisa menjelaskan kenapa query #2 harus dari invoices, Anda sudah memahami inti pemodelan data transaksional — selamat.
Rangkuman Bab¶
- SQL untuk data transaksional; NoSQL pelengkap untuk kebutuhan spesifik.
- PK/FK + constraint = integritas yang dijaga database sendiri; validasi aplikasi saja tidak cukup.
- Uang: DECIMAL/integer, jangan float. Tanggal: UTC + ISO 8601. Salin harga saat transaksi (
price_at_time). UPDATE/DELETEtanpaWHERE= bencana. Selalu cek.- Index mempercepat baca, memperlambat tulis — pasang dengan sengaja, verifikasi dengan EXPLAIN.
- N+1 adalah bug performa paling umum di kode AI; solusinya eager loading.
- Transaction untuk operasi multi-tabel; waspadai race condition pada stok/saldo.
- Perubahan skema hanya lewat migration; backup wajib dites restore.