Skip to main content
Summary

오류 메시지는 실패가 아니라 안내문입니다

PostgreSQL을 쓰다 보면 빨간 오류 메시지나 멈춘 듯한 조회 화면을 자주 만납니다. 초보자에게 가장 어려운 순간은 오류 자체보다 "이제 어디를 봐야 하지?"라는 막막함입니다.

이 장의 목표는 오류를 완벽히 해석하는 것이 아닙니다. 업무용 장부를 점검하듯이, SQL 문장과 조회 조건을 차례대로 확인하는 습관을 만드는 것입니다.

문제가 생기면 먼저 세 가지를 확인하세요.

  1. 지금 어느 데이터베이스에 접속했는가

  2. 어느 테이블을 조회하려는가

  3. SQL 문장을 한 번에 너무 많이 실행하지 않았는가

pgAdmin이나 DBeaver에서는 화면 왼쪽의 접속 정보와 데이터베이스 이름을 먼저 봅니다. 엉뚱한 장부방에 들어가서 "왜 이 장부가 없지?"라고 찾는 일이 생각보다 자주 생깁니다.

느린 조회와 흔한 오류를 해결하는 초보자 점검표AI 생성 이미지

초보자가 가장 자주 만나는 SQL 오류 점검표

SQL 오류는 대개 거창한 문제가 아닙니다. 테이블 이름 한 글자, 쉼표 하나, 따옴표 하나에서 시작됩니다.

아래 순서로 확인하면 대부분의 기본 오류를 잡을 수 있습니다.

  • 테이블 이름을 정확히 썼는가?

  • 컬럼 이름을 정확히 썼는가?

  • 문자 값에 작은따옴표 ' '를 썼는가?

  • 컬럼 이름에 불필요한 따옴표를 쓰지 않았는가?

  • 컬럼 사이에 쉼표 ,를 빠뜨리지 않았는가?

  • 괄호를 열고 닫는 짝이 맞는가?

  • SQL 문장 끝에 세미콜론 ;을 붙였는가?

  • WHERE, ORDER BY, LIMIT의 순서가 맞는가?

세미콜론은 도구에 따라 단일 문장에서는 없어도 실행될 수 있습니다. 하지만 여러 문장을 함께 실행하거나, SQL 문장의 끝을 분명히 표시할 때는 세미콜론이 큰 도움이 됩니다. 초보 단계에서는 문장 끝에 세미콜론을 붙이는 습관을 들이는 편이 안전합니다.

가장 흔한 예를 보겠습니다.

고객 테이블에서 서울 고객의 이름과 전화번호를 보려는 문장입니다.

SELECT customer_name phone
FROM customer
WHERE city = "서울"

이 문장에는 여러 문제가 섞여 있습니다.

  • customer_name과 phone 사이에 쉼표가 없습니다. PostgreSQL이 phone을 컬럼이 아니라 별칭처럼 해석할 수도 있어, 의도한 결과가 나오지 않을 수 있습니다.

  • 실제 테이블 이름이 customers라면 customer는 틀린 이름입니다.

  • 문자 값인 서울은 큰따옴표가 아니라 작은따옴표로 감싸야 합니다.

  • 문장 끝에 세미콜론이 없습니다.

올바른 문장은 다음과 같습니다.

고객 테이블에서 지역이 서울인 고객의 이름과 전화번호를 조회합니다.

SELECT customer_name, phone
FROM customers
WHERE city = '서울';

결과는 다음처럼 서울 고객만 남은 작은 표가 됩니다.

customer_name | phone
--------------+--------------
김민지        | 010-1111-2222
박준호        | 010-3333-4444

오류가 나면 SQL 전체를 새로 쓰려고 하지 말고, 먼저 이름과 따옴표부터 봅니다. 장부 이름과 항목 이름이 맞는지 확인하는 것이 첫 단계입니다.

작은따옴표와 큰따옴표를 구분하세요

PostgreSQL에서 작은따옴표와 큰따옴표는 역할이 다릅니다.

  • 작은따옴표 '서울': 실제 데이터 값

  • 큰따옴표 "customer_name": 컬럼명이나 테이블명을 특별히 감쌀 때 사용

초보자는 보통 문자 값을 큰따옴표로 감싸서 오류를 만납니다.

지역이 부산인 고객을 찾으려는 문장입니다.

SELECT customer_name, city
FROM customers
WHERE city = "부산";

PostgreSQL은 "부산"을 문자 값이 아니라 컬럼 이름처럼 이해하려고 합니다. 그래서 "부산이라는 컬럼이 없다"는 식의 오류가 날 수 있습니다.

올바른 문장은 다음과 같습니다.

지역이 부산인 고객의 이름과 지역을 조회합니다.

SELECT customer_name, city
FROM customers
WHERE city = '부산';

결과 해석은 단순합니다. city 값이 '부산'인 행만 결과표에 남습니다.

반대로 컬럼명은 보통 따옴표 없이 씁니다. 특별한 이유가 없다면 테이블명과 컬럼명은 소문자, 밑줄 방식으로 만드는 것이 좋습니다. 예를 들어 Customer Name보다 customer_name이 다루기 쉽습니다.

오류 메시지를 읽는 쉬운 순서

오류 메시지를 처음부터 끝까지 해석하려고 하면 어렵습니다. 다음 세 부분만 먼저 보세요.

  • 첫 줄: 무엇이 문제인지 요약합니다.

  • LINE 또는 Position: SQL 문장의 어느 근처에서 문제가 났는지 알려 줍니다.

  • relation, column, syntax 같은 단어: 문제 종류를 짐작하게 해 줍니다.

자주 만나는 표현은 다음과 같습니다.

오류 표현쉬운 뜻먼저 볼 곳
relation does not exist그런 테이블이 없습니다테이블명, 스키마, 접속 DB
column does not exist그런 컬럼이 없습니다컬럼명 오타, 따옴표 사용
syntax errorSQL 문법이 어색합니다쉼표, 괄호, SQL 순서
unterminated string문자 따옴표가 닫히지 않았습니다작은따옴표 짝
permission denied권한이 없습니다읽기/수정 권한

예를 들어 relation "orderss" does not exist가 보이면 복잡하게 생각하지 마세요. 먼저 orderss가 아니라 orders인지 확인하면 됩니다. 테이블명이 맞는데도 같은 오류가 난다면, 내가 접속한 데이터베이스가 맞는지, 테이블이 다른 스키마에 있는지도 이어서 확인합니다.

주문 테이블의 최근 주문 5건을 조회합니다.

SELECT order_id, order_date, total_amount
FROM orders
ORDER BY order_date DESC
LIMIT 5;

결과는 주문일이 최신인 순서로 5건만 표시됩니다. 테이블명이 틀렸다면 결과가 나오기 전에 테이블을 찾을 수 없다는 오류가 납니다.

조회가 느릴 때 먼저 확인할 것

조회가 느리다고 해서 처음부터 어려운 성능 문제로 생각할 필요는 없습니다. 많은 경우는 너무 큰 장부를 한꺼번에 펼쳐 놓았기 때문입니다.

느린 조회를 만나면 아래 순서로 확인합니다.

  1. SELECT *로 모든 컬럼을 보고 있지 않은가?

  2. WHERE 조건 없이 전체 행을 불러오고 있지 않은가?

  3. ORDER BY로 너무 많은 행을 정렬하고 있지 않은가?

  4. LIMIT 없이 화면에 많은 결과를 보내고 있지 않은가?

  5. 자주 검색하는 컬럼에 인덱스가 필요한 상황인가?

처음 테이블 구조를 확인할 때는 SELECT *가 편합니다. 하지만 주문 내역이 수십만 건이라면 이야기가 달라집니다. 모든 컬럼, 모든 행을 화면으로 가져오는 것은 큰 창고의 모든 박스를 한 번에 꺼내는 일과 같습니다.

먼저 나쁜 예를 보겠습니다.

주문 테이블의 모든 데이터를 전부 조회합니다.

SELECT *
FROM orders;

결과 해석은 간단하지만 위험합니다. orders 테이블의 모든 컬럼과 모든 행을 가져오므로 데이터가 많을수록 느려집니다.

더 나은 문장은 다음과 같습니다.

최근 결제 완료 주문 20건의 핵심 정보만 조회합니다.

SELECT order_id, customer_id, order_date, total_amount
FROM orders
WHERE status = 'paid'
ORDER BY order_date DESC
LIMIT 20;

이 문장은 세 가지를 줄였습니다.

  • 필요한 컬럼만 봅니다.

  • 결제 완료 주문만 남깁니다.

  • 최신 20건만 가져옵니다.

조회가 느리면 "데이터베이스가 느리다"라고 결론 내리기 전에, 내가 너무 많은 자료를 한꺼번에 요구하고 있는지 먼저 확인해야 합니다.

WHERE 조건은 검색칸입니다

엑셀에서 수만 줄짜리 표를 열고 필터 없이 눈으로 찾으면 오래 걸립니다. PostgreSQL도 마찬가지입니다. WHERE는 데이터베이스에 주는 검색 조건입니다.

특정 고객의 주문만 보려는 문장입니다.

SELECT order_id, order_date, total_amount, status
FROM orders
WHERE customer_id = 101
ORDER BY order_date DESC;

결과는 customer_id가 101인 주문만 주문일이 최신인 순서로 표시됩니다. 전체 주문 장부에서 한 고객의 주문만 따로 꺼내는 것입니다.

날짜 조건도 중요합니다. 기간 조건 없이 몇 년 치 주문을 모두 정렬하면 느려질 수 있습니다.

2026년 6월 주문 중 결제 완료 주문만 조회합니다.

SELECT order_id, order_date, total_amount
FROM orders
WHERE order_date >= DATE '2026-06-01'
  AND order_date < DATE '2026-07-01'
  AND status = 'paid'
ORDER BY order_date DESC;

결과는 2026년 6월에 해당하고 상태가 결제 완료인 주문만 남습니다. 날짜나 날짜·시간 컬럼에서는 6월 1일 이상, 7월 1일 미만처럼 범위를 잡으면 하루 끝 시간 때문에 빠지는 데이터를 줄일 수 있습니다.

인덱스는 책의 찾아보기입니다

인덱스는 책 뒤에 있는 찾아보기와 비슷합니다. 책에서 "환불 정책"이라는 단어를 찾을 때 첫 페이지부터 끝까지 읽지 않습니다. 찾아보기를 보고 해당 페이지로 바로 갑니다.

데이터베이스도 비슷합니다. 주문 테이블에서 customer_id = 101인 행을 자주 찾는다면, customer_id 컬럼에 인덱스가 있을 때 더 빠르게 찾을 수 있습니다.

다만 인덱스는 모든 문제를 해결하는 마법 버튼이 아닙니다.

인덱스가 도움이 되는 경우는 보통 다음과 같습니다.

  • WHERE customer_id = ...처럼 자주 검색하는 컬럼

  • WHERE order_date >= ...처럼 기간 검색에 자주 쓰는 날짜 컬럼

  • JOIN에서 자주 연결하는 컬럼

  • 값이 다양해서 특정 행을 좁혀 찾기 좋은 컬럼

반대로 이런 경우에는 효과가 작을 수 있습니다.

  • 값 종류가 적은 컬럼: 예를 들어 status가 paid, cancelled 정도뿐인 경우

  • 거의 조회하지 않는 컬럼

  • 데이터가 아주 적은 작은 테이블

  • 인덱스를 너무 많이 만들어 입력·수정 작업이 느려지는 경우

인덱스를 새로 만드는 작업은 데이터 자체를 지우지는 않지만, 운영 중인 데이터베이스에서는 영향을 줄 수 있습니다. 실무에서는 담당자와 상의하고, 가능하면 사용량이 적은 시간에 적용하는 것이 좋습니다.

예를 들어 주문 테이블에서 고객 번호로 주문을 자주 찾는 상황이라면 다음과 같은 인덱스를 생각할 수 있습니다.

고객 번호로 주문을 자주 조회하므로 orders 테이블의 customer_id 컬럼에 인덱스를 만듭니다.

CREATE INDEX idx_orders_customer_id
ON orders (customer_id);

이후 다음과 같은 조회에서 도움이 될 수 있습니다.

고객 번호가 101인 주문을 조회합니다.

SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 101
ORDER BY order_date DESC;

결과는 이전과 같은 주문 목록입니다. 인덱스는 결과 내용을 바꾸지 않습니다. 찾는 방법을 더 빠르게 만들 수 있을 뿐입니다.

EXPLAIN은 데이터베이스의 찾는 방법을 보는 도구입니다

EXPLAIN은 SQL 앞에 붙여서 PostgreSQL이 데이터를 어떻게 찾을 계획인지 보여 주는 도구입니다. 초보자가 실행 계획을 완벽히 읽을 필요는 없습니다. 처음에는 "전체를 훑는지, 인덱스를 이용하는지" 정도만 봐도 충분합니다.

고객 번호가 101인 주문을 PostgreSQL이 어떻게 찾을 계획인지 확인합니다.

EXPLAIN
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 101
ORDER BY order_date DESC;

결과는 환경마다 다르지만 대략 이런 단어가 보일 수 있습니다.

Seq Scan on orders
  Filter: (customer_id = 101)

Seq Scan은 테이블을 순서대로 훑어본다는 뜻입니다. 작은 테이블에서는 문제가 아닐 수 있지만, 큰 테이블에서 자주 반복되면 느릴 수 있습니다.

인덱스가 있고 PostgreSQL이 그 인덱스를 쓰기로 판단하면 다음과 비슷한 단어가 보일 수 있습니다.

Index Scan using idx_orders_customer_id on orders
  Index Cond: (customer_id = 101)

Index Scan은 인덱스를 이용해 찾는다는 뜻입니다. 상황에 따라 Bitmap Index Scan처럼 조금 다른 표현이 보일 수도 있지만, 처음에는 Seq Scan인지 Index라는 단어가 보이는지부터 확인하면 됩니다. 단, 인덱스가 있다고 항상 쓰는 것은 아닙니다. PostgreSQL은 데이터 양, 조건, 정렬, 예상 비용을 보고 스스로 방법을 고릅니다.

초보자는 EXPLAIN을 성적표처럼 볼 필요가 없습니다. 데이터베이스가 "어떤 방식으로 장부를 찾으려 하는지" 엿보는 도구로 이해하면 됩니다.

문제 해결용 기본 점검표

마지막으로 오류와 느린 조회를 만났을 때의 순서를 하나로 정리하겠습니다.

오류가 났을 때

  • 현재 접속한 데이터베이스가 맞는가?

  • 실행하려는 SQL 문장만 선택해서 실행했는가?

  • 테이블 이름이 맞는가?

  • 컬럼 이름이 맞는가?

  • 문자 값에 작은따옴표를 썼는가?

  • 쉼표와 괄호가 빠지지 않았는가?

  • SQL 문장 순서가 SELECT → FROM → WHERE → ORDER BY → LIMIT 흐름에 맞는가?

  • 권한이 없는 작업을 하려는 것은 아닌가?

조회가 느릴 때

  • SELECT * 대신 필요한 컬럼만 고를 수 있는가?

  • WHERE 조건으로 대상 행을 줄일 수 있는가?

  • 날짜 범위를 제한할 수 있는가?

  • LIMIT로 처음에는 일부만 볼 수 있는가?

  • 정렬이 꼭 필요한가?

  • 자주 검색하는 컬럼에 인덱스가 필요한가?

  • EXPLAIN으로 찾는 방식을 확인할 수 있는가?

PostgreSQL을 잘 쓰는 사람은 오류를 안 내는 사람이 아닙니다. 오류가 났을 때 차례대로 좁혀 가는 사람입니다. 느린 조회도 마찬가지입니다. 전체 장부를 다 펼치기 전에, 필요한 항목과 조건부터 줄이는 습관이 가장 중요합니다.

책 목록

비개발자를 위한 PostgreSQL 입문: 업무 데이터를 안전하게 다루는 가장 쉬운 방법

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기