LLM이 만든 SQL을 그대로 실행하면 안 되는 이유 — 검증 항목 여섯 가지
자연어로 SQL을 얻는 것은 편하지만 검증 없이 실행하면 위험하다. 실행 전 확인 목록.
편의와 위험이 같은 곳에서 온다
자연어로 원하는 것을 적으면 SQL이 나오는 흐름은 확실히 빠르다. 스키마를 다 외우지 않아도 되고, 복잡한 조인도 초안이 나온다.
문제는 결과가 그럴듯해 보인다는 점이다. 문법 오류로 실패하면 오히려 안전한데, 실행은 되고 결과가 틀린 경우가 위험하다.
검증 1 — WHERE 절 확인 (가장 중요)
```sql
UPDATE orders SET status = 'cancelled'; -- WHERE 없음
DELETE FROM users; -- 전체 삭제
```
파괴적 구문에 WHERE가 없으면 전체 행이 대상이다. 실행 전에 UPDATE·DELETE는 반드시 WHERE를 눈으로 확인한다.
안전 습관: 먼저 SELECT로 바꿔 대상 건수를 확인한다.
```sql
SELECT COUNT(*) FROM orders WHERE ...; -- 예상 건수인가?
```
검증 2 — 조인 조건 누락
조인 조건이 빠지면 카티션 곱이 된다.
```sql
-- 조건 없음: 100 × 1000 = 100,000행
SELECT * FROM orders, users;
```
결과가 이상하게 많으면 이것을 의심한다. LLM이 생성한 쿼리에서 테이블이 세 개 이상 얽히면 발생 가능성이 올라간다.
검증 3 — 집계와 GROUP BY
```sql
SELECT user_id, COUNT(*), name FROM orders GROUP BY user_id;
```
DB에 따라 name처럼 집계되지 않은 컬럼을 허용하는 경우가 있는데, 그때 나오는 값은 임의의 행에서 온 것이다. 의도한 결과가 아닐 수 있다.
검증 4 — NULL 처리
```sql
WHERE status != 'done'
```
status가 NULL인 행은 이 조건에 걸리지 않는다. NULL 비교는 참도 거짓도 아니기 때문이다. 의도가 "완료가 아닌 것 전부"라면:
```sql
WHERE status IS DISTINCT FROM 'done' -- 또는
WHERE status != 'done' OR status IS NULL
```
자연어 요청("완료 안 된 것")과 SQL의 NULL 의미가 어긋나는 대표적 지점이다.
검증 5 — 비즈니스 규칙
스키마에는 없고 코드나 관행에만 있는 규칙이 있다.
- 소프트 삭제:
deleted_at IS NULL조건이 필요한데 빠짐 - 테넌트 분리:
tenant_id조건 누락 → 다른 고객 데이터가 섞임 - 상태 코드의 실제 의미:
status = 1이 무엇인지
두 번째가 특히 위험하다. 멀티테넌트 환경에서 tenant 조건이 빠지면 데이터 유출이 된다. LLM은 스키마만 보고는 이 규칙을 알 수 없다.
검증 6 — 실행 계획
큰 테이블을 다루는 쿼리는 실행 전에 계획을 본다.
```sql
EXPLAIN SELECT ...;
```
풀스캔이 여러 개 겹치거나 예상 행 수가 비정상적으로 크면 운영 DB에 부하를 준다.
안전 장치
1. 읽기 전용 연결 사용
탐색 단계에서는 SELECT만 가능한 계정을 쓴다. 파괴적 실수 자체가 불가능해진다.
2. 트랜잭션으로 감싸기
```sql
BEGIN;
UPDATE ...;
SELECT ...; -- 결과 확인
-- 문제 있으면 ROLLBACK, 괜찮으면 COMMIT
```
3. LIMIT 먼저
결과 크기를 모르는 조회는 LIMIT 100으로 먼저 확인한다.
4. 운영 DB 직접 접근 최소화
가능하면 복제본이나 스테이징에서 검증한 뒤 운영에 적용한다.
잘 쓰는 방법
LLM에게 SQL을 요청할 때 정보를 더 주면 정확도가 올라간다.
- 스키마 정의(DDL)를 함께 제공
- 소프트 삭제·테넌트 등 관행 명시
- 원하는 결과의 예시 한 줄
그래도 위 검증은 남는다. 생성 도구는 초안을 빠르게 만드는 데 쓰고, 판단은 사람이 유지하는 구조가 안전하다.
최종 수정 2026-08-28