2026년 10월 23일 | DBMS Error 가이드
이 글에서 다루는 내용
2200N 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
2200N invalid xml content 는?
PostgreSQL 에러 코드 2200N: invalid xml content는 XML 데이터 타입 컬럼에 삽입하거나 XML 관련 함수를 사용할 때, 입력된 문자열이 유효한 XML 형식을 갖추지 못했을 경우 발생하는 에러입니다. PostgreSQL은 내부적으로 XML 파서를 통해 데이터를 검증하며, 태그 구조가 잘못되었거나 인코딩 문제, 혹은 XML 선언 오류가 있을 경우 이 에러를 반환합니다. 실무에서는 외부 시스템이나 API로부터 XML 데이터를 수신하여 데이터베이스에 저장할 때 가장 빈번하게 마주치는 에러 중 하나입니다.
주요 발생 원인
1. XML 태그 구조 불일치 (닫히지 않은 태그 또는 중첩 오류)
XML의 가장 기본적인 규칙은 열린 태그는 반드시 닫혀야 하며, 올바른 계층 구조를 유지해야 한다는 것입니다. 예를 들어 대신 처럼 닫는 태그가 누락되거나, 처럼 태그가 잘못 중첩된 경우 PostgreSQL XML 파서는 즉시 2200N 에러를 발생시킵니다. 외부 애플리케이션에서 동적으로 XML을 생성하는 경우 이런 실수가 매우 자주 발생합니다.
2. 잘못된 문자 인코딩 또는 특수 문자 미처리
XML 문서 내에 &, <, >, ", ' 같은 특수 문자를 엔티티로 변환하지 않고 그대로 삽입하면 XML 파서가 이를 태그의 일부로 인식하거나 파싱 오류를 발생시킵니다. 예를 들어 회사명에 & 기호가 포함된 경우 반드시 &로 변환해야 합니다. 또한 UTF-8이 아닌 인코딩으로 작성된 XML을 별도의 변환 없이 저장하려 할 때도 이 에러가 발생할 수 있습니다.
3. XML 선언부(Declaration) 불일치 또는 누락된 루트 엘리먼트
XML 문서가 같은 선언부를 포함할 때, 선언된 인코딩과 실제 데이터의 인코딩이 다르면 파싱 오류가 발생합니다. 또한 XML은 반드시 단일 루트 엘리먼트를 가져야 하는데, 루트 엘리먼트 없이 여러 개의 최상위 엘리먼트가 존재하는 경우에도 2200N 에러가 발생합니다. 이 경우 데이터를 래핑하는 루트 태그를 추가하거나 XML 선언부를 수정해야 합니다.
해결 방법
원인 1 해결: 태그 구조 검증 및 수정
삽입 전에 XML 문자열의 유효성을 먼저 확인하고, 문제가 되는 데이터를 필터링하는 쿼리를 작성합니다.
-- 잘못된 XML 삽입 시 발생하는 에러 예시
INSERT INTO documents (xml_data)
VALUES ('<root><name>홍길동</root>');
-- ERROR: invalid xml content
-- DETAIL: line 1: Opening and ending tag mismatch: name line 1 and root
-- 올바른 태그 구조로 수정
INSERT INTO documents (xml_data)
VALUES ('<root><name>홍길동</name></root>');
-- 기존 테이블에서 유효하지 않은 XML 데이터 찾기
-- (텍스트 컬럼에 저장된 XML 데이터를 검증할 때 활용)
SELECT id, raw_xml
FROM staging_documents
WHERE (
SELECT TRUE
FROM xmlparse(DOCUMENT raw_xml::text) x
-- 캐스팅 시도로 유효성 검사
) IS NULL;
-- DO 블록을 활용한 안전한 XML 삽입
DO $$
DECLARE
v_xml_text TEXT := '<root><name>홍길동</name></root>';
BEGIN
BEGIN
INSERT INTO documents (xml_data)
VALUES (v_xml_text::xml);
RAISE NOTICE '삽입 성공';
EXCEPTION WHEN invalid_xml_content THEN
RAISE WARNING '유효하지 않은 XML: %', v_xml_text;
END;
END;
$$;
원인 2 해결: 특수 문자 이스케이프 처리
-- 특수 문자가 포함된 잘못된 XML 예시
INSERT INTO companies (info)
VALUES ('<company><name>Smith & Jones</name></company>');
-- ERROR: invalid xml content (& 문자가 이스케이프되지 않음)
-- xmlelement 함수를 활용하면 PostgreSQL이 자동으로 특수 문자를 처리
SELECT xmlelement(
name company,
xmlelement(name name, 'Smith & Jones'),
xmlelement(name founded, 2001)
);
-- 결과: <company><name>Smith & Jones</name><founded>2001</founded></company>
-- replace 함수로 수동 이스케이프 처리
WITH raw_data AS (
SELECT 'Smith & Jones <Corp>' AS company_name
)
SELECT xmlelement(
name company,
xmlelement(
name name,
replace(replace(replace(company_name, '&', '&'), '<', '<'), '>', '>')
)
)
FROM raw_data;
-- xmlforest를 활용한 여러 컬럼의 XML 변환
SELECT xmlforest(
company_name AS name,
founded_year AS founded,
ceo_name AS ceo
)
FROM companies
WHERE id = 1;
원인 3 해결: XML 선언부 및 루트 엘리먼트 처리
-- 루트 엘리먼트가 없는 잘못된 XML
INSERT INTO logs (log_xml)
VALUES ('<event>로그인</event><event>로그아웃</event>');
-- ERROR: invalid xml content (루트 엘리먼트가 두 개)
-- 래핑 루트 태그 추가로 해결
INSERT INTO logs (log_xml)
VALUES ('<events><event>로그인</event><event>로그아웃</event></events>');
-- XML 선언부 문제 해결: encoding 선언 제거 또는 통일
-- PostgreSQL은 encoding 선언이 데이터베이스 인코딩과 다르면 에러를 발생시킴
-- 해결책: XML 선언부에서 encoding 속성을 제거하거나 UTF-8로 통일
INSERT INTO documents (xml_data)
VALUES ('<?xml version="1.0"?><root><data>테스트</data></root>');
-- 저장된 텍스트 데이터를 XML로 안전하게 변환
UPDATE staging_table
SET xml_column = (
'<root>' ||
regexp_replace(raw_text, '&(?!amp;|lt;|gt;|quot;|apos;)', '&', 'g') ||
'</root>'
)::xml
WHERE id = 42;
-- xmlparse 함수로 명시적 파싱
SELECT xmlparse(DOCUMENT '<?xml version="1.0"?><root><item>데이터</item></root>');
SELECT xmlparse(CONTENT '<item>데이터</item><item>두번째</item>');
예방 방법
1. 애플리케이션 레벨에서 XML 유효성 검증 후 저장
데이터베이스에 XML 데이터를 저장하기 전에 애플리케이션 레벨에서 반드시 XML 유효성 검증을 수행해야 합니다. Python의 lxml, Java의 JAXP, Node.js의 fast-xml-parser 같은 라이브러리를 활용하여 사전에 파싱 오류를 차단하고, 오류 발생 시 원본 데이터와 함께 별도의 오류 로그 테이블에 기록하는 파이프라인을 구성하는 것이 좋습니다. 이렇게 하면 데이터베이스 레벨에서의 에러를 최소화하고 문제 원인 추적도 쉬워집니다.
-- 오류 데이터 격리를 위한 스테이징 테이블 구성 예시
CREATE TABLE xml_staging (
id SERIAL PRIMARY KEY,
raw_data TEXT NOT NULL,
is_valid BOOLEAN DEFAULT FALSE,
error_msg TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE xml_documents (
id SERIAL PRIMARY KEY,
xml_data XML NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 유효성 검사 후 이관하는 함수
CREATE OR REPLACE FUNCTION migrate_valid_xml()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT id, raw_data FROM xml_staging WHERE is_valid = FALSE LOOP
BEGIN
INSERT INTO xml_documents (xml_data)
VALUES (rec.raw_data::xml);
UPDATE xml_staging SET is_valid = TRUE WHERE id = rec.id;
EXCEPTION WHEN invalid_xml_content THEN
UPDATE xml_staging
SET error_msg = SQLERRM
WHERE id = rec.id;
END;
END LOOP;
END;
$$;
2. CHECK 제약 조건과 트리거를 활용한 데이터베이스 레벨 방어
데이터베이스 스키마 설계 단계에서부터 XML 타입 컬럼을 명시적으로 XML 타입으로 선언하면 PostgreSQL이 자동으로 유효성을 검증합니다. 추가적으로 트리거를 활용하여 삽입/수정 시 XML 구조에 대한 비즈니스 규칙(예: 특정 루트 엘리먼트 존재 여부)을 강제하면 이중으로 데이터 품질을 보호할 수 있습니다.
-- XML 타입 컬럼 사용으로 자동 유효성 검증
CREATE TABLE product_specs (
id SERIAL PRIMARY KEY,
product_id INT NOT NULL,
spec_xml XML NOT NULL, -- XML 타입 자체가 유효성 검사 수행
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 트리거를 활용한 XML 구조 검증 예시
CREATE OR REPLACE FUNCTION validate_xml_structure()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
-- 루트 엘리먼트가 <spec>인지 확인
IF (xpath('local-name(/*)', NEW.spec_xml))[1]::text != 'spec' THEN
RAISE EXCEPTION '유효하지 않은 XML 구조: 루트 엘리먼트는 <spec>이어야 합니다.';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_validate_xml
BEFORE INSERT OR UPDATE ON product_specs
FOR EACH ROW EXECUTE FUNCTION validate_xml_structure();
관련 에러
2200M: invalid XML document:2200N과 유사하지만,XMLPARSE(DOCUMENT ...)함수 사용 시 완전한 XML 문서 형식(XML 선언 포함)이 아닐 때 발생합니다.2200N이 일반적인 XML 콘텐츠 오류라면,2200M은 문서 레벨의 엄격한 검증 실패입니다.22000: data exception: XML 관련 처리 중 발생하는 상위 카테고리 에러로,2200N은 이 카테고리 하위에 속합니다.42804: datatype mismatch: TEXT 타입 데이터를 XML로 캐스팅할 때 타입 불일치가 발생하는 경우로, XML 파싱 오류와 함께 나타날 수 있습니다.39000: external routine invocation exception: PL/pgSQL 또는 외부 프로시저에서 XML 처리 중 발생하는 예외로, XML 관련 함수 호출 시 연관될 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.