Teori Basis Data Jeffrey Ullman di Balik Kueri Cepat dan Berskala
Bagaimana gagasan inti Jeffrey Ullman mendasari basis data modern: aljabar relasional, aturan optimisasi, join, dan perencanaan ala kompiler yang membantu sistem berskala.

Mengapa Ullman Penting bagi Pekerjaan Data Modern
Kebanyakan orang yang menulis SQL, membuat dashboard, atau menyetel kueri lambat telah mendapat manfaat dari pekerjaan Jeffrey Ullman—bahkan jika mereka belum pernah mendengar namanya. Ullman adalah ilmuwan komputer dan pendidik yang riset serta buku-bukunya membantu mendefinisikan bagaimana basis data menggambarkan data, menganalisis kueri, dan mengeksekusinya secara efisien.
Pengaruh tenang di balik alat sehari-hari
Saat mesin basis data mengubah SQL Anda menjadi sesuatu yang bisa dijalankan dengan cepat, ia mengandalkan ide-ide yang harus presisi sekaligus adaptif. Ullman membantu memformalkan makna kueri (agar sistem bisa menulis ulang dengan aman), dan menghubungkan pemikiran basis data dengan pemikiran kompiler (agar kueri dapat diparse, dioptimalkan, dan diterjemahkan menjadi langkah-langkah yang dapat dieksekusi).
Pengaruh itu tenang karena tidak muncul sebagai tombol di alat BI Anda atau fitur terlihat di konsol cloud. Ia muncul sebagai:
- Kueri yang berjalan cepat setelah Anda menambah index atau menulis ulang sebuah
JOIN - Pengoptimal yang memilih rencana berbeda saat data tumbuh
- Sistem yang bisa skala tanpa mengubah hasil yang dikembalikan kueri Anda
Apa yang akan Anda pelajari di artikel ini (tanpa beban matematika)
Tulisan ini menggunakan ide inti Ullman sebagai tur berpemandu mengenai internal basis data yang paling berguna: bagaimana aljabar relasional ada di bawah SQL, bagaimana penulisan ulang kueri mempertahankan makna, mengapa pengoptimal berbasis biaya membuat pilihan tertentu, dan bagaimana algoritma join sering menentukan apakah pekerjaan selesai dalam hitungan detik atau jam.
Kita juga akan menarik beberapa konsep ala kompiler—parsing, penulisan ulang, dan perencanaan—karena mesin basis data berperilaku lebih seperti kompiler canggih daripada yang banyak orang pikirkan.
Janji singkat: diskusi akan tetap akurat, tetapi menghindari bukti berat-matematika. Tujuannya memberi Anda model mental yang bisa diterapkan di tempat kerja saat performa, skala, atau perilaku kueri yang membingungkan muncul.
Dasar-dasar Basis Data yang Dikukuhkan Ullman
Jika Anda pernah menulis kueri SQL dan mengira ia “hanya berarti satu hal”, Anda bergantung pada gagasan yang dipopulerkan dan diformalkan oleh Jeffrey Ullman: model data yang bersih, plus cara presisi untuk menggambarkan apa yang diminta sebuah kueri.
Model relasional dalam kata-kata sederhana
Inti model relasional memperlakukan data sebagai tabel (relasi). Setiap tabel memiliki baris (tuple) dan kolom (atribut). Sekilas terdengar jelas sekarang, tetapi bagian pentingnya adalah disiplin yang diciptakan:
- Kunci mengidentifikasi baris. Sebuah primary key adalah “label nama” untuk setiap rekaman.
- Relasi menghubungkan tabel lewat foreign key, sehingga Anda bisa menyimpan fakta di satu tempat dan mereferensikannya dari tempat lain.
Pembingkaian ini memungkinkan penalaran tentang kebenaran dan performa tanpa basa-basi. Saat Anda tahu apa yang direpresentasikan tabel dan bagaimana baris diidentifikasi, Anda bisa memprediksi apa yang seharusnya dilakukan join, apa arti duplikasi, dan mengapa filter tertentu mengubah hasil.
Aljabar relasional: kalkulator untuk kueri
Pengajaran Ullman sering menggunakan aljabar relasional sebagai semacam kalkulator kueri: satu set operasi kecil (select, project, join, union, difference) yang bisa digabungkan untuk mengekspresikan apa yang Anda inginkan.
Mengapa ini penting untuk bekerja dengan SQL: basis data menerjemahkan SQL ke bentuk aljabar dan kemudian menulis ulangnya ke bentuk ekuivalen lain. Dua kueri yang terlihat berbeda bisa secara aljabar sama—itulah cara optimizer bisa mengubah urutan join, memajukan filter, atau menghapus kerja berulang sambil mempertahankan makna.
Aljabar vs. kalkulus (tingkat tinggi)
- Aljabar relasional lebih berfokus pada “bagaimana”: urutan operasi untuk menghitung hasil.
- Kalkulus relasional lebih pada “apa”: deskripsi hasil yang Anda inginkan.
SQL sebagian besar adalah “apa”, tetapi mesin sering melakukan optimisasi menggunakan aljabar yang bersifat “bagaimana.”
Fondasi mengalahkan menghafal dialek
Dialek SQL berbeda (Postgres vs. Snowflake vs. MySQL), tetapi dasar-dasarnya tidak. Memahami kunci, relasi, dan ekuivalensi aljabar membantu Anda melihat kapan kueri secara logis salah, kapan cuma lambat, dan perubahan mana yang mempertahankan makna lintas platform.
Aljabar Relasional: Bahasa Terselubung di Bawah SQL
Aljabar relasional adalah “matematika di bawah” SQL: satu set operator kecil yang menggambarkan hasil yang Anda inginkan. Karya Ullman membantu membuat pandangan operator ini tajam dan mudah diajarkan—dan itu masih model mental yang dipakai sebagian besar optimizer.
Operator inti (dan apa artinya)
Kueri basis data bisa diekspresikan sebagai rangkaian blok bangunan:
- Select (σ): menyaring baris (ide
WHEREdalam SQL) - Project (π): mempertahankan kolom tertentu (ide
SELECT col1, col2) - Join (⋈): menggabungkan tabel berdasarkan kondisi (
JOIN ... ON ...) - Union (∪): menumpuk hasil dengan bentuk sama (
UNION) - Difference (−): baris di A tapi tidak di B (mirip
EXCEPTdi banyak dialek SQL)
Karena jumlah operator kecil, lebih mudah menalar tentang kebenaran: bila dua ekspresi aljabar ekuivalen, keduanya mengembalikan tabel yang sama untuk setiap keadaan basis data yang valid.
Bagaimana SQL dipetakan ke aljabar (secara konseptual)
Ambil kueri yang familiar:
SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.total > 100;
Secara konseptual, ini adalah:
- mulai dengan join customers dan orders:
customers ⋈ orders - select hanya orders di atas 100:
σ(o.total > 100)(...) - project kolom yang Anda mau:
π(c.name)(...)
Itu bukan notasi internal yang persis dipakai setiap engine, tapi idenya: SQL menjadi pohon operator.
Ekuivalensi: pintu menuju optimisasi
Banyak pohon berbeda bisa berarti hasil yang sama. Misalnya, filter sering bisa dipindahkan lebih awal (terapkan σ sebelum join besar), dan proyeksi sering bisa menjatuhkan kolom yang tidak dipakai lebih awal (terapkan π lebih cepat).
Aturan-aturan ekuivalensi itulah yang memungkinkan basis data menulis ulang kueri Anda menjadi rencana yang lebih murah tanpa mengubah makna. Setelah Anda melihat kueri sebagai aljabar, “optimisasi” berhenti jadi sulap dan menjadi pembentukan ulang yang aman berdasarkan aturan.
Dari SQL ke Rencana Kueri: Penulisan Ulang yang Mempertahankan Makna
Ketika Anda menulis SQL, basis data tidak mengeksekusinya “sebagaimana tertulis.” Ia menerjemahkan pernyataan Anda menjadi rencana kueri: representasi terstruktur dari pekerjaan yang harus dilakukan.
Model mental yang baik adalah pohon operator. Daun membaca tabel atau index; node internal mentransformasikan dan menggabungkan baris. Operator umum termasuk scan, filter (selection), project (pilih kolom), join, group/aggregate, dan sort.
Rencana logis vs rencana fisik (apa vs bagaimana)
Basis data biasanya memisahkan perencanaan menjadi dua lapis:
- Rencana logis: apa hasil yang harus dihitung, dinyatakan dengan operator abstrak (filter, join, aggregate) dan hubungan antar mereka.
- Rencana fisik: bagaimana mengeksekusinya di penyimpanan riil dan perangkat keras (index scan vs full scan, hash join vs nested-loop join, paralel vs single-threaded).
Pengaruh Ullman terlihat pada penekanan transformasi yang mempertahankan makna: tata ulang rencana logis dengan banyak cara tanpa mengubah jawaban, lalu pilih strategi fisik yang efisien.
Penulisan ulang berbasis aturan yang mengurangi kerja
Sebelum memilih pendekatan eksekusi akhir, optimizer menerapkan aturan "pembersihan" aljabar. Penulisan ulang ini tidak mengubah hasil; mereka mengurangi kerja yang tidak perlu.
Contoh umum:
- Selection pushdown: terapkan filter sedini mungkin sehingga lebih sedikit baris mengalir ke langkah berikutnya.
- Projection pruning: pertahankan hanya kolom yang diperlukan, mengurangi I/O dan memori.
- Join reordering: gabungkan hasil yang lebih kecil/intermediat dahulu (saat aman), daripada mengikuti urutan yang terlihat di SQL.
Contoh penulisan ulang sederhana
Misalkan Anda ingin order untuk pengguna di suatu negara:
SELECT o.order_id, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'CA';
Interpretasi naif mungkin menggabungkan semua users dengan semua orders lalu memfilter ke Canada. Penulisan ulang yang mempertahankan makna memajukan filter sehingga join menyentuh lebih sedikit baris:
- Filter users ke
country = 'CA' - Lalu join users yang sudah difilter itu ke orders
- Lalu project hanya
order_iddantotal
Dalam istilah rencana, optimizer mencoba mengubah:
Join(Users, Orders) → Filter(country='CA') → Project(order_id,total)
menjadi sesuatu seperti:
Filter(country='CA') on Users → Join(with Orders) → Project(order_id,total)
Jawaban sama. Kerja lebih sedikit.
Penulisan ulang ini mudah diabaikan karena Anda tidak mengetiknya—namun mereka alasan utama mengapa SQL yang sama bisa berjalan cepat di satu basis data dan lambat di basis lain.
Optimisasi Berbasis Biaya Tanpa Jargon
Saat Anda menjalankan kueri SQL, basis data mempertimbangkan beberapa cara valid untuk memperoleh jawaban yang sama, lalu memilih yang diperkirakan paling murah. Proses pengambilan keputusan ini disebut optimisasi berbasis biaya—dan ini salah satu tempat paling praktis di mana teori ala Ullman muncul dalam performa sehari-hari.
Apa itu "model biaya" sebenarnya
Model biaya adalah sistem penilaian yang dipakai optimizer untuk membandingkan rencana alternatif. Sebagian besar engine memperkirakan biaya menggunakan beberapa sumber daya inti:
- Baris yang diproses (kerja cenderung sebanding dengan berapa banyak data mengalir)
- I/O (membaca halaman dari disk atau SSD, plus efek cache)
- CPU (filtering, hashing, sorting, aggregating)
- Memori (apakah operasi muat di RAM atau harus spill ke disk)
Model tidak harus sempurna; yang penting arahnya cukup sering benar untuk memilih rencana yang bagus.
Estimasi kardinalitas, secara sederhana
Sebelum menilai rencana, optimizer menanyakan pada setiap langkah: berapa banyak baris yang akan dihasilkan ini? Itu estimasi kardinalitas.
Jika Anda memfilter WHERE country = 'CA', engine memperkirakan proporsi tabel yang cocok. Jika Anda menggabungkan customers dengan orders, engine memperkirakan berapa banyak pasangan yang cocok pada kunci join. Tebakan jumlah baris inilah yang menentukan apakah ia memilih index scan atau full scan, hash join atau nested loop, atau apakah sort akan kecil atau sangat besar.
Mengapa statistik penting (dan apa yang salah tanpa mereka)
Tebakan optimizer didorong oleh statistik: hitungan, distribusi nilai, tingkat null, dan kadang korelasi antar kolom.
Ketika statistik kadaluarsa atau hilang, engine dapat salah memperkirakan jumlah baris hingga beberapa order besaran. Rencana yang tampak murah di atas kertas bisa menjadi mahal di kenyataan—gejala klasik termasuk perlambatan tiba-tiba setelah pertumbuhan data, perubahan rencana “acak”, atau join yang tak terduga harus menulis ke disk.
Trade-off yang tak terhindarkan: akurasi vs waktu perencanaan
Perkiraan lebih baik sering membutuhkan lebih banyak kerja: statistik yang lebih rinci, sampling, atau menjelajahi lebih banyak kandidat rencana. Tetapi perencanaan sendiri memakan waktu, terutama untuk kueri kompleks.
Jadi optimizer menyeimbangkan dua tujuan:
- Merencanakan cukup cepat untuk beban interaktif
- Merencanakan cukup cerdas untuk menghindari pilihan yang katastrofik
Memahami trade-off ini membantu Anda menginterpretasi keluaran EXPLAIN: optimizer bukan mencoba jadi pintar—ia mencoba konsisten benar di bawah informasi yang terbatas.
Algoritma Join dan Inti Performa Kueri
Ullman membantu mempopulerkan gagasan sederhana namun kuat: SQL bukan sekadar “dijalankan” melainkan diterjemahkan menjadi rencana eksekusi. Tidak ada tempat yang lebih jelas daripada pada join. Dua kueri yang mengembalikan baris yang sama bisa berbeda drastis waktu eksekusinya tergantung algoritma join yang dipilih engine—dan urutan penggabungan tabel.
Nested loop, hash join, merge join—kapan masing-masing masuk akal
Nested loop join sederhana secara konseptual: untuk setiap baris di kiri, cari baris yang cocok di kanan. Ini bisa cepat saat sisi kiri kecil dan sisi kanan punya index yang berguna.
Hash join membangun tabel hash dari salah satu input (sering yang lebih kecil) dan melakukan probe dengan input lain. Ia unggul untuk input besar yang tidak diurutkan dengan kondisi kesetaraan (mis. A.id = B.id), tapi membutuhkan memori; spill-ke-disk bisa menghapus keunggulannya.
Merge join berjalan pada dua input yang diurutkan. Ia cocok ketika kedua sisi sudah terurut (atau bisa diurutkan dengan murah), misalnya saat index bisa mengembalikan baris dalam urutan kunci join.
Mengapa urutan join bisa mendominasi performa
Dengan tiga tabel atau lebih, jumlah kemungkinan urutan join membengkak. Menggabungkan dua tabel besar lebih dulu dapat menghasilkan intermediate yang sangat besar yang memperlambat semua langkah berikutnya. Urutan yang lebih baik sering mulai dari filter paling selektif (paling sedikit baris) dan bergabung ke luar, menjaga intermediate tetap kecil.
Index mengubah daftar rencana yang bagus
Index tidak hanya mempercepat lookup—mereka membuat strategi join tertentu jadi layak. Index pada kunci join dapat mengubah nested loop mahal menjadi pola “seek per row” yang cepat. Sebaliknya, index yang hilang atau tidak bisa dipakai bisa memaksa engine ke hash join atau sort besar untuk merge join.
Daftar periksa praktis: gejala rencana join buruk
- Runtime tumbuh dramatis saat data sedikit bertambah (urutan join memperbesar intermediate).
- Rencana menunjukkan perbedaan besar “rows estimated vs rows actual” (perkiraan kardinalitas buruk memicu pilihan join yang salah).
- Anda melihat sort besar atau hash spill ke disk (tekanan memori atau index pendukung hilang).
- Tabel kecil yang sudah difilter digabung terlambat, bukan lebih awal (filter tidak diterapkan cukup cepat).
- Predikat join bukan kesetaraan bersih pada tipe yang kompatibel (menghalangi perilaku hash/merge efisien).
Ide Kompiler di Dalam Mesin Basis Data
Basis data tidak sekadar “menjalankan SQL.” Mereka mengompilasinya. Pengaruh Ullman meluas di kedua domain—teori basis data dan pemikiran kompiler—dan hubungan itu menjelaskan mengapa mesin kueri mirip dengan toolchain bahasa pemrograman: mereka menerjemahkan, menulis ulang, dan mengoptimalkan sebelum melakukan kerja apapun.
Parsing dan pohon sintaks: bagaimana SQL dibaca
Saat Anda mengirim kueri, langkah pertama mirip front end kompiler. Engine men-token-kan kata kunci dan identifier, memeriksa tata bahasa, dan membangun sebuah parse tree (sering disederhanakan menjadi abstract syntax tree). Di sinilah kesalahan dasar ditangkap: koma yang hilang, nama kolom ambigu, aturan pengelompokan yang tidak valid.
Model mental yang berguna: SQL adalah bahasa pemrograman yang “program”-nya kebetulan menggambarkan relasi data alih-alih loop.
Dari pohon parse ke operator logis
Kompiler mengubah sintaks menjadi representasi antara (IR). Basis data melakukan hal serupa: menerjemahkan sintaks SQL menjadi operator logis seperti:
- Selection (penyaringan baris)
- Projection (memilih kolom)
- Join (menggabungkan tabel)
- Aggregation (
GROUP BY)
Bentuk logis ini lebih dekat ke aljabar relasional daripada teks SQL, sehingga lebih mudah menalar tentang makna dan ekuivalensi.
Mengapa optimizer mirip optimisasi kompiler
Optimisasi kompiler menjaga hasil program identik sambil membuat eksekusi lebih murah. Optimizer basis data melakukan hal yang sama, menggunakan sistem aturan seperti:
- Majukan filter lebih awal (kurangi kerja lebih cepat)
- Ubah urutan join (hasil sama, biaya berbeda)
- Hapus perhitungan redundan
Ini versi basis data dari “dead code elimination”: bukan teknik identik, tetapi filosofi yang sama—pertahankan semantik, kurangi biaya.
Debugging: membaca rencana seperti kode terkompilasi
Jika kueri Anda lambat, jangan hanya menatap SQL. Lihat rencana kueri seperti Anda memeriksa output kompiler. Rencana memberi tahu apa yang dipilih mesin sebenarnya: urutan join, pemakaian index, dan di mana waktu dihabiskan.
Inti praktis: pelajari membaca EXPLAIN sebagai "daftar assembly performa." Itu mengubah penyetelan dari tebak-tebakan menjadi debugging berbasis bukti. Untuk lebih lanjut tentang mengubah itu menjadi kebiasaan, lihat /blog/practical-query-optimization-habits.
Teori Desain Skema yang Mempengaruhi Performa Nyata
Performa kueri yang baik sering dimulai sebelum Anda menulis SQL. Teori desain skema Ullman (khususnya normalisasi) tentang mengatur data agar basis data dapat menjaga kebenaran, prediktabilitas, dan efisiensi saat bertumbuh.
Tujuan normalisasi (mengapa ia ada)
Normalisasi bertujuan untuk:
- Mengurangi anomali (mis. memperbarui alamat pelanggan di lima tempat dan melewatkan satu)
- Meningkatkan konsistensi dengan menempatkan setiap fakta di satu “rumah”
- Membuat constraint dapat diekspresikan (kunci, foreign key) sehingga mesin bisa menegakkan aturan, bukan kode aplikasi
Keuntungan kebenaran itu menerjemah ke keuntungan performa nanti: lebih sedikit kolom duplikat, index yang lebih kecil, dan update yang lebih murah.
Bentuk normal dalam bahasa sederhana
Anda tidak perlu menghafal bukti untuk menggunakan ide-idenya:
- 1NF: simpan nilai dalam kolom atomik (tidak ada daftar dipisah koma). Ini membuat filtering dan indexing lebih mudah.
- 2NF: pada tabel dengan primary key komposit, setiap kolom non-kunci harus bergantung pada keseluruhan key, bukan hanya bagian. Ini menghindari pengulangan atribut di banyak baris.
- 3NF: kolom non-kunci harus bergantung hanya pada key, bukan pada kolom non-kunci lain. Ini mencegah duplikasi tersembunyi.
- BCNF: versi yang lebih ketat dari 3NF di mana setiap determinant adalah kandidat key—berguna ketika kolom yang “nyaris unik” menimbulkan duplikasi halus.
Kapan denormalisasi masuk akal
Denormalisasi bisa jadi pilihan cerdas ketika:
- Anda membangun tabel berat-analitik (wide fact tables, reporting)
- Join menjadi bottleneck dan Anda bisa menerima duplikasi terkontrol
- Anda mengoptimalkan untuk kecepatan baca dengan aturan refresh yang jelas (mis. rebuild malam hari)
Kuncinya adalah denormalisasi dilakukan sengaja, dengan proses untuk menjaga duplikat tetap sinkron.
Bagaimana pilihan skema memengaruhi optimizer dan skala
Desain skema membentuk apa yang bisa dilakukan optimizer. Kunci dan foreign key yang jelas memungkinkan strategi join yang lebih baik, penulisan ulang yang lebih aman, dan estimasi jumlah baris yang lebih akurat. Sementara itu, duplikasi berlebihan dapat membengkakkan index dan memperlambat penulisan, dan kolom multi-nilai menghalangi predikat yang efisien. Saat volume data tumbuh, keputusan modeling awal ini seringkali lebih penting daripada mengoptimalkan kueri tunggal secara mikro.
Bagaimana Teori Muncul Saat Sistem Menjadi Besar
Saat sebuah sistem "berskala", jarang hanya soal menambah mesin yang lebih besar. Seringkali, bagian sulitnya adalah makna kueri yang sama harus dipertahankan sementara mesin memilih strategi fisik yang sangat berbeda agar waktu berjalan tetap dapat diprediksi. Penekanan Ullman pada ekuivalensi formal adalah tepat yang memungkinkan perubahan strategi itu tanpa mengubah hasil.
Skala sering kali adalah tata letak fisik + pilihan rencana
Pada ukuran kecil, banyak rencana "bekerja". Pada skala, perbedaan antara memindai tabel, memakai index, atau menggunakan hasil yang sudah dihitung sebelumnya bisa berarti selisih detik dan jam. Sisi teori penting karena optimizer butuh kumpulan aturan penulisan ulang yang aman (mis. memajukan filter, merombak urutan join) yang tidak mengubah jawaban—walau mereka radikal mengubah kerja yang dilakukan.
Partisi mengubah kueri yang dijalankan, walau SQL tetap sama
Partisi (berdasarkan tanggal, pelanggan, wilayah, dll.) mengubah satu tabel logis menjadi banyak potongan fisik. Itu memengaruhi perencanaan:
- partisi mana yang bisa dilewati (partition pruning)
- apakah join terjadi di dalam partisi atau memerlukan pengocokan data antar node
- apakah pengelompokan bisa dilakukan secara lokal sebelum menggabungkan hasil
Teks SQL mungkin tidak berubah, tetapi rencana terbaik kini bergantung pada di mana baris-barisan tersebut berada.
Materialized views: precomputation sebagai jalan pintas aljabar
Materialized view pada dasarnya adalah “sub-ekspresi yang disimpan.” Jika engine bisa membuktikan kueri Anda cocok (atau bisa ditulis ulang agar cocok) dengan hasil yang tersimpan, ia bisa mengganti kerja mahal—seperti join berulang dan agregasi—dengan lookup cepat. Ini pemikiran aljabar relasional dalam praktik: kenali ekuivalensi, lalu pakai ulang.
Caching: membantu, tapi tidak memperbaiki bentuk kerja yang salah
Caching bisa mempercepat baca berulang, tetapi ia tidak akan menyelamatkan kueri yang harus memindai terlalu banyak data, mengocok intermediate besar, atau menghitung join raksasa. Ketika masalah skala muncul, perbaikannya sering kali: kurangi jumlah data yang disentuh (layout/partisi), kurangi komputasi yang diulang (materialized views), atau ubah rencana—bukan sekadar “tambah cache.”
Kebiasaan Optimisasi Praktis yang Terinspirasi Ullman
Pengaruh Ullman terlihat di pola pikir sederhana: perlakukan kueri lambat sebagai pernyataan maksud yang bebas ditulis ulang oleh basis data, lalu verifikasi apa yang sebenarnya diputuskan untuk dilakukan. Anda tidak perlu menjadi teoritikus untuk mendapat manfaat—cukup rutinitas yang bisa diulang.
1) Baca rencana EXPLAIN: apa yang dilihat pertama
Mulailah dengan bagian yang biasanya mendominasi runtime:
- Metode akses: apakah engine memindai seluruh tabel ketika Anda mengharapkan lookup index?
- Estimasi baris vs aktual (jika ditampilkan): celah besar sering menjelaskan kelambanan misterius.
- Urutan join: tabel mana yang dipakai untuk menggerakkan join, dan apakah ia memulai dari filter paling selektif?
- Operator mahal: sort, build hash, nested loop besar—ini sering mengungkap di mana kerja sebenarnya berada.
Jika Anda hanya melakukan satu hal, identifikasi operator pertama di mana jumlah baris meledak. Itu biasanya akar masalah.
2) Anti-pola umum yang mengalahkan optimizer
Ini mudah ditulis dan mengejutkan mahal:
- Fungsi pada kolom yang diindeks:
WHERE LOWER(email) = ...bisa mencegah pemakaian index (gunakan kolom ter-normalisasi atau functional index bila didukung). - Predicate yang hilang: lupa rentang tanggal atau filter tenant mengubah kueri yang ditarget menjadi wide scan.
- Cross join tak sengaja: kondisi join yang hilang bisa melipatgandakan baris dan memaksa intermediate besar.
3) Bentuk hipotesis menggunakan pemikiran aljabar
Aljabar relasional mendorong dua langkah praktis:
- Majukan filter lebih awal: terapkan kondisi
WHEREsebelum join bila memungkinkan untuk mengecilkan input. - Kurangi kolom lebih awal: pilih hanya kolom yang diperlukan (terutama sebelum join) untuk memotong memori dan I/O.
Hipotesis yang baik berbunyi: “Join ini mahal karena kita menggabungkan terlalu banyak baris; jika kita memfilter orders ke 30 hari terakhir dulu, input join menyusut.”
4) Index, tulis ulang, atau ubah skema?
Gunakan aturan keputusan sederhana:
- Tambah index bila kueri benar, selektif, dan dieksekusi berulang.
- Tulis ulang kueri bila
EXPLAINmenunjukkan kerja yang bisa dihindari (join tidak perlu, filter terlambat, predikat non-sargable). - Ubah skema bila pola beban stabil dan Anda terus berperang dengan bottleneck yang sama (mis. agregat pra-hitung, field lookup terdenormalisasi, atau partisi berdasarkan waktu/tenant).
Tujuannya bukan “SQL yang cerdik.” Tujuannya hasil intermediate yang dapat diprediksi dan kecil—persis jenis perbaikan yang mempertahankan ekuivalensi yang dipermudah ide Ullman.
Menerapkan Ide-ide Ini Saat Anda Membangun Produk Nyata
Konsep-konsep ini bukan hanya untuk administrator basis data. Jika Anda mengirim aplikasi, Anda membuat keputusan basis data dan perencanaan kueri—baik Anda sadar atau tidak: bentuk skema, pilihan kunci, pola kueri, dan lapisan akses data semuanya memengaruhi apa yang bisa dilakukan optimizer.
Jika Anda memakai workflow vibe-coding (misalnya, menghasilkan aplikasi React + Go + PostgreSQL dari antarmuka chat di Koder.ai), model mental ala Ullman adalah jaring pengaman praktis: Anda bisa meninjau skema yang dihasilkan untuk kunci dan relasi yang bersih, memeriksa kueri yang diandalkan aplikasi, dan memvalidasi performa dengan EXPLAIN sebelum masalah muncul di produksi. Semakin cepat Anda mengiterasi “maksud kueri → rencana → perbaikan”, semakin besar nilai yang Anda dapat dari pengembangan yang dipercepat.
Di Mana Belajar Lebih Lanjut dan Cara Menerapkannya di Tempat Kerja
Anda tidak perlu “mempelajari teori” sebagai hobi terpisah. Cara tercepat mendapat manfaat dari dasar-dasar ala Ullman adalah mempelajari secukupnya untuk membaca rencana kueri dengan percaya diri—lalu berlatih pada basis data Anda sendiri.
Sumber ramah pemula untuk dicari
Cari buku dan topik kuliah ini (tanpa afiliasi—hanya titik awal yang sering dikutip):
- “A First Course in Database Systems” (Ullman & Widom) — dasar basis data yang terjangkau dengan bingkai praktis.
- “Principles of Database and Knowledge-Base Systems” (Ullman) — teori lebih dalam bila Anda ingin ketelitian lebih.
- “Compilers: Principles, Techniques, and Tools” (Aho, Lam, Sethi, Ullman) — untuk hubungan “mengapa optimizer mirip kompiler?”.
- Topik kuliah/cari: aljabar relasional, penulisan ulang kueri, pengurutan join, optimisasi berbasis biaya, index dan selektivitas, parsing dan bahasa kueri.
Jalur pembelajaran ringan
Mulailah kecil dan kaitkan setiap langkah ke sesuatu yang bisa Anda amati:
- Aljabar relasional: pelajari selection, projection, join, dan aturan ekuivalensi.
- Rencana: belajar membaca node rencana (tipe scan, filter, join, sort, aggregate).
- Join: pahami nested loop vs hash join vs merge join dan kapan masing-masing unggul.
- Model biaya: pelajari input yang mendorong keputusan (jumlah baris, selektivitas, I/O vs CPU).
Latihan kecil yang cepat memberi untung
Pilih 2–3 kueri nyata dan iterasikan:
- Tulis ulang: ubah
INmenjadiEXISTS, majukan predikat, hilangkan kolom tak perlu, dan bandingkan hasil. - Bandingkan rencana: tangkap rencana “sebelum/sesudah” dan catat apa yang berubah (urutan join, tipe join, tipe scan).
- Ubah index: coba tambah/hapus satu index pada satu waktu dan amati estimasi vs aktual baris.
Mengkomunikasikan temuan ke rekan tim
Gunakan bahasa yang jelas dan berbasis rencana:
- “Rencana beralih dari sequential scan ke index scan karena filter menjadi selektif.”
- “Perkiraan baris meleset 100×, jadi optimizer memilih urutan join yang salah.”
- “Penulisan ulang ini ekuivalen (hasil sama), tetapi memungkinkan predicate pushdown dan lebih sedikit baris ke join.”
Itu keuntungan praktis dari fondasi Ullman: Anda mendapat kosakata bersama untuk menjelaskan performa—tanpa menebak.
Pertanyaan umum
Siapa Jeffrey Ullman, dan mengapa karyanya penting bagi saya yang hanya menulis SQL?
Jeffrey Ullman membantu memformalkan bagaimana basis data merepresentasikan arti kueri dan bagaimana sistem dapat mentransformasikan kueri secara aman menjadi ekuivalen yang lebih cepat. Fondasi itu muncul setiap kali mesin menulis ulang kueri, mengubah urutan join, atau memilih rencana eksekusi berbeda sambil menjamin hasil yang sama.
Apa itu aljabar relasional, dan bagaimana hubungannya dengan SQL?
Aljabar relasional adalah sekumpulan operator kecil (select, project, join, union, difference) yang mendeskripsikan hasil kueri secara presisi. Mesin biasanya menerjemahkan SQL ke bentuk seperti aljabar—sebuah pohon operator—sehingga bisa menerapkan aturan ekuivalensi (mis. memajukan filter lebih awal) sebelum memilih strategi eksekusi.
Mengapa penulisan ulang kueri yang mempertahankan makna penting dalam praktik?
Karena optimisasi bergantung pada bukti bahwa kueri yang ditulis ulang mengembalikan hasil yang sama. Aturan ekuivalensi memungkinkan optimizer untuk:
- memajukan kondisi
WHEREsebelum join - memangkas kolom yang tidak diperlukan lebih awal
- mengubah urutan join bila secara logis aman
Perubahan ini dapat memangkas kerja secara drastis tanpa mengubah makna.
Apa perbedaan antara rencana kueri logis dan rencana kueri fisik?
Rencana logis menjelaskan apa operasi yang diperlukan (filter, join, agregat) terlepas dari detail penyimpanan. Rencana fisik memilih bagaimana menjalankannya (index scan vs full scan, hash join vs nested loop, paralelisme, strategi sort). Sebagian besar perbedaan performa muncul dari pilihan fisik, yang dimungkinkan setelah penulisan ulang logis.
Apa itu optimisasi berbasis biaya dalam bahasa sehari-hari?
Optimisasi berbasis biaya membandingkan beberapa rencana valid dan memilih yang diperkirakan paling murah. Biaya biasanya dipengaruhi oleh faktor praktis seperti jumlah baris yang diproses, I/O, CPU, dan memori (termasuk apakah hash atau sort harus ditulis ke disk).
Apa itu estimasi kardinalitas, dan mengapa itu menyebabkan performa yang tidak terduga?
Estimasi kardinalitas adalah tebakan optimizer tentang “berapa banyak baris yang akan dihasilkan pada langkah ini?” Perkiraan ini menentukan urutan join, jenis join, dan apakah index scan layak. Saat perkiraan salah (seringnya karena statistik yang kadaluarsa atau tidak ada), Anda bisa melihat perlambatan tiba-tiba, spill besar ke disk, atau perubahan rencana yang mengejutkan.
Kapan saya harus mengharapkan nested loop, hash join, atau merge join menjadi yang tercepat?
- Nested loop join: bagus saat sisi kiri kecil dan sisi kanan bisa dicari efisien (sering lewat index).
- Hash join: unggul untuk join kesetaraan pada data besar yang tidak terurut, tapi memerlukan memori yang cukup agar tidak terjadi spill.
- Merge join: efektif bila kedua input sudah terurut (atau gampang diurutkan), sering terbantu oleh index yang mengembalikan baris menurut kunci join.
Bagaimana cara membaca rencana EXPLAIN tanpa kewalahan?
Fokus pada beberapa petunjuk bernilai tinggi:
- di mana jumlah baris meledak (titik pertama ledakan biasanya penyebab utama)
- celah “perkiraan vs aktual” (statistik/anggapan buruk)
- operator mahal (sort besar, build hash besar, nested loop di atas input besar)
- pilihan akses (full scan ketika Anda menunggu index)
Anggap rencana seperti output kompilasi: ia menunjukkan apa yang sebenarnya diputuskan mesin.
Bagaimana normalisasi memengaruhi performa kueri, dan kapan denormalisasi dapat diterima?
Normalisasi mengurangi penggandaan fakta dan anomali update, yang biasanya berarti tabel dan index lebih kecil serta join lebih dapat diandalkan. Denormalisasi bisa tepat untuk analitik atau pola baca berat yang berulang, tapi harus dilakukan sengaja (aturan refresh jelas, duplikasi terkendali) agar konsistensi tidak memburuk.
Teknik apa yang membantu kueri tetap cepat saat data meningkat tanpa mengubah hasil?
Skala sering mengharuskan mengubah strategi fisik sambil menjaga makna kueri identik. Alat umum meliputi:
- partisi untuk pruning dan lokalitas
- materialized view untuk memakai ulang subhasil ekuivalen
- perubahan rencana yang dipicu oleh statistik yang diperbarui saat data tumbuh
Caching mempercepat baca berulang, tapi tidak memperbaiki kueri yang harus menyentuh terlalu banyak data atau menghasilkan join antar-intermediate yang besar.