# Perilaku Indeks PostgreSQL

#### Studi Kasus: Indeks Tunggal

Saat kamu membuat indeks pada satu kolom dan menggunakannya dalam klausa `WHERE`, PostgreSQL akan melakukan `Index Scan` untuk menemukan data, yang jauh lebih cepat daripada membaca seluruh tabel.

* Tabel & Indeks:

  ```sql
  CREATE TABLE users (
      user_id SERIAL PRIMARY KEY,
      username VARCHAR(50) UNIQUE,
      email VARCHAR(100),
      registered_at DATE
  );

  CREATE INDEX idx_users_username ON users(username);
  ```
* Kueri & Analisis:

  ```sql
  EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john_doe';
  ```
* Perilaku & Hasil yang Diharapkan:

  PostgreSQL akan melihat klausa WHERE dan menemukan bahwa ada indeks yang cocok pada kolom username. Alih-alih melakukan Seq Scan (Sequential Scan), query planner akan memilih Index Scan. Kamu akan melihat Index Scan atau Index Only Scan di hasil EXPLAIN ANALYZE.

  `Index Only Scan` terjadi jika semua kolom yang diminta dalam `SELECT` (dalam hal ini `*`) juga ada di indeks. Untuk indeks tunggal ini, ini tidak akan terjadi, tapi kamu pasti akan melihat `Index Scan`.

  Contoh hasil `EXPLAIN ANALYZE`:

  ```bash
  Index Scan using idx_users_username on users  (cost=0.29..8.30 rows=1 width=122) (actual time=0.010..0.011 rows=1 loops=1)
  ...
  ```

  Perhatikan kata `Index Scan` yang menunjukkan bahwa indeks telah digunakan.

***

#### Studi Kasus: Indeks Majemuk (Composite Index)

Ketika kamu sering memfilter data menggunakan kombinasi kolom (misalnya, mencari pengguna yang terdaftar pada tanggal tertentu dengan nama tertentu), membuat indeks majemuk adalah cara paling efisien.

* Tabel & Indeks:

  ```sql
  CREATE INDEX idx_users_username_registered ON users(username, registered_at);
  ```
* Kueri & Analisis:
  * Kueri 1 (menggunakan dua kolom indeks):

    ```sql
    EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'jane_doe' AND registered_at >= '2025-01-01';
    ```

    Perilaku: PostgreSQL akan melakukan `Index Scan` yang sangat efisien karena kedua kolom ada di indeks. Ini adalah skenario yang paling optimal.
  * Kueri 2 (menggunakan kolom pertama saja):

    ```sql
    EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john_doe';
    ```

    Perilaku: Indeks majemuk masih bisa digunakan untuk mencari berdasarkan kolom pertamanya (`username`). `Index Scan` akan tetap terjadi, menunjukkan bahwa indeks tersebut dapat digunakan secara parsial.
  * Kueri 3 (menggunakan kolom kedua saja):

    ```sql
    EXPLAIN ANALYZE SELECT * FROM users WHERE registered_at = '2025-02-15';
    ```

    Perilaku: Indeks ini TIDAK akan digunakan. *Query planner* tidak dapat menggunakan indeks majemuk jika hanya kolom kedua (atau kolom di tengah) yang digunakan dalam klausa `WHERE`. Ia akan beralih ke `Seq Scan` karena tidak ada cara lain yang lebih cepat.

***

#### Studi Kasus: Multiple Indeks Terpisah

Skenario ini terjadi ketika kamu membuat indeks terpisah pada setiap kolom yang akan digunakan dalam klausa `WHERE`.

* Tabel & Indeks:

  ```sql
  -- Hapus indeks sebelumnya jika ada
  DROP INDEX IF EXISTS idx_users_username;
  DROP INDEX IF EXISTS idx_users_username_registered;

  CREATE INDEX idx_users_username ON users(username);
  CREATE INDEX idx_users_registered ON users(registered_at);
  ```
* Kueri & Analisis:

  ```sql
  EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'jane_doe' AND registered_at >= '2025-01-01';
  ```
* Perilaku & Hasil yang Diharapkan:

  PostgreSQL, melalui query planner-nya, akan melihat dua indeks yang potensial. Ia bisa:

  1. Menggunakan `Index Scan` pada `idx_users_username`, lalu memfilter hasilnya berdasarkan `registered_at`.
  2. Menggunakan `Index Scan` pada `idx_users_registered`, lalu memfilter hasilnya berdasarkan `username`.
  3. Melakukan `Bitmap Index Scan`. Ini adalah teknik canggih di mana PostgreSQL menggunakan kedua indeks secara bersamaan. Ia membuat *bitmap* (peta bit) dari hasil indeks pertama dan *bitmap* dari hasil indeks kedua. Kemudian, ia menggabungkan kedua *bitmap* tersebut untuk menemukan baris yang memenuhi kedua syarat.

  Contoh hasil `EXPLAIN ANALYZE`:

  ```bash
  Bitmap Heap Scan on users  (cost=12.28..12.29 rows=1 width=122) (actual time=0.012..0.012 rows=1 loops=1)
    Recheck Cond: ((username = 'jane_doe'::text) AND (registered_at >= '2025-01-01'::date))
    ->  BitmapAnd  (cost=12.28..12.28 rows=1 width=0) (actual time=0.009..0.009 rows=0 loops=1)
          ->  Bitmap Index Scan on idx_users_username  (cost=0.00..6.14 rows=1 width=0) (actual time=0.007..0.007 rows=1 loops=1)
                Index Cond: (username = 'jane_doe'::text)
          ->  Bitmap Index Scan on idx_users_registered  (cost=0.00..6.14 rows=1 width=0) (actual time=0.002..0.002 rows=1 loops=1)
                Index Cond: (registered_at >= '2025-01-01'::date)
  ```

  Perhatikan istilah `Bitmap Index Scan` dan `BitmapAnd`. Ini adalah petunjuk bahwa *query planner* berhasil menggunakan lebih dari satu indeks untuk memproses kueri.

Sekarang mari kita lihat kasus di mana indeks yang sudah dibuat tidak digunakan oleh PostgreSQL karena kesalahan dalam kueri. Ini sering terjadi dan penting untuk dipahami agar kamu bisa menulis kueri yang lebih optimal.

#### Studi Kasus: Indeks Tidak Terpakai Karena Operasi pada Kolom

Skenario ini terjadi ketika kamu membuat indeks pada sebuah kolom, tetapi dalam kueri, kamu menerapkan fungsi atau operasi lain pada kolom tersebut di dalam klausa `WHERE`. PostgreSQL biasanya tidak akan menggunakan indeks dalam kasus seperti ini.

* Tabel & Indeks:

  Kita gunakan tabel users yang sama, tapi kali ini kita membuat indeks pada kolom registered\_at.

  ```sql
  CREATE TABLE users (
      user_id SERIAL PRIMARY KEY,
      username VARCHAR(50) UNIQUE,
      email VARCHAR(100),
      registered_at DATE
  );

  -- Buat indeks pada tanggal registrasi
  CREATE INDEX idx_users_registered_at ON users(registered_at);
  ```
* Kueri yang Salah & Analisis:

  Anggap kamu ingin mencari semua pengguna yang terdaftar pada bulan Juni di tahun 2025. Secara intuitif, kamu mungkin akan menulis kueri seperti ini:

  SQL

  ```
  EXPLAIN ANALYZE SELECT * FROM users WHERE MONTH(registered_at) = 6 AND YEAR(registered_at) = 2025;
  ```

  Perilaku & Hasil yang Diharapkan:

  Meskipun ada indeks pada kolom registered\_at, query planner tidak akan menggunakannya. Kenapa? Karena klausa WHERE tidak membandingkan kolom registered\_at secara langsung. Sebaliknya, ia membandingkan hasil dari fungsi MONTH() dan YEAR() yang diaplikasikan pada kolom tersebut.

  Untuk menggunakan indeks, PostgreSQL perlu membandingkan nilai kolom secara langsung dengan suatu nilai atau ekspresi lain. Karena `MONTH(registered_at)` dan `YEAR(registered_at)` harus dihitung untuk setiap baris, PostgreSQL akan beralih ke `Seq Scan` (Sequential Scan) yang lambat.

  Contoh hasil `EXPLAIN ANALYZE`:

  ```
  Seq Scan on users  (cost=0.00..33.12 rows=195 width=12) (actual time=0.009..0.010 rows=2 loops=1)
    Filter: ((date_part('month'::text, registered_at) = '6'::double precision) AND (date_part('year'::text, registered_at) = '2025'::double precision))
  ...
  ```

  Perhatikan kata `Seq Scan` yang menunjukkan PostgreSQL membaca seluruh tabel dari awal sampai akhir.

***

#### Solusi: Menggunakan Kueri yang Benar

Untuk memastikan indeks digunakan, kamu harus mengubah kueri agar perbandingannya langsung pada kolom yang diindeks. Alih-alih menggunakan fungsi, gunakan rentang waktu (range).

* Kueri yang Benar & Analisis:

  SQL

  ```
  EXPLAIN ANALYZE SELECT * FROM users WHERE registered_at >= '2025-06-01' AND registered_at < '2025-07-01';
  ```
* Perilaku & Hasil yang Diharapkan:

  Kali ini, query planner akan melihat bahwa kamu membandingkan kolom registered\_at secara langsung dengan literal tanggal. Ini memungkinkan penggunaan indeks idx\_users\_registered\_at. PostgreSQL akan melakukan Index Scan yang cepat dan efisien.

  Contoh hasil `EXPLAIN ANALYZE`:

  ```
  Index Scan using idx_users_registered_at on users  (cost=0.29..8.30 rows=1 width=122) (actual time=0.010..0.011 rows=1 loops=1)
    Index Cond: ((registered_at >= '2025-06-01'::date) AND (registered_at < '2025-07-01'::date))
  ...
  ```

Ini adalah contoh yang jelas bahwa indeks hanya berguna jika kueri memanfaatkannya dengan benar. Dengan memahami cara kerja `EXPLAIN ANALYZE`, kamu bisa mengidentifikasi masalah seperti ini dan mengoptimalkan kuerimu.
