Composite Index #

Menambahkan index ke kolom yang sering difilter adalah langkah yang sudah dipahami banyak developer. Tapi saat query melibatkan lebih dari satu kolom — yang hampir selalu terjadi di aplikasi nyata — single-column index sering tidak cukup, atau bahkan tidak terpakai sama sekali. Di sinilah composite index berperan. Composite index memungkinkan database mengoptimalkan query yang memfilter, mengurutkan, atau meng-cover beberapa kolom sekaligus dalam satu struktur B-Tree yang efisien. Tapi composite index punya aturan yang tidak intuitif: urutan kolom menentukan segalanya. Index (status, user_id) dan (user_id, status) adalah dua index yang berbeda secara fundamental, dan salah pilih urutan bisa membuat index sama sekali tidak terpakai. Artikel ini membahas cara kerja internal composite index, aturan leftmost prefix, cara mendesain urutan kolom berdasarkan query nyata, dan kapan composite index bisa sekaligus menggantikan baca ke tabel utama.

Cara Kerja Internal: Struktur B-Tree Composite Index #

Sebelum masuk ke aturan penggunaan, penting untuk memahami bagaimana composite index disimpan secara fisik. Composite index menggunakan struktur B-Tree yang sama dengan single-column index — bedanya, kunci di setiap node B-Tree adalah kombinasi terurut dari beberapa kolom.

Misalnya composite index (user_id, status, created_at) pada tabel orders:

Representasi B-Tree composite index (user_id, status, created_at):
──────────────────────────────────────────────────────────────────────
  Node index disortir berdasarkan user_id DULU,
  lalu di dalam user_id yang sama, disortir berdasarkan status,
  lalu di dalam status yang sama, disortir berdasarkan created_at.

  Contoh entri di B-Tree (diurutkan):
    [user_id=1, status='active',   created_at=2026-01-01] → row ptr
    [user_id=1, status='active',   created_at=2026-01-15] → row ptr
    [user_id=1, status='inactive', created_at=2025-12-01] → row ptr
    [user_id=2, status='active',   created_at=2026-01-10] → row ptr
    [user_id=2, status='active',   created_at=2026-02-05] → row ptr
    [user_id=3, status='inactive', created_at=2026-01-20] → row ptr
    ...
──────────────────────────────────────────────────────────────────────

Implikasi langsung:
  ✓ WHERE user_id = 1
      → Langsung ke bagian B-Tree dengan user_id=1, ambil semua
  ✓ WHERE user_id = 1 AND status = 'active'
      → Langsung ke user_id=1, lalu filter status='active'
  ✓ WHERE user_id = 1 AND status = 'active' AND created_at >= '2026-01-01'
      → Langsung ke titik yang tepat, scan rentang
  ✗ WHERE status = 'active'
      → Tidak bisa langsung ke status tertentu — status tersebar
        di seluruh B-Tree karena urutan pertama adalah user_id
      → Full index scan atau full table scan

Inilah mengapa urutan kolom dalam composite index bukan sekadar konvensi — ia mencerminkan struktur fisik data di disk.


Leftmost Prefix Rule: Aturan Paling Penting #

Aturan paling fundamental dalam composite index adalah leftmost prefix rule: database hanya bisa menggunakan composite index mulai dari kolom paling kiri. Kolom di tengah atau di kanan tidak bisa digunakan secara langsung tanpa kolom-kolom di sebelah kirinya.

-- Composite index: (A, B, C)
CREATE INDEX idx_example ON orders (user_id, status, created_at);

-- ✓ Bisa pakai index — dimulai dari kolom paling kiri (user_id)
WHERE user_id = 1
WHERE user_id = 1 AND status = 'active'
WHERE user_id = 1 AND status = 'active' AND created_at >= '2026-01-01'

-- ✗ TIDAK bisa pakai index secara optimal
WHERE status = 'active'                         -- melewati user_id
WHERE created_at >= '2026-01-01'                -- melewati user_id dan status
WHERE status = 'active' AND created_at >= '2026-01-01'  -- melewati user_id

Ada satu nuansa penting: jika kolom di tengah di-skip tapi ada kondisi equality di kiri, database masih bisa menggunakan bagian index-nya:

-- Index: (user_id, status, created_at)

-- Bisa pakai index untuk user_id, lalu filter created_at di luar index
WHERE user_id = 1 AND created_at >= '2026-01-01'
-- → Pakai index untuk user_id=1, lalu scan semua baris user_id=1
--   dan filter created_at di level engine (tidak optimal tapi pakai index)
-- → Extra: "Using index condition"

-- Vs. query yang pakai semua kolom prefix:
WHERE user_id = 1 AND status = 'active' AND created_at >= '2026-01-01'
-- → Jauh lebih efisien: langsung ke titik yang tepat di B-Tree

Untuk membuktikannya dengan EXPLAIN:

-- Buat tabel dan index untuk demonstrasi
CREATE TABLE orders (
    id         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id    BIGINT UNSIGNED NOT NULL,
    status     VARCHAR(20) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    total      DECIMAL(15,2) NOT NULL,
    PRIMARY KEY (id),
    INDEX idx_orders_composite (user_id, status, created_at)
);

-- Query 1: pakai semua prefix → sangat efisien
EXPLAIN SELECT id, total
FROM orders
WHERE user_id = 42 AND status = 'paid' AND created_at >= '2026-01-01';
-- type: range | key: idx_orders_composite | rows: ~150 | Extra: Using index condition

-- Query 2: skip kolom tengah → masih pakai index tapi kurang efisien
EXPLAIN SELECT id, total
FROM orders
WHERE user_id = 42 AND created_at >= '2026-01-01';
-- type: ref | key: idx_orders_composite | rows: ~8400 | Extra: Using index condition

-- Query 3: mulai dari kolom tengah → index tidak terpakai
EXPLAIN SELECT id, total
FROM orders
WHERE status = 'paid';
-- type: ALL | key: NULL | rows: 2,500,000 | Extra: Using where

Urutan Kolom: Cara Menentukan yang Benar #

Menentukan urutan kolom yang tepat dalam composite index adalah keputusan desain yang harus didasarkan pada pola query nyata di aplikasi, bukan intuisi. Ada beberapa prinsip yang membantu.

Prinsip 1: Kolom Equality Sebelum Kolom Range #

Kolom yang difilter dengan = (equality) harus selalu diletakkan sebelum kolom yang difilter dengan >, <, >=, <=, BETWEEN, atau LIKE 'prefix%' (range).

-- Query yang akan dioptimasi:
SELECT * FROM orders
WHERE user_id = 42
  AND status = 'paid'
  AND created_at >= '2026-01-01';

-- ANTI-PATTERN: kolom range di awal
CREATE INDEX idx_wrong ON orders (created_at, user_id, status);
-- → Index bisa pakai created_at untuk range, tapi user_id dan status
--   tidak bisa dipakai setelah range — database terpaksa scan semua
--   baris dalam rentang created_at, lalu filter user_id dan status

-- BENAR: kolom equality dulu, kolom range terakhir
CREATE INDEX idx_correct ON orders (user_id, status, created_at);
-- → Database langsung ke user_id=42, status='paid',
--   lalu scan range created_at dari titik itu
-- → Jauh lebih sedikit baris yang dibaca

Visualisasi perbedaannya:

Index (created_at, user_id, status) — SALAH URUTAN:
──────────────────────────────────────────────────────────
  Scan dari created_at >= '2026-01-01'
  → Ditemukan: jutaan baris (semua order sejak Jan 2026)
  → Filter user_id = 42 → saring dari jutaan
  → Filter status = 'paid' → saring lagi

Index (user_id, status, created_at) — URUTAN BENAR:
──────────────────────────────────────────────────────────
  Langsung ke user_id = 42, status = 'paid'
  → Ditemukan: ratusan baris user ini dengan status paid
  → Scan range created_at >= '2026-01-01' dari situ
  → Mungkin hanya 50 baris yang relevan

Prinsip 2: Kolom dengan Selectivity Tinggi Lebih Awal (dalam Equality) #

Jika ada beberapa kolom equality, letakkan kolom dengan selectivity tinggi (banyak nilai unik, sedikit duplikat) di depan. Ini membantu database membuang baris yang tidak relevan lebih awal.

-- Tabel products: 1 juta baris
-- category_id: 50 kategori (selectivity rendah, ~20.000 baris per kategori)
-- brand_id: 5.000 brand (selectivity tinggi, ~200 baris per brand)

-- ANTI-PATTERN: category_id (low selectivity) di depan
CREATE INDEX idx_products_wrong ON products (category_id, brand_id, status);
-- WHERE category_id = 5 AND brand_id = 1234 AND status = 'active'
-- → Mulai dari 20.000 baris (category_id=5), filter ke brand, filter ke status

-- BENAR: brand_id (high selectivity) di depan
CREATE INDEX idx_products_correct ON products (brand_id, category_id, status);
-- WHERE category_id = 5 AND brand_id = 1234 AND status = 'active'
-- → Mulai dari ~200 baris (brand_id=1234), filter ke category, filter ke status
-- → Jauh lebih sedikit baris diproses dari awal

-- Catatan: urutan di WHERE tidak harus sama dengan urutan di index
-- Query planner akan menyesuaikan otomatis

Prinsip 3: Selaraskan dengan ORDER BY #

Composite index yang mencakup kolom WHERE sekaligus kolom ORDER BY bisa menghilangkan operasi sort sepenuhnya — database tinggal scan index yang sudah terurut.

-- Query: artikel terbaru dari penulis tertentu yang sudah dipublish
SELECT id, title, published_at
FROM articles
WHERE author_id = 7 AND status = 'published'
ORDER BY published_at DESC
LIMIT 20;

-- ANTI-PATTERN: index hanya untuk WHERE, ORDER BY tidak tercakup
CREATE INDEX idx_articles_filter ON articles (author_id, status);
-- → Database bisa filter dengan index, tapi sort published_at butuh filesort
-- Extra: "Using index condition; Using filesort"

-- BENAR: tambahkan kolom ORDER BY ke index
CREATE INDEX idx_articles_full ON articles (author_id, status, published_at);
-- → Database scan index dari (author_id=7, status='published')
--   dan baris sudah dalam urutan published_at
-- → Tidak ada filesort
-- Extra: "Using index condition" (tanpa filesort)

-- Untuk DESC: di MySQL 8.0+ bisa tentukan arah sort per kolom
CREATE INDEX idx_articles_desc ON articles (author_id, status, published_at DESC);
-- → Order BY published_at DESC sekarang tanpa filesort dan tanpa backward scan

Covering Index: Eliminasi Baca ke Tabel Utama #

Covering index adalah situasi di mana semua kolom yang dibutuhkan query tersedia di dalam index — sehingga database tidak perlu membaca ke tabel utama (heap) sama sekali. Ini adalah tingkat optimasi tertinggi yang bisa dicapai dengan index.

-- Query yang akan dioptimasi
SELECT id, title, status, published_at
FROM articles
WHERE author_id = 7 AND status = 'published'
ORDER BY published_at DESC
LIMIT 20;

-- Index yang hanya mencakup kolom WHERE dan ORDER BY
CREATE INDEX idx_articles_where_order ON articles (author_id, status, published_at);

-- Database masih perlu baca ke tabel untuk kolom 'title' dan 'id'
-- Extra: "Using index condition" (masih ada table lookup)

-- Covering index: tambahkan semua kolom yang di-SELECT
CREATE INDEX idx_articles_covering ON articles (author_id, status, published_at, title, id);
-- Sekarang semua kolom yang dibutuhkan ada di index
-- Extra: "Using index" ← ini tanda covering index aktif
-- Tidak ada baca ke tabel utama sama sekali

Visualisasi perbedaan akses data:

Tanpa covering index:
──────────────────────────────────────────────────────────────
  Query → B-Tree index → temukan row pointer
       → Lompat ke halaman heap → baca baris lengkap
       → Ulangi untuk setiap baris yang cocok

  Jika 50 baris cocok → 50 lompatan ke heap
  Jika halaman heap tidak di buffer → 50 disk I/O

Dengan covering index:
──────────────────────────────────────────────────────────────
  Query → B-Tree index → semua data ada di sini
       → Baca langsung dari index, tidak perlu ke heap

  Sama sekali tidak ada lompatan ke tabel utama
  → Jauh lebih sedikit I/O
  → Jauh lebih sedikit memory pressure
Covering index sangat efektif untuk query pagination yang sering dijalankan, endpoint listing dengan kolom terbatas, dan laporan yang mengambil kolom-kolom yang sama berulang kali. Tambahkan kolom SELECT ke index hanya jika kolom tersebut kecil (integer, enum, timestamp) — jangan masukkan TEXT atau BLOB ke dalam covering index.

Kasus Nyata: Mendesain Composite Index dari Query #

Berikut empat skenario query produksi yang umum, lengkap dengan proses berpikir mendesain composite index-nya.

Kasus 1: Filter Multi-Kolom dengan Pagination #

-- Query: daftar order user tertentu, filter status, urutkan terbaru, paginasi
SELECT id, product_id, total, status, created_at
FROM orders
WHERE user_id = 42
  AND status IN ('pending', 'paid')
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;

-- Analisis:
-- → user_id: equality, selectivity tinggi → posisi 1
-- → status: equality (IN = multiple equality) → posisi 2
-- → created_at: ORDER BY → posisi 3 (sudah tidak ada range WHERE)
-- → id, product_id, total: kolom SELECT → tambahkan untuk covering

-- Index optimal:
CREATE INDEX idx_orders_user_status_created
    ON orders (user_id, status, created_at DESC, id, product_id, total);

-- Verifikasi:
EXPLAIN SELECT id, product_id, total, status, created_at
FROM orders
WHERE user_id = 42 AND status IN ('pending', 'paid')
ORDER BY created_at DESC LIMIT 20;
-- → type: range, key: idx_orders_user_status_created
-- → Extra: Using index (covering index aktif, tidak ada filesort)

Kasus 2: Filter Waktu dengan Status per Tenant #

-- Query: laporan transaksi tenant dalam rentang waktu, filter status
SELECT id, amount, type, created_at
FROM transactions
WHERE tenant_id = 'tenant-abc'
  AND status = 'completed'
  AND created_at BETWEEN '2026-01-01' AND '2026-01-31';

-- Analisis:
-- → tenant_id: equality → posisi 1
-- → status: equality → posisi 2
-- → created_at: BETWEEN (range) → posisi 3, harus setelah equality

-- ANTI-PATTERN: created_at di awal
-- CREATE INDEX idx_wrong ON transactions (created_at, tenant_id, status);
-- → Scan seluruh bulan Januari (semua tenant), baru filter tenant dan status

-- BENAR:
CREATE INDEX idx_transactions_tenant_status_date
    ON transactions (tenant_id, status, created_at);
-- → Langsung ke tenant_id='tenant-abc' + status='completed'
-- → Scan range created_at dari titik itu
-- → Hanya baca transaksi yang relevan

Kasus 3: Soft Delete dengan Filter Aktif #

-- Query: produk aktif berdasarkan kategori, urutkan berdasarkan nama
SELECT id, name, price, stock
FROM products
WHERE category_id = 15
  AND deleted_at IS NULL
ORDER BY name ASC;

-- Kolom deleted_at IS NULL adalah kondisi yang hampir selalu true
-- (sebagian besar baris belum dihapus) → selectivity sangat rendah
-- Jangan letakkan di depan

-- Index yang tepat:
CREATE INDEX idx_products_category_deleted_name
    ON products (category_id, deleted_at, name);
-- → category_id pertama (selectivity lebih tinggi)
-- → deleted_at: meskipun selectivity rendah, harus ada agar name
--   bisa dimanfaatkan untuk sort tanpa filesort
-- → name: untuk ORDER BY name ASC

-- Catatan: IS NULL bisa dipakai dalam B-Tree index di MySQL dan PostgreSQL
-- Nilai NULL disimpan dan bisa di-index

Kasus 4: Multi-Tenant dengan Status dan Waktu Kadaluarsa #

-- Query: notifikasi yang belum dibaca untuk user tertentu, belum expired
SELECT id, title, type, created_at
FROM notifications
WHERE user_id = 99
  AND is_read = FALSE
  AND (expires_at IS NULL OR expires_at > NOW());

-- Kolom expires_at punya kondisi OR yang kompleks
-- Untuk kasus seperti ini, composite index sederhana sudah membantu:
CREATE INDEX idx_notifications_user_read_created
    ON notifications (user_id, is_read, created_at DESC);
-- → Database langsung ke user_id=99, is_read=FALSE
-- → expires_at di-filter di engine setelah index lookup (Using index condition)
-- → Tidak perlu full scan — baris yang diproses sudah sangat sedikit

Composite Index vs Multiple Single-Column Index #

Pertanyaan yang sering muncul: lebih baik satu composite index atau beberapa single-column index? Jawabannya hampir selalu composite index untuk query yang melibatkan beberapa kolom bersama-sama.

-- Tabel: orders (5 juta baris)
-- Query dominan: WHERE user_id = ? AND status = ?

-- Pendekatan A: dua single-column index
CREATE INDEX idx_orders_user_id ON orders (user_id);
CREATE INDEX idx_orders_status  ON orders (status);

-- Apa yang terjadi saat query dijalankan:
-- MySQL bisa merge dua index (index merge), tapi ini jarang optimal:
-- 1. Baca semua baris user_id=42 dari idx_orders_user_id → 8.000 baris
-- 2. Baca semua baris status='paid' dari idx_orders_status → 1.200.000 baris
-- 3. Intersect kedua set → operasi merge yang mahal
-- Extra: "Using intersect(idx_orders_user_id, idx_orders_status); Using where"

-- Pendekatan B: satu composite index
CREATE INDEX idx_orders_user_status ON orders (user_id, status);
-- → Langsung ke user_id=42 AND status='paid' → mungkin 400 baris
-- → Tidak ada merge, tidak ada intersect
-- Extra: "Using index condition"

-- Perbandingan performa:
-- Pendekatan A: baca 8.000 + 1.200.000 baris, merge → lambat
-- Pendekatan B: baca ~400 baris langsung → cepat
MySQL kadang memilih “index merge” ketika ada beberapa single-column index yang relevan. Ini terlihat di EXPLAIN sebagai Using intersect(...) atau Using union(...). Meskipun lebih baik dari full scan, ini hampir selalu lebih lambat dari composite index yang tepat. Jika kamu melihat index merge di EXPLAIN, itu sinyal bahwa composite index perlu dibuat.

Anti-Pattern yang Harus Dihindari #

-- ✗ Anti-pattern 1: urutan kolom terbalik (range sebelum equality)
CREATE INDEX idx_wrong ON orders (created_at, user_id, status);
-- WHERE user_id = 42 AND status = 'paid' AND created_at >= '2026-01-01'
-- → Database mulai dari range created_at, baru filter user_id dan status
-- ✓ Solusi: (user_id, status, created_at)

-- ✗ Anti-pattern 2: composite index yang redundant dengan prefix-nya
CREATE INDEX idx_a ON orders (user_id);
CREATE INDEX idx_b ON orders (user_id, status);          -- idx_a sudah tercakup
CREATE INDEX idx_c ON orders (user_id, status, created_at);  -- idx_b sudah tercakup
-- Tiga index, tapi idx_a dan idx_b tidak pernah dipakai karena idx_c mencakup semuanya
-- Overhead write untuk tiga index, manfaat hanya dari satu
-- ✓ Solusi: hapus idx_a dan idx_b, pertahankan idx_c saja

-- ✗ Anti-pattern 3: terlalu banyak kolom dalam satu composite index
CREATE INDEX idx_too_wide ON products
    (category_id, brand_id, status, price, name, description, stock, weight);
-- → Index sangat besar, lambat saat write
-- → Kolom description (TEXT) di index tidak efektif
-- ✓ Solusi: hanya masukkan kolom yang benar-benar dipakai di query

-- ✗ Anti-pattern 4: composite index untuk query yang berbeda-beda pola
-- Query A: WHERE user_id = ? AND status = ?
-- Query B: WHERE status = ? AND created_at >= ?
-- Membuat satu index untuk keduanya tidak mungkin optimal untuk keduanya
-- CREATE INDEX idx_compromise ON orders (user_id, status, created_at);
-- → Query B tetap tidak bisa pakai index karena mulai dari status
-- ✓ Solusi: buat dua index terpisah sesuai pola masing-masing query

-- ✗ Anti-pattern 5: tidak memeriksa EXPLAIN setelah membuat composite index
-- Index dibuat tapi tidak diverifikasi apakah benar-benar dipakai
-- ✓ Solusi: selalu jalankan EXPLAIN setelah membuat index baru

Decision Tree Desain Composite Index #

Gunakan alur berikut saat mendesain composite index untuk query baru:

LANGKAH 1: Identifikasi semua kondisi di query
    │
    ▼
LANGKAH 2: Pisahkan kolom berdasarkan jenis kondisi
    │
    ├─ Kolom equality (=, IN)  → kelompok A
    ├─ Kolom range (>, <, BETWEEN, LIKE 'x%') → kelompok B
    └─ Kolom ORDER BY → kelompok C
    │
    ▼
LANGKAH 3: Tentukan urutan dalam composite index
    │
    ├─ Kolom A (equality) dulu, urutkan dari selectivity tertinggi
    ├─ Kolom B (range) setelah semua equality
    └─ Kolom C (ORDER BY) setelah range (jika tidak ada range)
         atau sama posisi dengan range jika kolom ORDER BY = kolom range
    │
    ▼
LANGKAH 4: Pertimbangkan covering index
    │
    └─ Tambahkan kolom SELECT yang kecil (int, timestamp, enum)
         jika query ini sering dijalankan dan bisa dapat "Using index"
    │
    ▼
LANGKAH 5: Verifikasi dengan EXPLAIN
    │
    ├─ type = range atau ref → baik
    ├─ Extra tidak mengandung "filesort" → baik
    ├─ Extra mengandung "Using index" → optimal (covering index)
    └─ Jika masih ada masalah → kembali ke langkah 2
    │
    ▼
LANGKAH 6: Cek redundansi
    └─ Apakah ada index lain yang prefix-nya sama?
         Jika ya → pertimbangkan untuk menghapusnya

Checklist Review Composite Index #

SAAT MEMBUAT COMPOSITE INDEX BARU:
  □ Urutan kolom: equality sebelum range sebelum ORDER BY?
  □ Selectivity dipertimbangkan untuk kolom equality?
  □ Sudah diverifikasi dengan EXPLAIN setelah index dibuat?
  □ EXPLAIN tidak menunjukkan "filesort" yang tidak perlu?
  □ EXPLAIN tidak menunjukkan "index merge" (sinyal butuh composite)?
  □ Tidak ada index lain yang menjadi redundant?
  □ Kolom TEXT/BLOB tidak dimasukkan ke dalam index?

SAAT AUDIT INDEX YANG SUDAH ADA:
  □ Ada index yang prefix-nya merupakan subset dari index lain?
     (idx pada (A) sementara juga ada idx pada (A, B) → hapus (A))
  □ Ada index yang tidak pernah dipakai menurut sys.schema_index_statistics?
  □ Ada query dengan "index merge" di EXPLAIN yang bisa digabung?
  □ Ada query dengan "filesort" yang bisa dihilangkan dengan extend index?

Ringkasan #

  • Composite index disimpan sebagai B-Tree terurut — urutan kolom mencerminkan struktur fisik data. Index (A, B, C) mengurutkan entri by A dulu, lalu B dalam A yang sama, lalu C dalam B yang sama.
  • Leftmost prefix rule adalah aturan fundamental — database hanya bisa menggunakan index dari kolom paling kiri. Query yang melewati kolom pertama tidak bisa memanfaatkan composite index sama sekali.
  • Kolom equality selalu sebelum kolom range — letakkan kolom yang difilter dengan = atau IN di depan, lalu kolom range (>, <, BETWEEN) di belakang. Urutan terbalik membuat database scan terlalu banyak baris.
  • Sertakan kolom ORDER BY di akhir index — jika urutan sort selaras dengan urutan di index, database tidak perlu melakukan filesort. Ini menghilangkan Using filesort di EXPLAIN.
  • Covering index menghilangkan baca ke tabel utama — tambahkan kolom SELECT yang kecil ke index agar query mendapat Using index di EXPLAIN, artinya tidak ada lompatan ke heap sama sekali.
  • Composite index lebih baik dari index merge — dua single-column index yang di-merge jauh lebih lambat dari satu composite index yang tepat. Using intersect(...) di EXPLAIN adalah sinyal untuk membuat composite index.
  • Kolom redundant harus dihapus — index (A) tidak berguna jika sudah ada index (A, B) karena semua query yang bisa pakai (A) juga bisa pakai (A, B). Index redundant hanya menambah overhead write.
  • Selalu verifikasi dengan EXPLAIN — desain yang kelihatan benar secara teori bisa saja tidak dipilih oleh query planner. EXPLAIN adalah satu-satunya cara membuktikan bahwa index benar-benar terpakai.

← Sebelumnya: SQL Function Overuse   Berikutnya: COUNT() →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact