ORA-00001 unique constraint violated — PK·UK 중복 에러 원인·해결·예방
Oracle Database에서 INSERT나 UPDATE를 실행하다가 ORA-00001: unique constraint violated 에러를 만나는 경우가 있다. 단순한 중복 입력 외에도 MERGE 동시성 충돌이나 시퀀스 동기화 깨짐 같은 운영 이슈에서도 같은 에러로 나타나기 때문에 원인을 좁히기 위해서는 제약 종류와 위반 컬럼을 정확히 식별해야 한다.
본 글은 ORA-00001의 정확한 의미와 자주 발생하는 6가지 케이스, 단계별 디버깅, 회피 패턴(MERGE·DUP_VAL_ON_INDEX), UNIQUE 제약·인덱스·NULL 처리까지 정리한 트러블슈팅 자료다.

이 글의 구성
01 에러 메시지와 카테고리
ORA-00001은 INSERT·UPDATE·MERGE가 PRIMARY KEY 또는 UNIQUE 제약(UNIQUE INDEX 포함)으로 보호된 컬럼에 중복 값을 생성하려고 시도할 때 발생한다.
| 항목 | 내용 |
|---|---|
| 에러 코드 | ORA-00001 |
| 영문 메시지 | unique constraint (schema.constraint_name) violated |
| 23ai 이상 확장 | 에러 메시지에 테이블명과 위반 컬럼 목록까지 함께 표시 (23ai 신규 ERROR_MESSAGE_DETAILS 파라미터) |
| Cause (공식) | An UPDATE or INSERT statement attempted to insert a duplicate key. For Trusted Oracle configured in DBMS MAC mode, you may see this message if a duplicate entry exists at a different level. |
| Action (공식) | Either remove the unique restriction or do not insert the key. |
| 발생 영역 | SQL INSERT·UPDATE·MERGE 및 PL/SQL 블록 |
핵심 관찰
ORA-00001을 빠르게 진단하는 열쇠는 에러 메시지에 포함된 constraint_name이다. ALL_CONSTRAINTS · ALL_CONS_COLUMNS · ALL_INDEXES를 이 이름으로 조회하면 위반된 컬럼과 제약 종류를 즉시 확인할 수 있다.
02 발생 원인 6가지
실무에서 ORA-00001은 다음 6가지 상황에서 가장 자주 발생한다.
원인 1 — PRIMARY KEY 중복 INSERT
이미 존재하는 PK 값으로 INSERT를 시도할 때 발생한다. 시퀀스를 사용하지 않고 직접 PK를 지정하거나, ID 매핑 로직이 잘못된 경우 자주 나타난다.
원인 2 — UNIQUE 제약 중복 INSERT
이메일·사용자명·주민번호처럼 UNIQUE로 선언된 컬럼에 중복 값을 넣을 때 발생한다. 회원가입·중복 체크 누락 시 흔하다.
원인 3 — 복합 UNIQUE 제약 위반
두 개 이상의 컬럼이 함께 UNIQUE로 선언된 경우 같은 조합이 중복되면 발생한다. 주문 테이블의 (고객ID, 주문일자) 같은 조합이 대표적이다.
원인 4 — UPDATE로 UNIQUE 위반
기존 다른 행이 사용 중인 값으로 UPDATE를 시도할 때 발생한다. INSERT뿐 아니라 UPDATE도 제약 검사를 받기 때문에 자주 놓친다.
원인 5 — MERGE 시 동시성 충돌
두 세션이 동시에 같은 키로 MERGE를 실행할 때, 한 세션이 INSERT를 미커밋 상태로 유지하면 다른 세션은 그 행을 못 보고 INSERT 분기로 진입했다가 커밋 시점에 ORA-00001을 만난다.
원인 6 — 시퀀스 동기화 깨짐
데이터 마이그레이션이나 export/import 후 시퀀스 NEXTVAL이 테이블의 MAX(PK)보다 작을 때 발생한다. INSERT가 시퀀스 다음 값을 사용하지만 이미 그 값이 테이블에 존재한다.
03 재현 시나리오와 코드 예제
각 원인을 실제 코드로 살펴본다.
원인 1 — PRIMARY KEY 중복
CREATE TABLE emp (id NUMBER PRIMARY KEY, name VARCHAR2(50));
-- ✓ 첫 INSERT 성공
INSERT INTO emp VALUES (1, 'Alice');
-- ✗ 같은 PK로 두 번째 INSERT
INSERT INTO emp VALUES (1, 'Bob');
-- ORA-00001: unique constraint (SCOTT.SYS_C00xxx) violated
원인 2 — UNIQUE 제약 중복
ALTER TABLE emp ADD CONSTRAINT emp_email_uk UNIQUE(email);
INSERT INTO emp(id, email) VALUES (2, 'a@x.com');
-- ✗ 같은 이메일로 INSERT
INSERT INTO emp(id, email) VALUES (3, 'a@x.com');
-- ORA-00001: unique constraint (SCOTT.EMP_EMAIL_UK) violated
원인 3 — 복합 UNIQUE 위반
ALTER TABLE orders ADD CONSTRAINT ord_uk
UNIQUE(customer_id, order_date);
INSERT INTO orders VALUES (10, DATE '2026-05-11', 100);
-- ✗ 같은 조합 INSERT
INSERT INTO orders VALUES (10, DATE '2026-05-11', 200);
-- ORA-00001: unique constraint (SCOTT.ORD_UK) violated
원인 4 — UPDATE로 UNIQUE 위반
-- 현재 상태: id=2 → 'a@x.com', id=3 → 'b@x.com'
-- ✗ 이미 id=2가 사용 중인 이메일로 UPDATE
UPDATE emp SET email = 'a@x.com' WHERE id = 3;
-- ORA-00001: unique constraint (SCOTT.EMP_EMAIL_UK) violated
원인 5 — MERGE 동시성 충돌
-- 세션 A: id=1로 INSERT 후 미커밋
INSERT INTO target VALUES (1, 'X');
-- 세션 B: 같은 시점 MERGE 시도 → A의 미커밋 행 못 봄
MERGE INTO target t
USING (SELECT 1 id, 'Y' val FROM dual) s
ON (t.id = s.id)
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val);
-- 세션 A 커밋 후 세션 B 커밋 시점에 ORA-00001 발생
원인 6 — 시퀀스 동기화 깨짐
-- 데이터 import 후 시퀀스가 뒤처진 상황 확인
SELECT MAX(id) FROM emp; -- 결과: 9999
SELECT seq_emp.NEXTVAL FROM dual; -- 결과: 100
-- ✗ INSERT 시 100, 101 ... 사용 → 이미 존재 → ORA-00001
-- ✓ 해결: 시퀀스를 MAX(PK)보다 큰 값으로 점프
ALTER SEQUENCE seq_emp INCREMENT BY 9900;
SELECT seq_emp.NEXTVAL FROM dual; -- 10000
ALTER SEQUENCE seq_emp INCREMENT BY 1;
04 7단계 디버깅 순서
단계별 디버깅 순서
constraint_name을 정확히 추출한다.-- 제약 종류와 상태 확인
SELECT owner, constraint_name, constraint_type, table_name,
status, deferrable, deferred, validated, index_name
FROM all_constraints
WHERE constraint_name = 'EMP_EMAIL_UK';
-- constraint_type: P=Primary Key, U=Unique, C=Check, R=Foreign Key
-- 제약 컬럼 확인
SELECT constraint_name, column_name, position
FROM all_cons_columns
WHERE constraint_name = 'EMP_EMAIL_UK';
-- 백킹 인덱스 확인
SELECT index_name, uniqueness, table_name, status
FROM all_indexes
WHERE table_name = 'EMP';
05 회피 패턴
ORA-00001을 미리 방지하거나 우아하게 처리하는 표준 패턴 3가지가 있다.
패턴 1 — INSERT … WHERE NOT EXISTS
-- 동일 이메일이 없을 때만 INSERT
INSERT INTO emp(id, email)
SELECT 3, 'a@x.com' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM emp WHERE email = 'a@x.com'
);
패턴 2 — MERGE INTO (UPSERT)
Oracle은 MySQL의 INSERT ... ON DUPLICATE KEY UPDATE 같은 문법을 지원하지 않는다. UPSERT는 MERGE로 구현한다.
MERGE INTO emp t
USING (SELECT 3 id, 'a@x.com' email FROM dual) s
ON (t.id = s.id)
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);
패턴 3 — DUP_VAL_ON_INDEX 예외 처리 (PL/SQL)
BEGIN
INSERT INTO emp(id, email) VALUES (3, 'a@x.com');
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- 이미 존재 → UPDATE로 전환 또는 로깅 후 무시
UPDATE emp SET email = 'a@x.com' WHERE id = 3;
END;
/
DUP_VAL_ON_INDEX는 ORA-00001에 매핑된 미리 정의된 예외(predefined exception)다. MERGE 동시성 이슈가 잦은 환경에서는 INSERT를 먼저 시도하고 실패 시 UPDATE로 전환하는 이 패턴이 MERGE보다 안전하게 작동한다.
06 UNIQUE 제약·인덱스 차이와 NULL 처리
ORA-00001을 다룰 때 자주 혼동되는 두 가지 개념이 UNIQUE CONSTRAINT와 UNIQUE INDEX, 그리고 NULL 처리다.
| 항목 | UNIQUE CONSTRAINT | UNIQUE INDEX |
|---|---|---|
| 목적 | 데이터 무결성 선언 | 빠른 조회 + 부수적 유일성 |
| 메타데이터 위치 | ALL_CONSTRAINTS | ALL_INDEXES |
| DEFERRABLE 지원 | 가능 (INITIALLY DEFERRED) | 불가 |
| 활성/비활성 | ALTER ... DISABLE/ENABLE | DROP/CREATE만 가능 |
| FK 참조 대상 | 가능 | 불가 |
NULL 처리 규칙
- UNIQUE 제약과 UNIQUE INDEX 모두 NULL을 "유일하지 않음"으로 취급한다. 따라서 NULL은 같은 컬럼에 여러 행이 존재할 수 있다.
- 복합 UNIQUE(col1, col2)에서는 모든 키 컬럼이 NULL인 행만 다수 허용된다. 한 컬럼이라도 값이 있으면 유일성 검사가 적용된다.
- "NULL을 한 행만 허용"하고 싶다면 함수 기반 인덱스로 우회한다.
-- NULL도 유일하게 만들고 싶다면 함수 기반 인덱스
CREATE UNIQUE INDEX emp_email_uix
ON emp (NVL(email, '__NO_EMAIL__'));
제약 비활성화/활성화 (대량 적재 시)
-- 대량 적재 직전 비활성화
ALTER TABLE emp DISABLE CONSTRAINT emp_email_uk;
-- 데이터 적재 후 위반 행을 EXCEPTIONS 테이블로 받으며 재활성화
ALTER TABLE emp ENABLE VALIDATE CONSTRAINT emp_email_uk
EXCEPTIONS INTO exceptions;
07 자주 묻는 질문 5가지
Q1Oracle에 INSERT ON DUPLICATE KEY UPDATE 같은 문법이 있나
없다. Oracle은 MySQL의 INSERT ... ON DUPLICATE KEY UPDATE를 직접 지원하지 않는다. 같은 기능이 필요하면 MERGE INTO 문으로 UPSERT를 구현하거나, PL/SQL에서 INSERT 후 DUP_VAL_ON_INDEX 예외로 UPDATE로 전환하는 패턴을 사용한다.
Q2UNIQUE 컬럼에 NULL을 여러 개 넣을 수 있나
단일 컬럼 UNIQUE는 NULL을 유일성 검사에서 제외하므로 여러 행에 NULL을 가질 수 있다. 복합 UNIQUE에서는 모든 키 컬럼이 NULL인 경우만 다수 허용되고, 한 컬럼이라도 값이 있으면 유일성 검사가 적용된다. NULL도 한 행만 허용하고 싶다면 NVL을 사용한 함수 기반 인덱스로 우회한다.
Q3MERGE가 더 안전한데 왜 DUP_VAL_ON_INDEX 패턴을 쓰나
MERGE도 동시 세션이 같은 키로 INSERT를 시도하면 ORA-00001이 발생할 수 있다. 한 세션의 미커밋 INSERT는 다른 세션이 볼 수 없기 때문이다. 이런 환경에서는 INSERT를 먼저 시도하고 실패 시 UPDATE로 전환하는 DUP_VAL_ON_INDEX 패턴이 더 안정적이다. 다만 단일 세션 작업이거나 동시성이 낮은 환경에서는 MERGE가 더 간결하다.
Q4시퀀스를 사용하는데 ORA-00001이 발생하면 어떻게 진단하나
데이터 마이그레이션이나 export/import 직후 시퀀스 NEXTVAL이 테이블의 MAX(PK)보다 작아진 경우가 흔하다. SELECT MAX(id) FROM 테이블과 SELECT 시퀀스.NEXTVAL FROM dual을 비교해 동기화 상태를 확인하고, ALTER SEQUENCE ... INCREMENT BY로 점프시킨 뒤 다시 1로 되돌려 동기화를 맞춘다.
Q5대량 데이터 적재 시 ORA-00001을 줄이는 방법은
적재 전에 UNIQUE 제약을 일시 비활성화(ALTER TABLE ... DISABLE CONSTRAINT)하고, 적재 후 ENABLE VALIDATE CONSTRAINT ... EXCEPTIONS INTO exceptions로 활성화하면 중복 행을 별도 테이블로 분리해 받을 수 있다. 또는 적재 데이터를 사전 정제하거나 MERGE를 사용해 중복을 흡수하는 방식도 있다.
08 결론
ORA-00001은 트러블슈팅 측면에서 비교적 명확한 에러다. 핵심은 에러 메시지의 제약 이름을 출발점으로 ALL_CONSTRAINTS·ALL_CONS_COLUMNS·ALL_INDEXES를 차례로 조회해 위반 컬럼과 제약 종류를 확인하는 것이다.
실무에서 이 에러를 줄이려면 다음 세 가지 습관이 도움이 된다.
첫째, 중복 가능성이 있는 INSERT는 처음부터 MERGE 또는 PL/SQL의 DUP_VAL_ON_INDEX 패턴으로 안전하게 처리한다.
둘째, 데이터 마이그레이션 후에는 시퀀스 NEXTVAL과 MAX(PK)를 비교하는 검증 단계를 표준화한다.
셋째, 대량 적재 시에는 제약 비활성화·재활성화 패턴과 EXCEPTIONS 테이블을 적극 활용해 중복 행을 분리 처리한다.
ORA-00001의 진단 출발점은 에러 메시지의 제약 이름이다. ALL_CONSTRAINTS·ALL_CONS_COLUMNS·ALL_INDEXES 3개 뷰로 위반 컬럼을 즉시 추적할 수 있다.
— 회피는 MERGE 또는 DUP_VAL_ON_INDEX 예외 처리가 표준
트러블슈팅 체크리스트
본 글은 Oracle Database에서 발생하는 ORA-00001 에러의 일반적 원인과 해결 방법을 정리한 자료다. 시퀀스·MERGE 동시성·제약 비활성화는 운영 환경마다 영향이 다르므로 적용 전 테스트 환경에서 충분히 검증한다. Oracle 공식 문서와 최신 패치 노트를 함께 참고하면 더 안정적인 트러블슈팅이 가능하다.
#ORA00001 #uniqueconstraint #오라클에러 #Oracle #OracleDatabase #PRIMARYKEY #UNIQUE #MERGE #UPSERT #DUPVALONINDEX #PLSQL #시퀀스 #ALLCONSTRAINTS #DB트러블슈팅 #SQL제약
'IT' 카테고리의 다른 글
| ORA-00936 missing expression — 표현식 누락 에러 원인·해결·예방 (0) | 2026.05.12 |
|---|---|
| ORA-01017 invalid username/password — 로그인 실패 에러 원인·해결·예방 (0) | 2026.05.12 |
| ORA-00942 table or view does not exist — 테이블·뷰 없음 에러 원인·해결·예방 (0) | 2026.05.12 |
| ORA-00919 invalid function — 잘못된 SQL 함수 에러 원인·해결·예방 (0) | 2026.05.12 |
| ORA-00918 : 컬럼 정의가 애매합니다 - 조인할 때 꼭 알아야 할 것 (0) | 2026.04.08 |
댓글