Optimasi SQL dengan Claude Code: Panduan Praktis
Optimasi SQL dengan Claude Code: slow query, EXPLAIN, index, N+1, pagination, agregasi, pengujian, dan rollout.
Apa arti optimasi SQL sebenarnya
Optimasi SQL adalah menata query, index, dan cara mengambil data supaya hasil yang sama bisa dikembalikan dengan pembacaan lebih sedikit dan waktu tunggu lebih pendek. Ini bukan sekadar menulis SQL yang terlihat lebih pintar. Dalam praktik produksi, kamu perlu melihat endpoint aplikasi, pemanggilan ORM, execution plan, volume data yang mendekati produksi, regression test, dan monitoring setelah rollout.
Claude Code berguna karena dapat membaca konteks lengkap: route handler, model Prisma atau ORM lain, migration, log slow query, dan test. Di sistem nyata, latency biasanya bukan berasal dari satu query misterius, tetapi dari akumulasi masalah kecil: SELECT * di halaman daftar, N+1 loop, OFFSET pagination yang terlalu dalam, composite index yang tidak sesuai query, dan agregasi dashboard yang dihitung sinkron di setiap request.
Panduan ini memakai PostgreSQL sebagai contoh utama dan tetap relevan untuk MySQL di banyak bagian. Saat membaca execution plan, gunakan dokumentasi resmi: PostgreSQL Using EXPLAIN, PostgreSQL EXPLAIN, PostgreSQL Indexes, PostgreSQL Multicolumn Indexes, dan MySQL 8.4 Using EXPLAIN.
Untuk pekerjaan terkait, lihat juga desain database, migrasi database, Prisma ORM, dan optimasi performance.
Tiga use case yang sering muncul
Mulai dari layar atau workflow. Dengan begitu Claude Code tidak hanya memberi saran index yang umum.
| Use case | Gejala | Perbaikan utama | Risiko |
|---|---|---|---|
| Daftar admin | pencarian lambat, halaman dalam makin lambat | composite index, keyset pagination, kurangi kolom | filter opsional terlalu banyak bisa membuat index berlebihan |
| SaaS atau ecommerce | API daftar melambat saat traffic naik | index sesuai WHERE dan ORDER BY, cache, agregasi async | filter tenant, stok, atau izin tidak boleh hilang |
| Dashboard | grafik harian/bulanan scan tabel besar | tabel ringkasan, materialized view, refresh terjadwal | kebutuhan freshness data harus jelas |
Sebelum mengubah kode, tetapkan query budget: misalnya p95 di bawah 300 ms, maksimal lima statement SQL per request daftar, dan JSON response tetap kompatibel.
Prompt untuk Claude Code:
Optimalkan API daftar ini untuk performa SQL.
Target: p95 di bawah 300 ms, maksimal 5 statement SQL, response JSON tidak berubah.
Tinjau: src/routes/admin/orders.ts, prisma/schema.prisma, migrations, slow-query.log.
Kerjakan: identifikasi SQL lambat, baca EXPLAIN ANALYZE, usulkan index, hapus N+1, tambah regression test.
Batasan: perubahan tetap kecil dan kompatibel dengan produksi.
Langkah 1: tangkap slow query
Jangan membuat index berdasarkan tebakan. Tangkap dulu SQL yang benar-benar lambat. Di PostgreSQL, pg_stat_statements dan log database bisa menjadi titik awal. Log ORM juga berguna karena menghubungkan SQL dengan endpoint pemanggil.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT
query,
calls,
ROUND(total_exec_time::numeric, 2) AS total_ms,
ROUND(mean_exec_time::numeric, 2) AS mean_ms,
rows
FROM pg_stat_statements
WHERE query NOT ILIKE '%pg_stat_statements%'
ORDER BY total_exec_time DESC
LIMIT 10;
Dengan Prisma, kamu bisa mencatat query lambat di development menggunakan API Prisma Client yang nyata:
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient({
log: [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "error" },
{ emit: "stdout", level: "warn" },
],
});
prisma.$on("query", (event) => {
if (event.duration > 100) {
console.log({
durationMs: event.duration,
query: event.query,
params: event.params,
});
}
});
Berikan log yang sudah disanitasi kepada Claude Code. Masking email, token, dan ID pelanggan. Minta ia mengelompokkan pola SQL berdasarkan layar, pemanggil, waktu rata-rata, jumlah panggilan, dan dugaan penyebab.
Langkah 2: baca EXPLAIN dan EXPLAIN ANALYZE
Setelah ada query kandidat, gunakan EXPLAIN untuk melihat rencana eksekusi. Di lingkungan aman dengan data representatif, gunakan EXPLAIN (ANALYZE, BUFFERS). Ingat bahwa ANALYZE benar-benar menjalankan query, jadi hati-hati untuk UPDATE atau DELETE.
EXPLAIN (ANALYZE, BUFFERS)
SELECT
o.id,
o.created_at,
o.status,
c.name AS customer_name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid'
AND o.created_at >= DATE '2026-01-01'
ORDER BY o.created_at DESC, o.id DESC
LIMIT 50;
Jangan menganggap semua Seq Scan sebagai bug. Untuk tabel kecil atau filter yang tidak selektif, sequential scan bisa tepat. Perhatikan selisih estimasi dan jumlah baris aktual, sort besar, buffer read tinggi, dan urutan join yang tidak wajar.
Prompt untuk Claude Code:
Review EXPLAIN ANALYZE ini. Pisahkan jawaban menjadi bottleneck, mismatch estimasi baris, kandidat index, dan rewrite SQL yang aman. Jangan mengubah result set.
Langkah 3: desain composite index
Index mempercepat read, tetapi menambah biaya write, storage, dan risiko migration. Desain dari query nyata: equality predicate, range predicate, kolom sort, lalu kebutuhan join.
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_status_created_id
ON orders (status, created_at DESC, id DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_customer_created
ON orders (customer_id, created_at DESC);
Di PostgreSQL, CREATE INDEX CONCURRENTLY mengurangi blocking write, tetapi tidak bisa berjalan di transaction block normal. Tulis di PR bagaimana migration tool akan menjalankannya.
Sebelum meminta saran index, berikan index yang sudah ada:
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'orders'
ORDER BY indexname;
Langkah 4: hapus N+1
N+1 terjadi ketika daftar diambil sekali, lalu data relasi diambil satu per satu untuk setiap baris. Ini menambah kerja database dan waktu tunggu jaringan.
// Buruk: satu query customer untuk setiap order
const orders = await prisma.order.findMany({
take: 100,
orderBy: { createdAt: "desc" },
});
const rows = [];
for (const order of orders) {
const customer = await prisma.customer.findUnique({
where: { id: order.customerId },
});
rows.push({ ...order, customerName: customer?.name ?? "" });
}
// Lebih baik: ambil hanya kolom yang dibutuhkan UI
const orders = await prisma.order.findMany({
take: 100,
orderBy: { createdAt: "desc" },
select: {
id: true,
createdAt: true,
status: true,
totalAmount: true,
customer: {
select: {
id: true,
name: true,
},
},
},
});
Minta Claude Code mempertahankan bentuk response sambil mengurangi jumlah query. Dengan begitu ia perlu membaca UI atau serializer, bukan sekadar menambahkan include yang terlalu luas.
Langkah 5: ganti OFFSET dalam dengan keyset pagination
OFFSET 10000 LIMIT 50 biasanya makin lambat karena database tetap melewati baris yang dilewati. Untuk timeline, daftar order, dan infinite scroll, gunakan created_at dan id terakhir sebagai cursor.
SELECT id, created_at, status, total_amount
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC, id DESC
LIMIT 50;
SELECT id, created_at, status, total_amount
FROM orders
WHERE status = 'paid'
AND (created_at, id) < (TIMESTAMP '2026-05-01 10:30:00', 98765)
ORDER BY created_at DESC, id DESC
LIMIT 50;
Index (status, created_at DESC, id DESC) cocok untuk pola ini. id menjadi tie-breaker agar urutan stabil saat banyak baris memiliki timestamp yang sama.
Langkah 6: pisahkan agregasi dari request panas
Dashboard sering lambat karena setiap request menghitung ulang total yang sama. Jika bisnis menerima sedikit delay, gunakan tabel ringkasan atau refresh terjadwal.
CREATE TABLE IF NOT EXISTS daily_sales_summary (
sales_date date PRIMARY KEY,
order_count integer NOT NULL,
revenue numeric(12, 2) NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO daily_sales_summary (sales_date, order_count, revenue)
SELECT
created_at::date AS sales_date,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY created_at::date
ON CONFLICT (sales_date) DO UPDATE
SET
order_count = EXCLUDED.order_count,
revenue = EXCLUDED.revenue,
updated_at = now();
Beri tahu Claude Code seberapa fresh data harus tersedia: satu menit, lima menit, harian, atau real-time ketat. Tanpa batasan ini, optimasi bisa mengubah makna dashboard.
Query budget dan regression test
Performa bisa mundur di perubahan berikutnya. Snapshot execution plan yang persis sering rapuh; mulai dari jumlah query, kompatibilitas response, dan beberapa kasus representatif.
import { PrismaClient } from "@prisma/client";
import { expect, it } from "vitest";
it("keeps the order list within the query budget", async () => {
const queries: string[] = [];
const prisma = new PrismaClient({
log: [{ emit: "event", level: "query" }],
});
prisma.$on("query", (event) => {
queries.push(event.query);
});
await prisma.order.findMany({
take: 50,
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
select: {
id: true,
createdAt: true,
status: true,
customer: { select: { id: true, name: true } },
},
});
await prisma.$disconnect();
expect(queries.length).toBeLessThanOrEqual(2);
});
Peta alur
flowchart LR
A["Log slow query"] --> B["EXPLAIN / EXPLAIN ANALYZE"]
B --> C["Perubahan index dan SQL"]
C --> D["N+1, pagination, agregasi"]
D --> E["Test query budget"]
E --> F["Rollout dan monitoring"]
Jebakan umum
- Membaca kolom besar dengan
SELECT *di halaman daftar. - Mengharapkan terlalu banyak dari index tunggal pada kolom low-cardinality seperti
status. - Mencoba mempercepat
LIKE '%keyword%'dengan B-tree index biasa. - Menilai execution plan dari data development yang kecil.
- Menjalankan index migration di jam sibuk tanpa memeriksa lock, disk, dan replica lag.
- Menggunakan ORM
includelalu mengembalikan seluruh graph relasi.
Checklist rollout
1. Endpoint dan layar pengguna sudah diidentifikasi.
2. SQL lambat ditangkap dari logs atau pg_stat_statements.
3. EXPLAIN sebelum/sesudah disimpan.
4. Index yang sudah ada sudah diperiksa.
5. N+1, OFFSET, kolom terpilih, dan agregasi sinkron sudah direview.
6. Kompatibilitas response sudah diuji.
7. Test jumlah query atau latency sudah ditambahkan.
8. Risiko lock, durasi, dan rollback perubahan index sudah didokumentasikan.
9. Metrik setelah release sudah ditentukan.
Optimasi SQL adalah workflow, bukan trik satu baris. Claude Code dapat menghubungkan kode, migration, dan test, tetapi keputusan akhir tetap harus berdasarkan data nyata, execution plan, dan kebutuhan produk.
ClaudeCodeLab menyediakan training dan konsultasi berdasarkan review praktis seperti ini. Bawa endpoint lambat, log, dan schema, lalu ubah investigasi sekali jalan menjadi checklist optimasi SQL yang bisa dipakai ulang oleh tim.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.