Penurunan performa mendadak pada database sering kali berakar dari hal sepele: ketidakcocokan tipe data (type mismatch) antara nilai parameter aplikasi dan tipe kolom target. Masalah ini memicu konversi tipe implisit (implicit type conversion), memaksa query planner membatalkan operasi B-Tree Index Scan dan beralih ke Full Table Scan (Sequential Scan). Artikel ini mengupas mekanisme query planner saat type mismatch terjadi dan cara mengatasinya secara efektif di PostgreSQL dan MySQL.

Mekanisme Type Inference dan Masalah Implicit Type Conversion

Saat parser SQL menerima query, tahap pertama yang dilakukan adalah type resolution atau type inference. Parser memeriksa tipe data kolom dan parameter literal. Jika tipe data berbeda (misalnya membandingkan string dengan integer), database harus menstandarkan keduanya ke tipe data umum melalui type coercion sesuai aturan presedensi operator.

Titik kegagalannya berada pada sisi mana konversi tersebut dieksekusi:

  • Jika literal/parameter dikonversi ke tipe kolom: Kondisi predikat tetap sargable (Search Argument Able). B-Tree Index tetap dapat digunakan karena nilai kolom tidak dimodifikasi.
  • Jika kolom dikonversi ke tipe literal: Engine membungkus kolom ke dalam fungsi konversi implisit, misalnya CAST(kolom AS target_type) = input_literal. Indeks B-Tree standar tidak dapat mengevaluasi nilai hasil fungsi tersebut per baris secara langsung, sehingga database terpaksa melakukan Sequential Scan.

Pada MySQL, aturan presedensi numerik lebih tinggi dibanding string. Jika kolom VARCHAR dibandingkan dengan integer angka, MySQL mengonversi setiap nilai pada kolom menjadi floating-point/integer sebelum perbandingan dilakukan. PostgreSQL lebih ketat: engine sering memunculkan error operator does not exist, atau jika casting implisit terdaftar dalam sistem katalog (pg_cast), PostgreSQL mengevaluasi fungsi konversi pada kolom yang menghilangkan selektivitas indeks.

Analisis Kasus: VARCHAR vs INT pada PostgreSQL

Perhatikan skenario umum tabel pengguna berikut yang menyimpan nomor identitas (misal NIK atau nomor telepon) dalam tipe VARCHAR yang telah diindeks:

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    identity_number VARCHAR(64) NOT NULL,
    full_name VARCHAR(100) NOT NULL
);

CREATE INDEX idx_users_identity_number ON users (identity_number);

Pengembang di backend sering kali mem-passing input sebagai numerik primitif (contoh binding typed integer dari ORM) ke query database:

-- Query bermasalah
SELECT full_name FROM users WHERE identity_number = 1234567890;

Hasil dari EXPLAIN ANALYZE pada PostgreSQL menunjukkan pola degradasi berikut:

Seq Scan on users  (cost=0.00..25412.00 rows=1 width=32) (actual time=45.123..120.450 rows=1 loops=1)
  Filter: ((identity_number)::numeric = '1234567890'::numeric)
  Rows Removed by Filter: 999999
Planning Time: 0.150 ms
Execution Time: 120.512 ms

Database mengonversi seluruh nilai kolom identity_number menjadi tipe numeric untuk setiap baris. Karena operasi perbandingan membutuhkan komputasi fungsi pada kolom data mentah, indeks idx_users_identity_number diabaikan secara total.

Solusi 1: Explicit Casting yang Sargable

Solusi paling bersih dan hemat resource adalah memastikan konversi dilakukan pada parameter input (literal), bukan pada kolom database. Dengan pendekatan ini, struktur data kolom tetap murni sehingga engine dapat langsung menavigasi struktur B-Tree.

-- Perbaikan: cast parameter, biarkan kolom tetap VARCHAR
SELECT full_name FROM users WHERE identity_number = '1234567890';
-- Atau menggunakan explicit cast parameter di PostgreSQL
SELECT full_name FROM users WHERE identity_number = 1234567890::text;

Hasil EXPLAIN ANALYZE setelah perbaikan casting parameter:

Index Scan using idx_users_identity_number on users  (cost=0.42..8.44 rows=1 width=32) (actual time=0.045..0.048 rows=1 loops=1)
  Index Cond: ((identity_number)::text = '1234567890'::text)
Planning Time: 0.112 ms
Execution Time: 0.071 ms

Waktu eksekusi terpangkas dari ratusan milidetik menjadi di bawah 1 milidetik karena I/O disk berkurang secara drastis dari membaca seluruh blok tabel menjadi hanya beberapa blok node indeks.

Solusi 2: Functional Index (Jika Aplikasi Tidak Bisa Diubah)

Ketika codebase aplikasi legacy sulit diubah atau integrasi pihak ketiga terus mengirimkan format parameter yang tidak konsisten, solusi alternatif pada layer database adalah membuat functional/expression index.

Jika data kolom string dijamin selalu valid sebagai representasi numerik, Anda dapat membuat indeks berbasis ekspresi ekspisit:

-- PostgreSQL: Functional Index
CREATE INDEX idx_users_identity_numeric ON users ((identity_number::numeric));

-- Query yang kini menggunakan functional index
SELECT full_name FROM users WHERE identity_number::numeric = 1234567890;
Peringatan Trade-off: Functional index menambah overhead I/O dan memori saat operasi INSERT dan UPDATE, serta dapat gagal (melempar runtime exception) jika ada string yang tidak dapat di-cast ke angka (misal mengandung karakter non-digit). Prioritaskan selalu perbaikan tipe data pada sisi client/query.

Contoh Masalah: TIMESTAMP vs TEXT

Masalah serupa sering terjadi pada pemfilteran kolom temporal. Membandingkan kolom TIMESTAMP WITH TIME ZONE langsung dengan string format tanggal yang ambigu sering memicu type mismatch parsing:

-- Buruk: Bergantung pada implicit conversion dari session locale
SELECT * FROM orders WHERE created_at = '2026-03-30';

-- Baik: Menggunakan rentang tanggal yang sargable dengan tipe pasti
SELECT * FROM orders 
WHERE created_at >= '2026-03-30 00:00:00Z'::timestamptz 
  AND created_at < '2026-03-31 00:00:00Z'::timestamptz;

Checklist Mitigasi Bottleneck Type Mismatch

  1. Audit ORM Type Mapping: Pastikan skema entity di aplikasi (Go struct tag, Hibernate entity, TypeScript Prisma schema) sinkron 1:1 dengan tipe kolom SQL.
  2. Monitor Sequential Scan: Pantau metrik PostgreSQL melalui view pg_stat_user_tables pada kolom seq_scan vs idx_scan untuk mengidentifikasi tabel berukuran besar yang jarang memanfaatkan indeks.
  3. Validasi Parameter Binding: Hindari memasukkan nilai parameter sebagai untyped string jika kolomnya adalah numerik, atau sebaliknya.