Menekan Enter di psql dan melihat baris data muncul terasa instan — padahal di dalam server, query kita melewati pipa pemrosesan yang panjang dan berlapis. Memahami pipa itu bukan pengetahuan akademik: kebiasaan membaca EXPLAIN, memahami prepared statement, dan tahu kapan rencana query dihitung ulang akan menentukan kualitas aplikasi yang kita bangun. Chapter ini menelusuri perjalanan satu query SELECT dari client sampai hasil kembali, dengan demonstrasi nyata di setiap tahap.
Perjalanan Singkat Sebuah Query #
PostgreSQL bekerja dalam arsitektur client/server: aplikasi kita (psql, aplikasi web, script) berperan sebagai client yang mengirim perintah SQL lewat koneksi, dan backend process di server yang menanganinya. Setelah perintah sampai, backend memrosesnya lewat tahap-tahap berikut:
Pada tahap parse, backend membaca teks SQL dan memeriksa sintaksnya — apakah kata-katanya membentuk kalimat SQL yang sah. Tahap ini juga memeriksa bahwa tabel dan kolom yang disebut benar-benar ada, lalu hasilnya menjadi parse tree (pohon struktur perintah). Pada tahap plan, planner (atau optimizer) mengubah pohon itu menjadi rencana eksekusi: urutan langkah paling murah untuk mendapatkan hasil — memakai index atau membaca seluruh tabel, join duluan atau filter duluan. Pada tahap execute, executor menjalankan rencana langkah demi langkah, mengambil baris dari shared buffers (memori server), dan mengirim hasilnya ke client.
Untuk perintah tulis seperti INSERT, ada satu langkah lagi yang krusial: saat transaksi di-COMMIT, perubahan dicatat dulu ke WAL (Write-Ahead Log) di disk sebelum dianggap resmi. Mekanisme ini dibahas lebih dalam di chapter tentang transaksi.
Menyiapkan Tabel Contoh #
Seluruh demonstrasi chapter ini memakai satu tabel kecil berisi pesanan Toko Maju Jaya. File berikut membuat tabelnya, mengisi enam baris dengan tanggal tetap, lalu menjalankan ANALYZE agar statistik tabel selalu segar:
CREATE TABLE pesanan (
id integer PRIMARY KEY,
pelanggan text NOT NULL,
barang text NOT NULL,
jumlah integer NOT NULL,
tanggal date NOT NULL
);
INSERT INTO pesanan (id, pelanggan, barang, jumlah, tanggal) VALUES
(1, 'Budi', 'Buku Tulis', 2, DATE '2026-01-10'),
(2, 'Siti', 'Pulpen Hitam', 5, DATE '2026-01-11'),
(3, 'Budi', 'Kertas HVS A4', 1, DATE '2026-01-12'),
(4, 'Siti', 'Stapler Besar', 1, DATE '2026-01-12'),
(5, 'Budi', 'Pulpen Hitam', 3, DATE '2026-01-15'),
(6, 'Siti', 'Buku Tulis', 4, DATE '2026-01-15');
ANALYZE pesanan;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 6
ANALYZESatu baris di file itu penting untuk pemahaman kita: ANALYZE pesanan memperbarui statistik — berapa baris tabelnya, nilai apa yang sering muncul di tiap kolom. Planner memakai statistik ini untuk menebak biaya tiap rencana, sehingga keputusan EXPLAIN di bawah menjadi stabil.
Tahap Parse: Gerbang Sintaks #
Tahap pertama paling mudah dibuktikan dengan cara membuatnya gagal. File berikut berisi query yang polos tetapi salah ketik — FORM alih-alih FROM:
SELECT * FORM pesanan;Output yang Dihasilkan:
psql:kesalahan-sintaks.sql:1: ERROR: syntax error at or near "FORM"
LINE 1: SELECT * FORM pesanan;
^Perhatikan isi pesan galatnya. Server menolak query sebelum menyentuh tabel sama sekali: ERROR: syntax error at or near "FORM" menunjukkan parser berhenti tepat di kata yang tidak dikenalnya, dan baris LINE 1 menandai posisi kesalahan lengkap dengan tanda sisip. Pesan semacam ini adalah tanda tahap parse bekerja — salah ketik tertangkap instan, tanpa efek samping apa pun pada data.
Tahap Plan: EXPLAIN Menampilkan Rencana #
Setelah parse lolos, planner menyusun rencana. Perintah EXPLAIN memperlihatkan rencana itu tanpa menjalankan query — seperti membaca denah sebelum membangun:
EXPLAIN
SELECT id, barang, jumlah
FROM pesanan
WHERE pelanggan = 'Budi';Output yang Dihasilkan:
QUERY PLAN
--------------------------------------------------------
Seq Scan on pesanan (cost=0.00..1.07 rows=3 width=20)
Filter: (pelanggan = 'Budi'::text)
(2 rows)Mari kita baca rencana tersebut baris per baris:
Seq Scan on pesanan: planner memilih sequential scan — membaca seluruh tabel baris demi baris. Untuk enam baris data, ini memang strategi termurah; index justru menambah loncatan untuk tabel sekecil ini.(cost=0.00..1.07 rows=3 width=20): tiga angka taksiran planner.costadalah biaya dalam satuan arbitrer (bukan milidetik) — dari posisi awal0.00sampai total1.07;rows=3adalah perkiraan jumlah baris hasil;width=20perkiraan lebar rata-rata satu baris dalam byte.Filter: (pelanggan = 'Budi'::text): setiap baris yang dibaca diuji dengan kondisiWHERE; baris Budi lolos, sisanya dibuang.
Angka-angka di atas diambil dari eksekusi nyata di lingkungan materi ini. Nilai cost dan rows bersifat taksiran — pada instalasi atau data yang berbeda angkanya bisa berbeda, tetapi bentuk rencananya sama.
Parse Sekali, Eksekusi Berkali-kali #
Tahap parse dan plan tidak gratis. Aplikasi nyata sering menjalankan query yang sama dengan parameter berbeda ribuan kali per menit — dan PostgreSQL menyediakan prepared statement supaya kerja parse dan plan itu cukup sekali:
PREPARE ambil_pesanan_pelanggan (text) AS
SELECT id, barang, jumlah
FROM pesanan
WHERE pelanggan = $1
ORDER BY id;
EXECUTE ambil_pesanan_pelanggan('Budi');
EXECUTE ambil_pesanan_pelanggan('Siti');
DEALLOCATE ambil_pesanan_pelanggan;Output yang Dihasilkan:
PREPARE
id | barang | jumlah
----+---------------+--------
1 | Buku Tulis | 2
3 | Kertas HVS A4 | 1
5 | Pulpen Hitam | 3
(3 rows)
id | barang | jumlah
----+---------------+--------
2 | Pulpen Hitam | 5
4 | Stapler Besar | 1
6 | Buku Tulis | 4
(3 rows)
DEALLOCATEBedah tiap perintahnya:
PREPARE ambil_pesanan_pelanggan (text) AS ...: query diberi nama dan tipe parameternya(text), lalu diparse dan diplan sekarang, hasilnya disimpan di session. Parameter$1adalah tempat kosong yang diisi belakangan.EXECUTE ambil_pesanan_pelanggan('Budi'): query tersimpan dijalankan dengan$1diisi'Budi'— tanpa parse ulang. Tiga baris pesanan Budi kembali.EXECUTE ambil_pesanan_pelanggan('Siti'): eksekusi kedua juga tanpa parse ulang; hanya nilai parameternya yang berubah.DEALLOCATE ambil_pesanan_pelanggan: query tersimpan dibuang dari session dan memorinya dibebaskan.
Inilah alasan driver database di aplikasi (psycopg untuk Python, pgx untuk Go, node-postgres untuk JavaScript) otomatis memakai prepared statement di belakang layar — mekanismenya sama dengan yang baru kita coba.
Sesudah Eksekusi #
Baris hasil yang dikirim ke client hanya separuh kisah. Saat query kita menulis data, backend menulis catatan ke WAL lebih dulu, dan saat COMMIT catatan itu didorong ke disk — baru transaksi dianggap resmi. Semua tahap ini berjalan di dalam backend process yang melayani koneksi kita, berdampingan dengan memori bersama dan proses-proses latar belakang server. Susunan besar itu — postmaster, backend, shared buffers, WAL, proses latar — adalah topik chapter Arsitektur PostgreSQL berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkasCREATE TABLE pesanan (
id integer PRIMARY KEY,
pelanggan text NOT NULL,
barang text NOT NULL,
jumlah integer NOT NULL,
tanggal date NOT NULL
);
INSERT INTO pesanan (id, pelanggan, barang, jumlah, tanggal) VALUES
(1, 'Budi', 'Buku Tulis', 2, DATE '2026-01-10'),
(2, 'Siti', 'Pulpen Hitam', 5, DATE '2026-01-11'),
(3, 'Budi', 'Kertas HVS A4', 1, DATE '2026-01-12'),
(4, 'Siti', 'Stapler Besar', 1, DATE '2026-01-12'),
(5, 'Budi', 'Pulpen Hitam', 3, DATE '2026-01-15'),
(6, 'Siti', 'Buku Tulis', 4, DATE '2026-01-15');
ANALYZE pesanan;
explain-query.sql
Unduh berkasEXPLAIN
SELECT id, barang, jumlah
FROM pesanan
WHERE pelanggan = 'Budi';
kesalahan-sintaks.sql
Unduh berkasSELECT * FORM pesanan;
prepared-statement.sql
Unduh berkasPREPARE ambil_pesanan_pelanggan (text) AS
SELECT id, barang, jumlah
FROM pesanan
WHERE pelanggan = $1
ORDER BY id;
EXECUTE ambil_pesanan_pelanggan('Budi');
EXECUTE ambil_pesanan_pelanggan('Siti');
DEALLOCATE ambil_pesanan_pelanggan;