Node Query Planner
🚨 Operasi yang Buruk
1. Full Table Scan / Seq Scan
Seq Scan on users (cost=0.00..10000.00 rows=100000)
Masalah: Membaca setiap baris dalam tabel Solusi: Tambahkan index pada kolom WHERE/JOIN
CREATE INDEX idx_users_email ON users(email);
Sesudah:
Index Scan using idx_users_email on users (cost=0.42..8.44 rows=1 width=64)
Index Cond: (email = 'john@example.com')
Cost turun dari 10000 menjadi ~8, dan kita hanya memeriksa 1 baris dari 100k baris.
2. Filesort / Sort (dengan jumlah baris besar)
Sort (cost=5000.00..5500.00 rows=200000)
Sort Key: created_date DESC
Masalah: Sorting di memory/disk alih-alih menggunakan urutan index Solusi: Tambahkan index dengan arah ORDER BY yang sesuai
CREATE INDEX idx_created ON orders(created_date DESC);
Sesudah:
Index Scan using idx_created on orders (cost=0.42..6500.00 rows=200000 width=40)
Tidak ada lagi node Sort—index mengembalikan baris yang sudah terurut. Tidak ada disk spill.
3. Using temporary / Using filesort
Using temporary; Using filesort
Masalah: Membuat temp table lalu sorting (penalti ganda) Solusi: Buat composite index yang mencakup GROUP BY dan ORDER BY
-- Untuk: GROUP BY category ORDER BY count DESC
CREATE INDEX idx_category ON products(category);
Sesudah:
GroupAggregate (cost=0.42..12000.00 rows=1000 width=44)
Group Key: category
-> Index Scan using idx_category on products (cost=0.42..10000.00 rows=200000 width=36)
Node Sort dihilangkan—index menyediakan akses terurut. Tidak perlu temp table.
4. Nested Loop dengan outer table besar
Nested Loop (cost=0.00..1000000.00 rows=1000000)
-> Seq Scan on orders (rows=100000)
-> Index Scan on customers (rows=10)
Masalah: Untuk setiap 100k orders, mencari customer (mahal) Solusi: Tambahkan index pada kolom join
CREATE INDEX idx_orders_customer ON orders(customer_id);
Sesudah (planner mungkin memilih Hash Join):
Hash Join (cost=3000.00..12000.00 rows=100000 width=100)
Hash Cond: (orders.customer_id = customers.id)
-> Seq Scan on orders (cost=0.00..5000.00 rows=100000 width=50)
-> Hash (cost=2000.00..2000.00 rows=50000 width=50)
-> Seq Scan on customers (cost=0.00..2000.00 rows=50000 width=50)
Atau jika memfilter customer terlebih dahulu:
Nested Loop (cost=0.85..8500.00 rows=100 width=100)
-> Index Scan using customers_pkey on customers (cost=0.42..8.44 rows=1 width=50)
Index Cond: (id = 123)
-> Index Scan using idx_orders_customer on orders (cost=0.42..8000.00 rows=100 width=50)
Index Cond: (customer_id = 123)
Cost turun drastis saat memfilter; index memungkinkan lookup efisien dari kedua arah.
5. Index Scan backward / Index Full Scan
Index Scan Backward using idx_date on orders
Masalah: Index ada tapi arahnya salah Solusi: Sesuaikan arah index dengan query
-- Query menggunakan: ORDER BY created_date DESC
CREATE INDEX idx_date ON orders(created_date DESC); -- bukan ASC
Sesudah:
Index Scan using idx_date on orders (cost=0.42..45000.00 rows=100000 width=40)
Index Cond: (created_date > '2024-01-01')
“Backward” hilang—index sekarang selaras dengan arah query. Cost sedikit lebih rendah dan cache utilization lebih baik.
6. Using where (setelah index scan)
Index Scan on users using idx_status
Filter: created_date > '2024-01-01' -- memfilter 90% baris!
Masalah: Index hanya membantu sebagian; masih memfilter banyak baris Solusi: Buat composite index
CREATE INDEX idx_status_date ON users(status, created_date);
Sesudah:
Index Scan using idx_status_date on users (cost=0.42..5000.00 rows=10000 width=64)
Index Cond: ((status = 'active') AND (created_date > '2024-01-01'))
Tidak ada lagi baris Filter—kedua kondisi ditangani oleh index. Cost berkurang ~80%.
7. “rows examined” vs “rows returned” tinggi
rows=1000000 filtered=1.00% -- memeriksa 1M untuk mengembalikan 10K!
Masalah: Selektivitas buruk, membaca terlalu banyak data Solusi: Susun ulang kolom index (yang paling selektif di depan)
Sesudah:
Index Scan using idx_status_region_date on orders (cost=0.42..8000.00 rows=10000 width=40)
Index Cond: ((status = 'completed') AND (region = 'US') AND (created_date > '2020-01-01'))
Rows examined sekarang sesuai dengan rows returned. Cost berkurang 90%.
8. Type: ALL
type: ALL -- paling buruk
Masalah: Full table scan Solusi: Tambahkan index yang sesuai
✅ Yang Ingin Anda Lihat
Operasi yang baik:
type: refatautype: eq_ref- Menggunakan index untuk lookuptype: range- Menggunakan index untuk range scanUsing index- Covering index (tidak menyentuh data tabel)Using index condition- Index digunakan untuk filtering- Cost rendah - Angka lebih rendah lebih baik
- Rows examined ≈ Rows returned - Tidak membuang-buang pembacaan
Contoh eksekusi yang baik:
Index Scan using idx_user_created on orders
Index Cond: (user_id = 123 AND created_at > '2024-01-01')
Rows: 50 (actual rows: 48)
Using index -- covering index, sangat cepat!
Pohon Keputusan Cepat
Full table scan? → Tambahkan index pada kolom WHERE
Filesort? → Tambahkan index dengan kolom ORDER BY (arah yang sesuai)
Using temporary? → Index kolom GROUP BY
Rows examined tinggi? → Composite index dengan urutan kolom yang lebih baik
Nested loop lambat? → Index kolom JOIN
Filter setelah index? → Perluas composite index
Tips: Fokus pada query dengan actual time dan rows tinggi terlebih dahulu - ini memberikan peningkatan performa terbesar!