Use Cases (Diperbarui: 1/6/2026)

Optimasi SQL dengan Claude Code: Panduan Praktis

Optimasi SQL dengan Claude Code: slow query, EXPLAIN, index, N+1, pagination, agregasi, pengujian, dan rollout.

Optimasi SQL dengan Claude Code: Panduan Praktis

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 caseGejalaPerbaikan utamaRisiko
Daftar adminpencarian lambat, halaman dalam makin lambatcomposite index, keyset pagination, kurangi kolomfilter opsional terlalu banyak bisa membuat index berlebihan
SaaS atau ecommerceAPI daftar melambat saat traffic naikindex sesuai WHERE dan ORDER BY, cache, agregasi asyncfilter tenant, stok, atau izin tidak boleh hilang
Dashboardgrafik harian/bulanan scan tabel besartabel ringkasan, materialized view, refresh terjadwalkebutuhan 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 include lalu 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.

#Claude Code #SQL #database #performance #optimization
Gratis

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.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.