2026년 09월 13일 | DBMS Error 가이드
이 글에서 다루는 내용
42723 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
42723 duplicate function 란?
PostgreSQL 에러 코드 42723은 duplicate function 에러로, 동일한 이름과 인자 타입(시그니처)을 가진 함수가 같은 스키마 내에 이미 존재할 때 발생합니다. PostgreSQL은 함수 오버로딩을 지원하지만, 완전히 동일한 시그니처(함수명 + 인자 타입 목록)를 가진 함수를 중복으로 생성하려 할 경우 이 에러를 발생시킵니다. 주로 CREATE FUNCTION 또는 CREATE AGGREGATE 구문 실행 시 나타나며, 마이그레이션 스크립트나 배포 파이프라인에서 자주 마주치는 에러 중 하나입니다.
주요 발생 원인
1. OR REPLACE 없이 기존 함수를 재생성하려는 경우
가장 흔한 원인입니다. 개발 과정에서 함수 로직을 수정한 뒤 CREATE FUNCTION만 사용하고 OR REPLACE를 빠뜨리면, PostgreSQL은 해당 시그니처의 함수가 이미 존재한다고 판단하여 42723 에러를 반환합니다. 특히 여러 명의 개발자가 동일한 데이터베이스에 작업하거나, CI/CD 파이프라인에서 멱등성(idempotent)이 보장되지 않는 스크립트를 반복 실행할 때 빈번하게 발생합니다.
2. 마이그레이션 스크립트의 중복 실행
Flyway, Liquibase, 혹은 커스텀 마이그레이션 도구를 사용할 때, 버전 관리가 올바르게 되지 않아 동일한 SQL 스크립트가 두 번 이상 실행되는 경우가 있습니다. 이 경우 처음 실행에서는 함수가 성공적으로 생성되지만, 두 번째 실행부터 42723 에러가 발생합니다. 마이그레이션 히스토리 테이블이 손상되거나, 수동으로 DB를 조작한 후 마이그레이션을 재실행할 때 이런 상황이 자주 발생합니다.
3. 동일 스키마 내 함수명·인자 타입 충돌
PostgreSQL은 함수명이 같더라도 인자의 타입이 다르면 다른 함수로 간주(오버로딩)합니다. 그러나 개발자가 타입을 미묘하게 다르게 설정했다고 생각하지만 실제로는 PostgreSQL 내부에서 동일하게 취급되는 경우가 있습니다. 예를 들어 varchar와 character varying은 PostgreSQL에서 동일한 타입으로 취급되므로, 이 두 타입으로 각각 함수를 만들려 하면 42723 에러가 발생합니다.
해결 방법
해결책 1: CREATE OR REPLACE FUNCTION 사용
가장 간단하고 권장되는 해결 방법입니다. 기존 함수의 리턴 타입이 동일하다면 OR REPLACE 키워드만 추가하면 됩니다.
-- 잘못된 방법 (42723 에러 발생 가능)
CREATE FUNCTION get_employee_name(p_emp_id INTEGER)
RETURNS VARCHAR AS $$
BEGIN
RETURN (SELECT name FROM employees WHERE emp_id = p_emp_id);
END;
$$ LANGUAGE plpgsql;
-- 올바른 방법: OR REPLACE 사용
CREATE OR REPLACE FUNCTION get_employee_name(p_emp_id INTEGER)
RETURNS VARCHAR AS $$
BEGIN
RETURN (SELECT name FROM employees WHERE emp_id = p_emp_id);
END;
$$ LANGUAGE plpgsql;
> ⚠️ 주의: CREATE OR REPLACE는 리턴 타입을 변경할 수 없습니다. 리턴 타입을 바꿔야 한다면 아래 해결책 2를 사용하세요.
해결책 2: 기존 함수를 DROP 후 재생성
리턴 타입을 변경해야 하거나, 함수를 완전히 재정의해야 할 때 사용합니다. CASCADE 옵션은 해당 함수에 의존하는 객체(뷰, 다른 함수 등)도 함께 삭제하므로 주의가 필요합니다.
-- 기존 함수 삭제 (인자 타입 명시 필수)
DROP FUNCTION IF EXISTS get_employee_name(INTEGER);
-- 새로운 함수 생성
CREATE FUNCTION get_employee_name(p_emp_id INTEGER)
RETURNS TEXT AS $$
BEGIN
RETURN (SELECT name FROM employees WHERE emp_id = p_emp_id);
END;
$$ LANGUAGE plpgsql;
-- 의존 객체가 있는 경우 CASCADE 사용 (주의 필요)
DROP FUNCTION IF EXISTS get_employee_name(INTEGER) CASCADE;
해결책 3: 기존 함수 존재 여부 확인 후 조건부 처리
DO 블록을 사용하여 함수 존재 여부를 사전에 확인하고 조건부로 처리하는 방법입니다. 마이그레이션 스크립트에서 멱등성을 보장할 때 유용합니다.
-- pg_proc 카탈로그에서 함수 존재 여부 확인
SELECT proname, proargtypes, prosrc
FROM pg_proc
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
WHERE pg_namespace.nspname = 'public'
AND pg_proc.proname = 'get_employee_name';
-- 조건부 삭제 후 생성 (DO 블록 활용)
DO $$
BEGIN
-- 함수가 존재하면 삭제
IF EXISTS (
SELECT 1 FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public'
AND p.proname = 'get_employee_name'
AND pg_get_function_identity_arguments(p.oid) = 'integer'
) THEN
DROP FUNCTION public.get_employee_name(INTEGER);
END IF;
END $$;
-- 이후 함수 생성
CREATE FUNCTION public.get_employee_name(p_emp_id INTEGER)
RETURNS TEXT AS $$
BEGIN
RETURN (SELECT name FROM employees WHERE emp_id = p_emp_id);
END;
$$ LANGUAGE plpgsql;
해결책 4: 전체 함수 목록 조회로 충돌 확인
동일 이름의 함수가 어떤 시그니처로 존재하는지 먼저 파악하는 것이 중요합니다.
-- 특정 함수명으로 등록된 모든 시그니처 조회
SELECT
n.nspname AS schema_name,
p.proname AS function_name,
pg_get_function_identity_arguments(p.oid) AS arguments,
pg_get_function_result(p.oid) AS return_type,
p.prolang::regproc AS language
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE p.proname = 'get_employee_name'
ORDER BY n.nspname, p.proname;
예방 방법
1. 모든 함수 정의 스크립트에 CREATE OR REPLACE 원칙 적용
팀 내 코딩 컨벤션으로 함수 생성 시 항상 CREATE OR REPLACE FUNCTION을 사용하도록 규칙화하세요. 리턴 타입이 변경될 가능성이 있는 경우에는 DROP FUNCTION IF EXISTS + CREATE FUNCTION 패턴을 마이그레이션 스크립트 템플릿으로 표준화하여 사용하는 것이 좋습니다. 코드 리뷰 단계에서 CREATE FUNCTION만 단독으로 사용한 경우를 자동으로 감지하는 린트 규칙을 추가하면 더욱 효과적입니다.
-- 팀 표준 함수 생성 템플릿 예시
-- Step 1: 안전하게 삭제
DROP FUNCTION IF EXISTS public.function_name(arg_type1, arg_type2);
-- Step 2: 재생성
CREATE OR REPLACE FUNCTION public.function_name(
p_arg1 arg_type1,
p_arg2 arg_type2
)
RETURNS return_type AS $$
BEGIN
-- 함수 본문
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public;
2. 마이그레이션 스크립트의 멱등성(Idempotency) 보장
마이그레이션 스크립트는 몇 번을 실행해도 동일한 결과를 보장해야 합니다. Flyway나 Liquibase를 사용할 때는 버전 번호를 엄격히 관리하고, 함수 관련 스크립트에는 반드시 IF EXISTS 조건이나 OR REPLACE를 포함시키세요. 또한, 스테이징 환경에서 마이그레이션 스크립트를 반드시 검증한 후 프로덕션에 적용하는 프로세스를 정착시키는 것이 중요합니다.
-- 멱등성이 보장된 마이그레이션 스크립트 예시
-- V20240101__create_get_employee_name_function.sql
DROP FUNCTION IF EXISTS public.get_employee_name(INTEGER);
CREATE FUNCTION public.get_employee_name(p_emp_id INTEGER)
RETURNS TEXT AS $$
DECLARE
v_name TEXT;
BEGIN
SELECT name INTO v_name
FROM employees
WHERE emp_id = p_emp_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Employee not found: %', p_emp_id;
END IF;
RETURN v_name;
END;
$$ LANGUAGE plpgsql
STABLE
SECURITY INVOKER;
관련 에러
- 42710
duplicate_object: 이미 존재하는 데이터베이스 객체(인덱스, 스키마, 데이터베이스 등)를 생성하려 할 때 발생하는 에러로, 42723과 유사한 맥락입니다. - 42701
duplicate_column: 테이블에 이미 존재하는 컬럼명을 추가하려 할 때 발생합니다. - 42P07
duplicate_table:CREATE TABLE시 동일한 이름의 테이블이 이미 존재할 때 발생하며,CREATE TABLE IF NOT EXISTS로 예방할 수 있습니다. - 2BP01
dependent_objects_still_exist: 함수를 삭제하려 할 때 해당 함수에 의존하는 다른 객체가 있을 경우 발생하며,DROP FUNCTION ... CASCADE로 해결할 수 있지만 신중하게 사용해야 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.