Query yang mulus pada sembilan baris latihan bisa memakan waktu menit pada tabel produksi berisi jutaan baris. Perbedaannya bukan kebenaran query, melainkan cara database mengeksekusinya: apakah engine membaca seluruh tabel, atau melompat langsung ke baris yang dibutuhkan. Kuncinya adalah membaca execution plan — peta rencana eksekusi yang disusun database sebelum query berjalan. Chapter penutup ini berlatih membaca plan di SQLite, membangun index pertama, dan menulis WHERE yang tidak meniadakan index — semua dengan hasil eksekusi nyata.

Membaca Plan dengan EXPLAIN QUERY PLAN #

Prefiks EXPLAIN QUERY PLAN tidak menjalankan query — database hanya menampilkan rencana eksekusinya. Dua kata kunci penting pada hasilnya: SCAN berarti engine membaca seluruh tabel baris demi baris, sedangkan SEARCH berarti engine melompat ke baris tertentu dengan bantuan index. Karena kita akan sering membandingkan plan, berikut fungsinya:

python
from setup_toko import buat_database

def tampilkan_plan(koneksi, query):
    for baris in koneksi.execute("EXPLAIN QUERY PLAN " + query):
        print(baris[-1])

koneksi = buat_database()
tampilkan_plan(koneksi, "SELECT * FROM pesanan WHERE pelanggan_id = 2")

Output yang Dihasilkan:

text
SCAN pesanan

Tabel pesanan belum memiliki index pada pelanggan_id, jadi meski hanya dua baris memenuhi kondisi, engine memeriksa kesembilan barisnya. Pada sembilan baris ini tidak terasa; pada sembilan juta baris, SCAN berarti sembilan juta pemeriksaan untuk hasil yang mungkin hanya ratusan baris.

Index adalah struktur terurut yang memetakan nilai kolom ke lokasi barisnya — seperti indeks buku memetakan kata ke halaman. Index pada kolom yang sering difilter mengubah rencana eksekusi:

sql
CREATE INDEX idx_pesanan_pelanggan ON pesanan(pelanggan_id);

Setelah index dibuat, plan query yang sama dibaca kembali:

python
koneksi.execute("CREATE INDEX idx_pesanan_pelanggan ON pesanan(pelanggan_id)")
tampilkan_plan(koneksi, "SELECT * FROM pesanan WHERE pelanggan_id = 2")

Output yang Dihasilkan:

text
SEARCH pesanan USING INDEX idx_pesanan_pelanggan (pelanggan_id=?)

Rencana berubah dari SCAN menjadi SEARCH ... USING INDEX idx_pesanan_pelanggan (pelanggan_id=?) — engine melompat langsung ke entri pelanggan_id = 2 di dalam index; tanda tanya menandai posisi nilai yang diisi saat eksekusi.

Mengapa SELECT * Merugikan #

Tiga alasan menjadikan SELECT * kebiasaan yang buruk pada pipeline produksi:

  • Transfer berlebih: query menarik semua kolom meski transformasi hanya butuh tiga — boros jaringan dan memori pada tabel lebar.
  • Kontrak rapuh: pipeline berhenti benar atau berubah diam-diam ketika kolom sumber bertambah, berkurang, atau berganti urutan.
  • Peluang index tertutup: kolom eksplisit memungkinkan covering index — index yang sudah memuat semua kolom yang dibutuhkan sehingga tabel tidak perlu disentuh.

Bentuk penggantinya sederhana: sebutkan kolom yang dipakai downstream.

sql
SELECT id, tanggal, status
FROM pesanan
WHERE pelanggan_id = 2;

Output yang Dihasilkan:

text
id | tanggal | status
2 | 2024-06-03 | selesai
6 | 2024-07-01 | diproses

WHERE yang Ramah Index #

Index hanya terpakai bila kondisi WHERE cocok langsung dengan isi index. Menyelubungi kolom dengan fungsi memaksa engine menghitung fungsi itu untuk tiap baris — index menjadi tidak terpakai. Bandingkan dua query pencari pesanan Juni pada kolom tanggal yang sudah terindeks:

python
koneksi = buat_database()
koneksi.execute("CREATE INDEX idx_pesanan_tanggal ON pesanan(tanggal)")
tampilkan_plan(koneksi, "SELECT status FROM pesanan WHERE DATE(tanggal) = '2024-06-01'")

Output yang Dihasilkan:

text
SCAN pesanan
python
tampilkan_plan(koneksi, """
    SELECT status FROM pesanan
    WHERE tanggal >= '2024-06-01' AND tanggal < '2024-07-01'
""")

Output yang Dihasilkan:

text
SEARCH pesanan USING INDEX idx_pesanan_tanggal (tanggal>? AND tanggal<?)

Query pertama memakai DATE(tanggal), sehingga engine menghitung fungsi itu pada tiap baris — plan kembali SCAN. Query kedua membandingkan kolom langsung dengan rentang nilai, dan engine memakai urutan index untuk melompat ke rentang tersebut. Prinsip ini disebut sargable (Search ARGument ABLE): letakkan fungsi pada nilai pembanding, bukan pada kolom yang diindeks — tanggal >= '2024-06-01' alih-alih strftime('%Y', tanggal) = '2024'.

LIMIT dan Pola Top-N #

LIMIT membatasi jumlah baris hasil dan paling pas dipasangkan ORDER BY untuk pola “N teratas”:

sql
SELECT id, pelanggan_id, tanggal, status
FROM pesanan
ORDER BY tanggal DESC
LIMIT 3;

Output yang Dihasilkan:

text
id | pelanggan_id | tanggal | status
9 | 1 | 2024-07-20 | selesai
8 | 3 | 2024-07-12 | diproses
7 | 5 | 2024-07-05 | selesai

Tiga pesanan terbaru tampil tanpa memuat seluruh tabel. Namun LIMIT bukan obat query lambat — pada pengujian nyata, ORDER BY tanggal DESC LIMIT 3 tanpa index tetap memicu SCAN plus pengurutan sementara; dengan index tanggal, engine membaca index terurut dan berhenti setelah tiga baris tanpa pengurutan. Kombinasi index + ORDER BY + LIMIT membuat laporan top-N cepat pada tabel besar; rujukan lengkapnya ada di dokumentasi resmi SQLite .

Section Dasar SQL tertutup dengan perangkat lengkap: membaca data, membentuk hasil (CTE, window function, ekspresi kondisional), menulis data beserta transaksinya, dan menjaga performa (index, plan, WHERE ramah index). SQL hanyalah bahasanya — rumahnya adalah sistem penyimpanan itu sendiri. Section berikutnya, Database dan Penyimpanan Data, membedah database relasional lebih dalam: pemodelan data, normalisasi, hingga pemilihan sistem penyimpanan untuk tiap kebutuhan pipeline.