Soal 1Ketika SCAN perf_sales muncul di execution plan, mana arti yang benar?
Membaca Execution Plan — EXPLAIN QUERY PLAN
Telusuri kosakata yang muncul di EXPLAIN QUERY PLAN. Kamu akan melihat bagaimana full table scan berbeda dari index lookup, kapan temporary B-tree muncul untuk sorting, seperti apa Index-Only Scan (covering index), bagaimana correlated subquery terbaca di plan, dan apa arti MATERIALIZE — semua dijalankan dengan menghidupkan dan mematikan indeks.
Dataset untuk artikel ini — perf_sales (50.000 baris penjualan)
Ketika kamu ingin tahu apakah sebuah kueri cepat atau lambat, hal pertama yang dilihat adalah execution plan (rencana eksekusi kueri).
Diberikan sebuah pernyataan SQL, database memutuskan secara internal "tabel mana yang dibaca dalam urutan apa, dan bagaimana".
Alat yang mencetak prosedur itu sebagai teks adalah EXPLAIN QUERY PLAN.
Tambahkan keyword ini di depan kueri dan kamu akan melihat langkah-langkah eksekusi alih-alih hasil sebenarnya.
SCAN dan SEARCH — full scan dan index scan
Dua kata pertama yang harus dipelajari di sebuah execution plan adalah SCAN dan SEARCH.
SCAN perf_sales berarti membaca setiap baris dari atas ke bawah tabel (full scan).
Ketika kamu memfilter pada kolom yang tidak punya indeks, database tidak tahu baris mana yang cocok, jadi ia memeriksa semua 50.000 baris satu per satu.
Sementara itu SEARCH perf_sales USING INDEX index_name (column=?) berarti mengikuti indeks untuk langsung mendarat di baris yang cocok (index scan).
Buat indeks dengan CREATE INDEX dan filter pada kolom itu berhenti memindai setiap baris — plan berubah dari SCAN menjadi SEARCH ... USING INDEX.
-- Filter berdasarkan region: tanpa indeks, jadi membaca setiap baris
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE region = 'East';
--> SCAN perf_sales
-- Bangun indeks pada region, lalu lihat plan yang sama
DROP INDEX IF EXISTS ix_region;
CREATE INDEX ix_region ON perf_sales(region);
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE region = 'East';
--> SEARCH perf_sales USING INDEX ix_region (region=?)
USE TEMP B-TREE — area sementara untuk sorting
USE TEMP B-TREE FOR ORDER BY (B-tree sementara) berarti kolom ORDER BY tidak punya indeks, jadi database membangun area kerja sementara untuk mengurutkan hasil.
Semua 50.000 baris masuk ke area sementara itu untuk diurutkan ulang, jadi biayanya tumbuh dengan jumlah baris.
Kalau ada indeks yang bisa dipakai untuk sorting, baris ini hilang dari plan dan urutan indeks yang sudah ada dipakai apa adanya.
-- Top 10 berdasarkan amount ascending: tanpa indeks, jadi area temp diperlukan untuk sorting
EXPLAIN QUERY PLAN
SELECT sale_id, amount FROM perf_sales ORDER BY amount LIMIT 10;
--> SCAN perf_sales
--> USE TEMP B-TREE FOR ORDER BY
-- Bangun indeks pada amount dan sorting bisa memakai ulang urutannya langsung
DROP INDEX IF EXISTS ix_amount;
CREATE INDEX ix_amount ON perf_sales(amount);
EXPLAIN QUERY PLAN
SELECT sale_id, amount FROM perf_sales ORDER BY amount LIMIT 10;
--> SEARCH perf_sales USING INDEX ix_amount
-- USE TEMP B-TREE hilang dari plan
USING COVERING INDEX — indeks yang tidak menyentuh tabel
USING COVERING INDEX (Index-Only Scan, juga disebut covering index dalam bahasa Inggris) berarti setiap kolom yang dibutuhkan kueri ada di dalam indeks, jadi database bisa membangun hasil dari indeks saja tanpa membaca tabelnya sendiri.
Indeks adalah salinan kecil dari hanya kolom yang kamu tarik dari tabel, jadi melewati pembacaan tabel adalah yang membuatnya lebih cepat.
Ini satu langkah di luar SEARCH ... USING INDEX — ketika plan menampilkan USING COVERING INDEX, kamu mendapat pembacaan paling efisien.
-- Indeks hanya pada emp_id -> temukan baris lewat indeks, lalu baca amount dari tabel
DROP INDEX IF EXISTS ix_emp;
CREATE INDEX ix_emp ON perf_sales(emp_id);
EXPLAIN QUERY PLAN
SELECT emp_id, amount FROM perf_sales WHERE emp_id = 7;
--> SEARCH perf_sales USING INDEX ix_emp (emp_id=?)
-- (tabel juga dibaca)
-- Indeks komposit pada (emp_id, amount) -> setiap kolom yang diperlukan ada di indeks, tanpa pembacaan tabel
DROP INDEX IF EXISTS ix_emp_amount;
CREATE INDEX ix_emp_amount ON perf_sales(emp_id, amount);
EXPLAIN QUERY PLAN
SELECT emp_id, amount FROM perf_sales WHERE emp_id = 7;
--> SEARCH perf_sales USING COVERING INDEX ix_emp_amount (emp_id=?)
Plan subquery — CORRELATED dan MATERIALIZE
Kueri dengan subquery mendapat kosakata khusus mereka sendiri di plan.
CORRELATED SCALAR SUBQUERY berarti nilai subquery bergantung pada baris luar saat ini, jadi ia dieksekusi ulang sekali per baris luar.
Dengan 50.000 baris luar, subquery bisa berjalan 50.000 kali — ketika kata ini muncul di plan, ia adalah kandidat untuk ditulis ulang.
Sementara itu MATERIALIZE berarti hasil dari subquery atau CTE dihitung sekali, disimpan di tabel sementara, dan dipakai ulang.
Ini optimasi yang membayar biaya komputasi sekali untuk hasil antara yang kamu rujuk beberapa kali.
Mana dari dua kata ini yang muncul memberi tahu kamu apakah subquery "dihitung setiap kali" atau "dihitung sekali dan dipakai ulang".
-- Correlated scalar subquery: ambil ulang "max amount untuk region ini" per baris luar
EXPLAIN QUERY PLAN
SELECT s.sale_id, s.amount
FROM perf_sales s
WHERE s.amount = (
SELECT MAX(amount) FROM perf_sales x WHERE x.region = s.region
)
LIMIT 10;
--> baris CORRELATED SCALAR SUBQUERY muncul
-- Tabel turunan (subquery di klausa FROM) dihitung sekali
EXPLAIN QUERY PLAN
SELECT t.region, t.s FROM (
SELECT region, SUM(amount) AS s FROM perf_sales GROUP BY region
) t
WHERE t.s > 100000000;
--> baris MATERIALIZE mungkin muncul
Bagaimana ini dipetakan ke RDBMS lain — MySQL / PostgreSQL / Oracle
Kosakata plan di artikel ini datang dari EXPLAIN QUERY PLAN SQLite, tapi MySQL / PostgreSQL / Oracle mengangkat konsep yang sama di bawah nama berbeda.
Mulai dengan memeriksa bagaimana setiap database menghasilkan execution plan, lalu bandingkan cheat sheet terminologi plan — kamu akan bisa membaca plan di database lain tanpa banyak pembelajaran tambahan.
Cara mencetak execution plan di setiap database
- SQLite: Tambahkan
EXPLAIN QUERY PLANdi depan kueri (misalnyaEXPLAIN QUERY PLAN SELECT ...;). Ia tidak menjalankan kueri, hanya mencetak plan.EXPLAINpolos menunjukkan bytecode tingkat rendah, jadi untuk pembacaan manusia kamu inginEXPLAIN QUERY PLAN - MySQL: Tambahkan
EXPLAINdi depan kueri (misalnyaEXPLAIN SELECT ...;). MySQL 8.0+ juga punyaEXPLAIN ANALYZE SELECT ...;untuk run time aktual dan jumlah baris aktual. Untuk output JSON, pakaiEXPLAIN FORMAT=JSON SELECT ...; - PostgreSQL: Tambahkan
EXPLAINdi depan kueri (misalnyaEXPLAIN SELECT ...;). Itu plan estimasi saja. Untuk aktual, pakaiEXPLAIN ANALYZE SELECT ...;, dan untuk info buffer I/O detail, pakaiEXPLAIN (ANALYZE, BUFFERS) SELECT ...; - Oracle: Pola dua langkah —
EXPLAIN PLAN FOR <query>;menyimpan plan ke tabel internal, danSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);mencetaknya. Untuk tampilan cepat, opsi sesiSET AUTOTRACE ONjuga bekerja
Cheat sheet terminologi plan
| SQLite (EXPLAIN QUERY PLAN) | MySQL (EXPLAIN) | PostgreSQL (EXPLAIN) | Oracle (EXPLAIN PLAN) |
|---|---|---|---|
SCAN (full table scan) | type=ALL | Seq Scan | TABLE ACCESS FULL |
SEARCH ... USING INDEX | type=ref / range / eq_ref | Index Scan / Bitmap Index Scan | INDEX RANGE SCAN / INDEX UNIQUE SCAN |
USING COVERING INDEX | Extra: Using index | Index Only Scan | INDEX FAST FULL SCAN |
USE TEMP B-TREE FOR ORDER BY | Extra: Using filesort | Sort | SORT ORDER BY |
USE TEMP B-TREE FOR GROUP BY | Extra: Using temporary; Using filesort | HashAggregate / GroupAggregate | HASH GROUP BY / SORT GROUP BY |
CORRELATED SCALAR SUBQUERY | DEPENDENT SUBQUERY | SubPlan (correlated) | correlated subquery (di dalam FILTER) |
MATERIALIZE | Extra: Using temporary | Materialize / CTE Scan | temp-table transformation (TEMP TABLE TRANSFORMATION) |
Kata-katanya berbeda, tapi apa yang dilakukan database secara internal pada dasarnya sama. Tujuan artikel ini adalah bisa membaca "apakah ia membaca setiap baris, apakah ia mempersempit dengan indeks, apakah ia memerlukan area kerja untuk sorting, apakah ia menjalankan subquery berulang-ulang" dari output EXPLAIN setiap database.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Mengapa USING COVERING INDEX lebih efisien daripada USING INDEX di execution plan?
Soal 3Ketika CORRELATED SCALAR SUBQUERY muncul di execution plan, mana interpretasi paling akurat?