PostgreSQL 2201W 오류 원인과 해결 방법 완벽 가이드

2201W
2026년 10월 17일 | DBMS Error 가이드

이 글에서 다루는 내용

2201W 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

2201W invalid row count in limit clause 는?

PostgreSQL 에러 코드 2201W는 LIMIT 절에 유효하지 않은 행 수(row count)가 지정되었을 때 발생하는 에러입니다. 이 에러는 SQL 표준(SQLSTATE)에서 정의된 에러로, LIMIT 또는 FETCH FIRST 절에 음수(negative number)나 허용되지 않는 값이 입력될 때 PostgreSQL이 해당 쿼리 실행을 거부하면서 발생합니다. 주로 애플리케이션에서 동적으로 페이징 파라미터를 생성하거나, 사용자 입력값을 검증 없이 쿼리에 직접 바인딩할 때 자주 마주치게 됩니다.


주요 발생 원인

1. LIMIT 절에 음수 값 입력

가장 흔한 원인으로, LIMIT 절에 음수 값이 전달되는 경우입니다. 애플리케이션 레이어에서 페이지당 항목 수를 계산하는 로직에 버그가 있거나, 사용자가 의도적 혹은 실수로 음수 값을 입력했을 때 발생합니다. PostgreSQL은 LIMIT -1처럼 일부 드라이버나 데이터베이스에서는 “제한 없음”을 의미하는 표현을 허용하지 않으며, 오직 0 이상의 정수 또는 ALL 키워드만 유효한 값으로 인정합니다.

2. 변수 또는 파라미터 바인딩 오류

ORM(Object-Relational Mapping) 프레임워크나 직접 작성한 동적 SQL에서 페이징 파라미터가 초기화되지 않거나 잘못된 타입으로 변환되어 LIMIT 절에 전달되는 경우입니다. 예를 들어 Python의 None, Java의 null, 또는 잘못된 형변환으로 인해 음수가 바인딩되면 이 에러가 발생합니다. 특히 REST API의 쿼리스트링 파라미터를 정수로 파싱하는 과정에서 유효성 검사가 누락되면 이 문제가 빈번하게 발생합니다.

3. FETCH FIRST 구문에서의 잘못된 값 사용

SQL 표준 구문인 FETCH FIRST n ROWS ONLY 또는 FETCH NEXT n ROWS ONLY 절에서도 동일한 에러가 발생할 수 있습니다. LIMIT의 대안으로 사용하는 표준 SQL 페이징 구문에서 n에 해당하는 값이 음수이거나 유효하지 않을 때 2201W 에러가 트리거됩니다. 이 구문은 JDBC나 표준 SQL을 지향하는 엔터프라이즈 환경에서 자주 사용되므로, 해당 환경에서 특히 주의가 필요합니다.


해결 방법

원인 1 해결: 음수 값 방어 처리

LIMIT 절에 값을 전달하기 전, 반드시 0 이상의 값인지 확인하는 로직을 추가해야 합니다.

-- 잘못된 사용 예 (에러 발생)
SELECT id, name, created_at
FROM users
LIMIT -10;
-- ERROR: ERROR:  LIMIT must not be negative
-- SQLSTATE: 2201W

-- 올바른 사용 예 1: LIMIT에는 0 이상의 정수만 허용
SELECT id, name, created_at
FROM users
LIMIT 10;

-- 올바른 사용 예 2: 제한 없이 모든 행을 가져오려면 ALL 사용
SELECT id, name, created_at
FROM users
LIMIT ALL;

-- 올바른 사용 예 3: 동적 쿼리에서 음수 방어 (GREATEST 활용)
-- 파라미터로 받은 값이 음수일 경우 0으로 치환
SELECT id, name, created_at
FROM users
LIMIT GREATEST(0, :user_supplied_limit);

원인 2 해결: 파라미터 바인딩 전 유효성 검사

애플리케이션 코드 또는 PostgreSQL 함수 내에서 파라미터 유효성 검사를 수행하는 방법입니다.

-- PL/pgSQL 함수에서 LIMIT 파라미터 유효성 검사 예제
CREATE OR REPLACE FUNCTION get_users_paged(
    p_limit  INT DEFAULT 20,
    p_offset INT DEFAULT 0
)
RETURNS TABLE(id INT, name TEXT, created_at TIMESTAMPTZ)
LANGUAGE plpgsql
AS $$
DECLARE
    v_limit  INT;
    v_offset INT;
BEGIN
    -- 음수 방어: 0 미만이면 기본값 또는 0으로 설정
    v_limit  := GREATEST(0, COALESCE(p_limit, 20));
    v_offset := GREATEST(0, COALESCE(p_offset, 0));

    -- 최대 LIMIT 제한 (서비스 보호 목적)
    IF v_limit > 1000 THEN
        v_limit := 1000;
    END IF;

    RETURN QUERY
    SELECT u.id, u.name, u.created_at
    FROM users u
    ORDER BY u.created_at DESC
    LIMIT v_limit
    OFFSET v_offset;
END;
$$;

-- 안전하게 함수 호출
SELECT * FROM get_users_paged(p_limit => 50, p_offset => 0);

-- 음수가 입력되어도 안전하게 처리됨
SELECT * FROM get_users_paged(p_limit => -5, p_offset => -10);

원인 3 해결: FETCH FIRST 구문 올바른 사용

-- 잘못된 사용 예 (에러 발생)
SELECT id, name
FROM orders
ORDER BY created_at DESC
FETCH FIRST -5 ROWS ONLY;
-- ERROR: SQLSTATE 2201W

-- 올바른 사용 예 1: FETCH FIRST 표준 구문
SELECT id, name
FROM orders
ORDER BY created_at DESC
FETCH FIRST 10 ROWS ONLY;

-- 올바른 사용 예 2: FETCH NEXT 구문 (FETCH FIRST와 동일)
SELECT id, name
FROM orders
ORDER BY created_at DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;

-- 올바른 사용 예 3: 동적 값 적용 시 WITH TIES 옵션
SELECT id, name, score
FROM leaderboard
ORDER BY score DESC
FETCH FIRST 10 ROWS WITH TIES;

예방 방법

1. 애플리케이션 레이어에서 입력값 유효성 검사 강제화

페이징 관련 파라미터(page, size, limit, offset 등)는 반드시 애플리케이션 서버 측에서 1차 유효성 검사를 수행해야 합니다. 값의 범위(0 이상, 최대값 이하)를 강제하는 유효성 검사 미들웨어 또는 DTO(Data Transfer Object) 어노테이션을 활용하고, 데이터베이스에 도달하기 전에 잘못된 값을 차단하는 것이 핵심입니다. 예를 들어 Java/Spring 환경에서는 @Min(0), @Max(1000) 어노테이션을, Python/Pydantic 환경에서는 ge=0, le=1000 제약 조건을 적극 활용하세요.

2. 데이터베이스 레이어에서 안전한 래퍼 함수 사용

직접 동적 SQL을 구성하는 대신, 위에서 소개한 것처럼 유효성 검사 로직이 내장된 PostgreSQL 함수를 래퍼(wrapper)로 두고 애플리케이션이 항상 해당 함수를 통해서만 데이터를 조회하도록 아키텍처를 설계하세요. GREATEST(0, ...) 와 COALESCE(..., 기본값) 조합은 PostgreSQL 내에서 가장 간단하고 효과적인 방어 패턴입니다. 또한 pg_stat_statements 확장을 활성화하여 비정상적인 LIMIT 값이 포함된 쿼리 패턴을 모니터링하고 조기에 감지하는 체계를 구축하는 것도 강력히 권장합니다.


관련 에러

  • 2201X (invalid row count in result offset clause): OFFSET 절에 음수 값이 입력되었을 때 발생하는 에러로, 2201W와 매우 유사한 맥락에서 함께 발생하는 경우가 많습니다. 페이징 처리 시 LIMIT와 OFFSET을 동시에 동적으로 설정하는 경우 두 에러를 함께 방어해야 합니다.
  • 22003 (numeric value out of range): LIMIT 절에 PostgreSQL이 허용하는 정수 범위를 초과하는 매우 큰 값을 입력했을 때 발생할 수 있습니다. 실무에서는 사용자 입력 기반의 페이징에서 LIMIT 최대값을 서비스 정책에 맞게 제한하여 이 에러도 함께 예방하는 것이 좋습니다.
  • 42601 (syntax error): LIMIT 절 자체의 문법이 잘못된 경우로, 동적 SQL 생성 시 파라미터 타입이 맞지 않거나 문자열이 그대로 삽입되는 경우 함께 발생할 수 있습니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기