☰ 분류

느린 쿼리 원인 좁히는 프롬프트

실행계획을 읽고 어디서 시간이 나가는지 짚습니다. 인덱스부터 추가하지 않습니다.

분류개발 › 데이터·DB
태그분석코드개발자
프롬프트 (영어 본문 · 답은 한국어로 옵니다)
Diagnose this slow query.

Work from the execution plan, in this order:
1. Find the node where actual time is concentrated. Quote it. *Start from measured cost, not from what looks suspicious in the SQL.*
2. Compare estimated rows to actual rows at each node. A large gap means the planner is working from bad statistics, and no index will fix that.
3. Classify the bottleneck: scan that should be a seek / wrong join order / join method mismatch / sort or hash spilling to disk / row count genuinely large.
4. Say where the fix belongs — **index, query rewrite, schema, or statistics** — and why not the others.
5. Give the change, with what to measure after.

Also check:
- Functions or casts applied to an indexed column in a predicate, which silently disable the index
- Leading-column mismatch against a composite index
- `SELECT *` pulling columns that force a lookup instead of an index-only read
- `OR` conditions that prevent index use
- Correlated subqueries running per row

Rules:
- *Do not recommend an index before step 4.* Most proposed indexes address a node that was not the bottleneck.
- If the plan does not explain the time, say so and name what to capture next.
- Note if the query is fast and the problem is that it runs too often — that is a different fix entirely.
붙여 넣으면 맨 아래에 채울 칸(쿼리 · 실행계획 · 테이블·인덱스 정보)이 나옵니다

어떤 프롬프트인가

느리면 일단 인덱스를 추가하는데, 그게 문제가 아닌 경우가 많다. 이 프롬프트는 실행계획에서 실제 비용이 큰 노드를 먼저 찾고, 인덱스·쿼리 재작성·스키마 중 어디를 고쳐야 하는지 갈라준다.

모델 비교

ChatGPT가 가장 정확하고 절제된 진단이다. Gemini도 방향은 맞지만 근거 없는 성능 예측과 통계 단정이 있으며, [C]는 답변이 없다.

ChatGPT종합 1위
48/ 50

+ 계획 수치에서 출발해 병목과 인덱스 근거를 정확히 연결했다.

- 정렬 노드 시간 표시는 실제 정렬 소요 시간으로 오해될 수 있다.

Gemini
43/ 50

+ 요구된 진단 순서와 추가 점검 항목을 빠짐없이 다뤘다.

- 통계 최신성, 1~2ms 성능, 버퍼 수를 근거 없이 단정했다.

기준ChatGPTGemini앞선 모델
지시 준수1010동점
정확성97ChatGPT +29%
구체성109ChatGPT +11%
구성·가독성109ChatGPT +11%
분량 적정성98ChatGPT +13%

gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-25). 측정값이 아니라 AI 심사 결과입니다.

답 전문 보기

같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.

넣은 입력
쿼리: SELECT o.order_id, o.created_at, o.status, c.name, c.email
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
  AND o.created_at >= '2026-08-01'
  AND o.created_at < '2026-09-01'
ORDER BY o.created_at DESC
LIMIT 50;

실행계획: Limit  (cost=15234.12..15234.24 rows=50 width=96) (actual time=842.311..842.355 rows=50 loops=1)
  ->  Sort  (cost=15234.12..15298.45 rows=25732 width=96) (actual time=842.309..842.331 rows=50 loops=1)
        Sort Key: o.created_at DESC
        Sort Method: top-N heapsort  Memory: 32kB
        ->  Hash Join  (cost=1123.00..14532.88 rows=25732 width=96) (actual time=12.442..818.204 rows=26890 loops=1)
              Hash Cond: (o.customer_id = c.customer_id)
              ->  Seq Scan on orders o  (cost=0.00..13102.00 rows=25732 width=24) (actual time=0.015..782.601 rows=26890 loops=1)
                    Filter: ((status = 'PAID'::text) AND (created_at >= '2026-08-01 00:00:00'::timestamp) AND (created_at < '2026-09-01 00:00:00'::timestamp))
                    Rows Removed by Filter: 812340
              ->  Hash  (cost=850.00..850.00 rows=20000 width=80) (actual time=12.201..12.202 rows=20000 loops=1)
                    ->  Seq Scan on customers c  (cost=0.00..850.00 rows=20000 width=80) (actual time=0.005..5.332 rows=20000 loops=1)
Planning Time: 0.512 ms
Execution Time: 842.601 ms

테이블·인덱스 정보: orders (약 84만 행, 최근 6개월 데이터)
  order_id       bigint PK
  customer_id    bigint  -- FK customers.customer_id
  status         text    -- 'PAID','CANCELLED','PENDING','REFUNDED'
  created_at     timestamp
  total_amount   numeric

인덱스:
  orders_pkey (order_id)
  idx_orders_customer_id (customer_id)

customers (약 2만 행)
  customer_id  bigint PK
  name         text
  email        text

인덱스:
  customers_pkey (customer_id)
gpt-5.6-luna (codex CLI · 에이전트 하네스) · 실행일 2026-09-24
  1. 시간 집중 노드

가장 오래 걸린 노드는 orders 전체 순차 스캔입니다.

Seq Scan on orders o ... (actual time=0.015..782.601 ms rows=26890)
Rows Removed by Filter: 812340

전체 약 84만 행을 읽어 26,890건만 남겼습니다. 이후 Hash Join까지 누적 818ms가 걸렸지만, 실질적인 원인은 이 순차 스캔입니다.

Sort는 병목이 아닙니다.

Sort Method: top-N heapsort Memory: 32kB
actual time=842.309..842.331

정렬은 32KB 메모리에서 수행됐고 디스크 spill도 없습니다.

  1. 예상 행과 실제 행 비교
노드 예상 행 실제 행 판단
orders 필터 25,732 26,890 약 4.5% 차이, 통계는 양호
customers 스캔 20,000 20,000 정확
Hash Join 25,732 26,890 약 4.5% 차이
Sort 입력 25,732 26,890 약 4.5% 차이
Limit 50 50 정확

큰 추정 오류가 없습니다. 따라서 이 문제는 잘못된 통계 때문에 생긴 것이 아니며, 통계 갱신만으로 해결되지 않습니다.

  1. 병목 분류

orders에 대해 seek할 수 있는데 전체를 scan하고 있는 문제입니다.

  • orders 84만 행을 순차 읽음
  • 필터 통과 행은 26,890건
  • 고객 테이블의 2만 행 Hash는 약 12ms뿐
  • top-N 정렬은 메모리 내에서 수행
  • 함수, 캐스팅, OR, 상관 서브쿼리는 없음
  • 기존 idx_orders_customer_id(customer_id)는 필터의 선두 조건과 맞지 않아 이 조회에 유용하지 않음

SELECT *는 아니며 필요한 컬럼만 선택하고 있습니다. 다만 새 인덱스가 order_id, customer_id까지 포함하면 orders 쪽 추가 테이블 조회를 줄일 수 있습니다. customers의 name, email은 고객 PK로 최대 50건만 lookup하면 됩니다.

  1. 수정 위치와 변경안

수정 위치는 인덱스입니다.

CREATE INDEX idx_orders_status_created_at
ON orders (status, created_at DESC)
INCLUDE (order_id, customer_id);

이 인덱스는 다음을 지원합니다.

  • status = 'PAID'로 선두 범위 탐색
  • created_at 날짜 범위 조건
  • ORDER BY created_at DESC
  • order_id, customer_id를 인덱스에서 제공

그러면 전체 orders scan 대신 조건에 맞는 최신 주문부터 읽고, LIMIT 50에서 조기 종료할 가능성이 큽니다. 고객 정보는 customers_pkey를 이용해 최대 50건만 조회하면 됩니다.

통계가 주원인이 아닌 이유는 예상 행과 실제 행이 이미 가깝기 때문입니다. 쿼리 rewrite도 필요하지 않고, 스키마 변경도 과합니다. 현재 문제는 데이터가 26,890건이라서가 아니라 84만 건을 먼저 읽는 데 있습니다.

  1. 변경 후 측정

인덱스 생성 후 다음을 비교하십시오.

EXPLAIN (ANALYZE, BUFFERS)
SELECT ...

확인할 항목은 다음입니다.

  • Seq Scan on orders가 Index Scan 또는 Index Only Scan으로 바뀌었는지
  • Rows Removed by Filter가 크게 줄었는지
  • orders의 shared read 블록 수
  • 전체 실행 시간이 약 842ms에서 크게 감소했는지
  • 실제로 LIMIT 50 근처에서 읽기를 멈추는지

이 계획은 한 번 실행하는 것 자체가 느린 경우를 명확히 설명합니다. 반복 실행 빈도가 문제라면 별도로 호출 빈도, 캐시, 애플리케이션 batching을 조사해야 하지만, 제공된 계획만 보면 우선 원인은 실행당 과도한 순차 스캔입니다.

같은 분류의 프롬프트

대용량 텍스트 변환기 프롬프트
AI 데이터 추출 도구 기획 프롬프트
AI2sql 자연어 SQL 생성 프롬프트
백엔드 아키텍트 역할을 맡기는 프롬프트
Base R 프로그래밍 프롬프트