Belajar dengan membaca secara berurutan

Desain Indeks — Urutan Kolom Komposit dan Kondisi yang Memakai Indeks

Bandingkan urutan kolom indeks komposit, aturan leftmost prefix, pengurutan equality-lalu-range, dan bagaimana membungkus kolom dalam fungsi kembali ke SCAN, semua dibaca dari EXPLAIN QUERY PLAN.

Dataset untuk artikel ini — perf_sales (50.000 baris)

Mulai dari sini, artikel-artikel tuning memakai tabel dengan banyak baris untuk mengamati bagaimana indeks sebenarnya berperilaku.

Artikel ini membahas urutan kolom dari indeks komposit (indeks yang dibangun di beberapa kolom dalam urutan tertentu), aturan leftmost prefix, dan cara menulis kondisi yang bisa memakai indeks (juga disebut SARGable).

Sebelum latihan, lihat sekilas definisi kolom dan ukuran serta sampel data untuk perf_sales. (Jalankan kueri dengan benar dan penjelasan akan dimunculkan.)

① Pakai PRAGMA table_info(perf_sales); untuk melihat nama kolom, tipe, dan primary key.

② Pakai SELECT COUNT(*) FROM perf_sales; untuk mengonfirmasi jumlah baris.

③ Intip 5 baris pertama dengan SELECT emp_id, region, product, amount, sale_date, status FROM perf_sales LIMIT 5; (karena tabel punya 50.000 baris, jangan pernah menarik setiap baris dengan SELECT * — pakai agregasi atau LIMIT untuk mengintip).

SQL Editor

Jalankan query untuk melihat hasil

Urutan kolom indeks komposit — aturan leftmost prefix

Indeks komposit (seperti CREATE INDEX ix ON table(col_A, col_B), mencantumkan beberapa kolom dalam urutan tetap) menyimpan entri-entrinya tepat dalam urutan itu.

Indeks diurutkan seperti kamus: berdasarkan col_A ascending, dan dalam col_A yang sama berdasarkan col_B ascending.

Karena ini, apakah indeks membantu tergantung pada apakah kondisi kamu mencakup kolom secara terus-menerus dari yang utama.

Ini disebut aturan leftmost prefix (indeks komposit hanya membantu kondisi yang ditentukan secara terus-menerus dari kolom utama).

Indeks pada (emp_id, sale_date) bekerja untuk kondisi pada emp_id, atau pada emp_id dan sale_date bersama, tapi tidak untuk kondisi pada sale_date saja — emp_id utama hilang, jadi indeks tidak dipakai.

Aturan leftmost prefix — indeks (emp_id, sale_date)
INDEX ix(emp_id, sale_date)Diurutkan berdasarkan emp_id ->dalam emp_id yang sama,diurutkan berdasarkan sale_dateWHERE emp_id=7Pakai indeks(kolom utama dipakai)WHERE emp_id=7AND sale_date>'...'Pakai indeks(utama lalu kolom berikutnya)WHEREsale_date>'...'Tidak bekerja(emp_id utama hilang)
Indeks komposit (emp_id, sale_date) diurutkan berdasarkan emp_id pertama. Kondisi yang menyertakan emp_id utama bisa memakai indeks; melewati emp_id dan memfilter pada sale_date saja meninggalkan kolom utama hilang, jadi indeks tidak bekerja.
-- Bangun indeks komposit pada region dan product, lalu bandingkan
-- plan untuk kondisi yang menyertakan region utama
-- dengan yang memakai product saja
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(region, product);

-- Memakai kolom utama region -> ix_demo bekerja
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE region = 'East';

-- Melewati region utama dan memfilter berdasarkan product saja -> ix_demo tidak bekerja
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE product = 'P050';

Bangun indeks komposit pada emp_id dan sale_date di atas perf_sales dan konfirmasi aturan leftmost prefix dengan membaca execution plan. Buat ulang indeks dan tampilkan plan di dalam satu eksekusi konsol supaya langkah-langkahnya mandiri. (Jalankan kueri dengan benar dan penjelasan akan dimunculkan.)

① Mulai dengan DROP INDEX IF EXISTS supaya indeks bisa dibangun ulang bahkan kalau yang bernama sama tertinggal.

② Buat indeks komposit pada perf_sales yang mencantumkan emp_id dan sale_date dengan urutan itu.

③ Pakai EXPLAIN QUERY PLAN untuk menampilkan plan untuk kueri agregat yang difilter berdasarkan emp_id saja.

④ Lalu tampilkan plan untuk kueri agregat yang difilter berdasarkan sale_date saja dan bandingkan bagaimana plan berubah dari ③.

SQL Editor

Jalankan query untuk melihat hasil

Cantumkan kolom dalam urutan equality-lalu-range

Ada panduan untuk urutan kolom di indeks komposit.

Taruh kondisi equality (= atau IN yang mematok kolom ke satu nilai) di depan, dan kondisi range (>, >=, <, BETWEEN, dll. yang mencakup rentang) di belakang.

Mematok kolom utama dengan equality menjaga entri yang cocok dalam satu blok kontigu di dalam indeks, jadi kamu bisa mengikis blok itu sekaligus.

Kondisi seperti WHERE emp_id = 7 AND sale_date >= '2024-01-01' adalah kecocokan alami untuk urutan (emp_id, sale_date).

Bagaimana penyaringan equality-pertama, range-kedua bekerja
INDEX (emp_id, sale_date)pengurutan(1) Patok emp_id = 7(2) AND sale_date >= '2024-01-01'(6, 2024-05-10)Di luar blok(7, 2022-03-12)Di dalam blokTanggal terlalu lama(7, 2023-08-20)Di dalam blokTanggal terlalu lama(7, 2024-02-15)Di dalam blokHit(7, 2024-09-30)Di dalam blokHit(8, 2022-01-05)Di luar blok
Indeks (emp_id, sale_date) diurutkan berdasarkan emp_id, dan dalam emp_id yang sama berdasarkan sale_date. (1) Patok kondisi equality emp_id=7 ke blok kontigu, lalu (2) persempit dalam blok itu memakai kondisi range sale_date >= '2024-01-01'.
Urutkan kolom: equality, lalu range
WHERE emp_id=7AND sale_date>='2024-01-01'emp_id = equalitysale_date = range(emp_id, sale_date)equality -> rangePatok emp_id, laluukir rentang sale_date(sale_date, emp_id)range -> equalityRange terbuka dulu,penyaringan kolom berikutnya lemah
Taruh kolom equality emp_id pertama dan kecocokan runtuh ke satu blok kontigu, yang bisa diukir lebih jauh oleh kondisi range pada sale_date. Pimpin dengan kolom range dan indeks terbuka terlalu lebar, jadi penyaringan kolom berikutnya kehilangan traksi.
-- Indeks region (equality) dan amount (range) bersama,
-- dengan kolom equality pertama dan kolom range kedua
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(region, amount);

EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales
WHERE region = 'West' AND amount >= 500000;

Bayangkan kebutuhannya: "Kami ingin merangkum penjualan untuk rep tertentu mulai dari tanggal tertentu." Kondisinya adalah equality pada emp_id dan range pada sale_date. Bangun indeks komposit pada dua kolom ini dalam urutan equality-lalu-range dan konfirmasi dengan execution plan bahwa ia berubah menjadi index seek.

① Jalankan DROP INDEX IF EXISTS untuk membuang indeks sebelumnya.

② Buat indeks komposit dengan kolom equality pertama dan kolom range kedua.

③ Pakai EXPLAIN QUERY PLAN pada kueri agregat yang menggabungkan equality pada emp_id dengan range pada sale_date lewat AND, dan konfirmasi bahwa ia menjadi index seek.

SQL Editor

Jalankan query untuk melihat hasil

Kondisi yang bisa memakai indeks — bandingkan kolom apa adanya, tidak dibungkus

Bahkan dengan indeks di tempat, cara kamu menulis kondisi memutuskan apakah ia dipakai.

Kondisi yang dibentuk supaya indeks bisa mempersempit disebut kondisi yang bisa memakai indeks (dalam bahasa Inggris juga dikenal sebagai SARGable).

Aturan dasarnya: bandingkan kolom yang diindeks apa adanya, tanpa membungkusnya dalam komputasi apa pun.

Sesuatu seperti WHERE emp_id = 7, di mana kolom dibandingkan langsung dengan equality atau range, memungkinkan indeks bekerja.

Di sisi lain, WHERE emp_id + 0 = 7 (kolom ada di dalam ekspresi) atau WHERE substr(sale_date,1,4) = '2024' (kolom dibungkus dalam fungsi) memutus korespondensi dengan pengurutan indeks dan kembali ke full row scan.

Jaga kolom mentah di sisi kiri perbandingan, dan kalau ada penyesuaian yang diperlukan, terapkan ke nilai di sisi kanan.

Kondisi yang dan tidak memakai indeks
Bentuk kondisiIndeksWHERE emp_id = 7(kolom dibandingkan apa adanya)Bekerja(index seek)WHERE emp_id + 0 = 7(kolom di dalam ekspresi)Tidak bekerja(kembali ke full scan)
Bandingkan kolom apa adanya dan indeks bisa mempersempit. Bungkus kolom dalam ekspresi atau fungsi dan ia berhenti berbaris dengan pengurutan indeks, kembali ke full row scan.
-- Arti sama, bentuk berbeda -> plan berbeda
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(amount);

-- Kolom dibandingkan apa adanya -> indeks bekerja, SEARCH
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE amount >= 800000;

-- Kolom di dalam ekspresi -> indeks tidak bekerja, kembali ke SCAN
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE amount + 0 >= 800000;

Amati bagaimana dua kondisi dengan arti sama — satu membandingkan kolom apa adanya dan satu membungkusnya dalam ekspresi — menghasilkan execution plan yang berbeda. Bangun indeks pada emp_id dan bandingkan plan untuk dua bentuk.

① Jalankan DROP INDEX IF EXISTS untuk membuang indeks sebelumnya.

② Buat indeks satu-kolom pada emp_id.

③ Tampilkan plan untuk kueri agregat yang membandingkan emp_id apa adanya dengan equality.

④ Lalu tampilkan plan untuk kueri agregat dengan arti sama tapi dengan emp_id di dalam ekspresi, dan bandingkan bagaimana plan berubah dari ③.

SQL Editor

Jalankan query untuk melihat hasil
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Kondisi mana yang gagal dibantu oleh indeks komposit (emp_id, sale_date)?

Soal 2Ketika memilih urutan kolom untuk indeks komposit yang dibangun untuk kueri dengan kondisi equality dan range, pengurutan mana yang tepat?

Soal 3Mana berikut yang menggambarkan kondisi yang bisa memakai indeks?