2026년 10월 11일 | DBMS Error 가이드
이 글에서 다루는 내용
22000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22000 data exception 는?
PostgreSQL 에러 코드 22000은 데이터 예외(data exception) 를 나타내는 최상위 에러 클래스입니다. 이 에러는 SQL 연산 중 데이터 값이 기대하는 형식, 범위, 또는 제약 조건과 맞지 않을 때 발생하며, 보다 구체적인 하위 에러 코드(예: 22001, 22003, 22P02 등)의 부모 클래스 역할을 합니다. 실무에서는 데이터 타입 변환 실패, 숫자 범위 초과, 문자열 길이 초과 등 다양한 상황에서 이 에러 클래스가 트리거되며, 정확한 원인 파악을 위해 반드시 하위 에러 코드와 메시지를 함께 확인해야 합니다.
주요 발생 원인
1. 데이터 타입 변환 실패 (Invalid Cast / Type Mismatch)
가장 빈번하게 발생하는 원인으로, 문자열을 숫자나 날짜로 변환할 때 값의 형식이 맞지 않으면 에러가 발생합니다. 예를 들어 'abc'를 INTEGER로 캐스팅하거나, '2024-13-45'처럼 유효하지 않은 날짜 문자열을 DATE 타입으로 변환하려 할 때 이 에러가 발생합니다. 외부 시스템(CSV, API 등)에서 받아온 원시 데이터를 그대로 INSERT하거나 CAST할 때 특히 자주 나타납니다.
2. 숫자 범위 초과 (Numeric Value Out of Range)
SMALLINT, INTEGER, BIGINT, NUMERIC(p, s) 등의 숫자 타입에서 해당 타입이 허용하는 범위를 초과하는 값을 저장하려 할 때 발생합니다. 예를 들어 SMALLINT의 최대값인 32,767을 초과하는 값을 삽입하거나, NUMERIC(5, 2)로 선언된 컬럼에 12345.67처럼 전체 자릿수를 초과하는 값을 넣으려 할 때 발생합니다. 컬럼 설계 시 예상 최대값을 충분히 고려하지 않으면 운영 중에 이 에러가 갑자기 터질 수 있습니다.
3. 문자열 길이 초과 (String Data Right Truncation)
VARCHAR(n) 또는 CHAR(n) 타입의 컬럼에 허용된 길이보다 긴 문자열을 삽입하거나 업데이트하려 할 때 발생합니다. 특히 다국어 환경(UTF-8)에서는 한글, 일본어, 이모지 등의 멀티바이트 문자가 바이트 수와 문자 수가 달라 예상치 못한 에러를 유발하기도 합니다. 초기 설계에서 VARCHAR 길이를 너무 짧게 잡거나, 요구사항 변경으로 더 긴 데이터가 들어오게 되었을 때 이 에러가 발생합니다.
해결 방법
원인 1: 데이터 타입 변환 실패 해결
변환 전에 값의 유효성을 검증하거나, TRY-CAST 패턴을 활용해 안전하게 처리합니다.
-- 문제 상황: 유효하지 않은 문자열을 INTEGER로 변환 시도
SELECT CAST('abc' AS INTEGER); -- ERROR: 22P02
-- 해결 방법 1: CASE + REGEXP를 이용한 사전 검증
SELECT
CASE
WHEN col ~ '^\d+$' THEN col::INTEGER
ELSE NULL
END AS safe_integer
FROM raw_data;
-- 해결 방법 2: PostgreSQL 함수로 안전한 캐스팅 구현
CREATE OR REPLACE FUNCTION safe_cast_to_int(p_val TEXT)
RETURNS INTEGER AS $$
BEGIN
RETURN p_val::INTEGER;
EXCEPTION
WHEN invalid_text_representation OR data_exception THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT safe_cast_to_int('123'); -- 123
SELECT safe_cast_to_int('abc'); -- NULL (에러 없이 처리)
SELECT safe_cast_to_int('2024'); -- 2024
-- 날짜 변환 안전 처리
SELECT
CASE
WHEN col ~ '^\d{4}-\d{2}-\d{2}$' THEN col::DATE
ELSE NULL
END AS safe_date
FROM raw_data;
원인 2: 숫자 범위 초과 해결
컬럼 타입을 더 큰 범위로 변경하거나, 삽입 전에 범위 검증을 수행합니다.
-- 문제 상황: SMALLINT 범위 초과
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
quantity SMALLINT -- 최대 32,767
);
INSERT INTO orders (quantity) VALUES (100000); -- ERROR: 22003
-- 해결 방법 1: 컬럼 타입을 더 큰 타입으로 변경
ALTER TABLE orders ALTER COLUMN quantity TYPE INTEGER;
-- 해결 방법 2: NUMERIC 타입의 정밀도/스케일 초과 처리
-- 기존: NUMERIC(5, 2) -> 최대 999.99
ALTER TABLE products ALTER COLUMN price TYPE NUMERIC(10, 2);
-- 해결 방법 3: 삽입 전 범위 검증
INSERT INTO orders (quantity)
SELECT v
FROM (VALUES (100000)) AS t(v)
WHERE v BETWEEN -32768 AND 32767; -- SMALLINT 범위 내인 경우만 삽입
-- 범위 초과 데이터 사전 탐지 쿼리
SELECT *
FROM raw_import
WHERE quantity > 32767 OR quantity < -32768;
원인 3: 문자열 길이 초과 해결
컬럼 크기를 늘리거나, 데이터를 삽입 전에 잘라냅니다.
-- 문제 상황: VARCHAR(10) 컬럼에 11자 이상 삽입
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(10)
);
INSERT INTO users (username) VALUES ('verylongusername'); -- ERROR: 22001
-- 해결 방법 1: 컬럼 크기 확장 (다운타임 없이 가능)
ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(50);
-- 해결 방법 2: SUBSTRING으로 데이터 잘라서 삽입
INSERT INTO users (username)
VALUES (SUBSTRING('verylongusername', 1, 10));
-- 해결 방법 3: 현재 컬럼 길이 초과 데이터 일괄 탐지
SELECT
column_name,
character_maximum_length,
COUNT(*) AS violation_count
FROM information_schema.columns c
JOIN users u ON LENGTH(u.username) > c.character_maximum_length
WHERE c.table_name = 'users'
AND c.column_name = 'username'
GROUP BY column_name, character_maximum_length;
-- UTF-8 멀티바이트 고려: 바이트 길이 vs 문자 길이 확인
SELECT
username,
LENGTH(username) AS char_length, -- 문자 수
OCTET_LENGTH(username) AS byte_length -- 바이트 수
FROM users
WHERE OCTET_LENGTH(username) > 10;
예방 방법
1. 데이터 입력 계층에서의 유효성 검사 (Input Validation Layer)
데이터베이스에 도달하기 전, 애플리케이션 레이어 또는 ETL 파이프라인에서 반드시 데이터 유효성 검사를 수행하세요. 아래와 같이 PostgreSQL의 CHECK 제약 조건과 도메인(DOMAIN)을 활용하면 DB 레벨에서도 이중으로 방어할 수 있습니다.
-- CHECK 제약으로 범위 및 형식 검증
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
amount NUMERIC(12, 2) CHECK (amount > 0),
status VARCHAR(20) CHECK (status IN ('pending', 'completed', 'failed')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- DOMAIN을 이용한 재사용 가능한 타입 정의
CREATE DOMAIN positive_amount AS NUMERIC(12, 2)
CHECK (VALUE > 0);
CREATE DOMAIN short_code AS VARCHAR(10)
NOT NULL;
2. 스테이징 테이블을 통한 데이터 검증 후 적재
외부 데이터를 직접 운영 테이블에 INSERT하지 말고, 스테이징(임시) 테이블에 먼저 적재한 뒤 검증을 거쳐 이동하는 패턴을 사용하세요. 이 방식은 대용량 배치 작업에서 특히 효과적이며, 문제 데이터를 별도로 격리해 분석할 수 있습니다.
-- 스테이징 테이블: 모든 컬럼을 TEXT로 받아서 유연하게 처리
CREATE TABLE stg_orders (
raw_id TEXT,
raw_quantity TEXT,
raw_price TEXT,
loaded_at TIMESTAMPTZ DEFAULT NOW()
);
-- 검증 후 운영 테이블로 이동
INSERT INTO orders (id, quantity, price)
SELECT
raw_id::INTEGER,
raw_quantity::INTEGER,
raw_price::NUMERIC(12, 2)
FROM stg_orders
WHERE raw_id ~ '^\d+$'
AND raw_quantity ~ '^\d+$'
AND raw_price ~ '^\d+(\.\d{1,2})?$';
관련 에러
| 에러 코드 | 이름 | 설명 |
|———–|——|——|
| 22001 | string_data_right_truncation | 문자열이 컬럼 최대 길이를 초과 |
| 22003 | numeric_value_out_of_range | 숫자 값이 타입의 허용 범위를 초과 |
| 22007 | invalid_datetime_format | 잘못된 날짜/시간 형식 |
| 22008 | datetime_field_overflow | 날짜/시간 값이 범위를 초과 |
| 22012 | division_by_zero | 0으로 나누기 시도 |
| 22P02 | invalid_text_representation | 텍스트를 특정 타입으로 변환 실패 |
| 23000 | integrity_constraint_violation | 무결성 제약 조건 위반 (data exception과 혼동 주의) |
22000 에러 클래스는 매우 광범위하므로, 실제 로그에서는 반드시 하위 에러 코드를 확인하여 정확한 원인을 파악하는 것이 중요합니다. PostgreSQL 로그에서 SQLSTATE를 함께 출력하도록 설정(log_line_prefix에 %e 추가)하면 운영 중 빠른 트러블슈팅이 가능합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.