Categories
웹개발/Oracle
Oracle ORA-00001 unique constraint 오류 원인과 해결 방법
ORA-00001 문제 상황
insert를 실행했더니 아래 메시지가 뜨면서 작업이 멈춘다. 운영 중인 화면에서 저장 버튼을 눌렀을 때, 또는 배치/프로시저를 돌렸을 때 자주 마주치는 Oracle ORA-00001 오류다.
ORA-00001: unique constraint (SCOTT.PK_MEMBER) violated
메시지를 그대로 읽으면 "유니크 제약조건이 위반됐다"는 뜻이다. 즉 PK나 UNIQUE 제약이 걸린 컬럼에 이미 있는 값을 또 넣으려고 해서 막힌 것이다. 괄호 안의 SCOTT.PK_MEMBER가 위반된 제약조건 이름이고, 여기서 원인 추적을 시작하면 된다.
MyBatis를 쓰는 Spring 프로젝트라면 로그에 보통 이런 형태로 같이 남는다.
org.springframework.dao.DuplicateKeyException:
### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SCOTT.PK_MEMBER) violated
원인 요약
| 원인 | 설명 | 확인 방법 |
|---|---|---|
| PK 값 중복 | 이미 존재하는 기본키 값을 insert | 해당 키로 SELECT 해서 기존 행 확인 |
| UNIQUE 제약 중복 | PK는 아니지만 UNIQUE 인덱스가 걸린 컬럼 중복 | 제약조건 이름으로 대상 컬럼 조회 |
| 시퀀스 꼬임 | 수동 입력과 시퀀스 채번이 섞여 값이 겹침 | 시퀀스 currval과 테이블 max값 비교 |
| 중복 클릭/재요청 | 저장 버튼 중복 클릭으로 같은 insert 두 번 | 요청 로그, 트랜잭션 시점 확인 |
| 복합 UNIQUE 위반 | 여러 컬럼 조합 UNIQUE에서 조합이 중복 | 조합 컬럼으로 GROUP BY HAVING 확인 |
해결 방법
1. 위반된 제약조건이 어떤 컬럼인지 찾기
에러 메시지의 제약조건 이름(PK_MEMBER 등)을 그대로 딕셔너리 뷰에서 조회하면 어떤 테이블, 어떤 컬럼에 걸린 건지 바로 나온다. PK인지 UNIQUE인지부터 구분하는 게 시작점이다.
-- 제약조건 이름으로 대상 테이블/타입 확인
SELECT constraint_name, constraint_type, table_name
FROM user_constraints
WHERE constraint_name = 'PK_MEMBER';
-- 그 제약조건에 묶인 컬럼 확인
SELECT constraint_name, column_name, position
FROM user_cons_columns
WHERE constraint_name = 'PK_MEMBER'
ORDER BY position;
constraint_type이 P면 PK, U면 UNIQUE다. 복합키라면 position 순서대로 여러 컬럼이 나온다. 여기서 나온 컬럼이 실제 중복을 봐야 할 대상이다.
2. 중복 데이터 직접 확인하기
대상 컬럼을 알았으면 실제로 어떤 값이 겹치는지 본다. insert하려던 값으로 먼저 SELECT를 돌려 기존 행이 있는지 확인하는 게 가장 확실하다.
-- 넣으려는 값이 이미 있는지 확인
SELECT *
FROM member
WHERE member_id = 'mem1001';
-- 테이블 전체에서 중복 키가 있는지 점검 (복합키 예시)
SELECT member_id, COUNT(*)
FROM member
GROUP BY member_id
HAVING COUNT(*) > 1;
이미 같은 값이 있다면, 업무적으로 이게 "수정"이어야 하는지 "신규"여야 하는지부터 판단한다. 신규가 맞다면 키 채번 로직이 잘못된 것이고, 수정이 맞다면 insert가 아니라 update 또는 MERGE로 가야 한다.
3. 신규/수정이 섞인다면 MERGE로 처리
"있으면 update, 없으면 insert" 패턴에서 insert만 고집하면 ORA-00001이 반복된다. 이럴 때 MERGE가 적합하다.
-- 있으면 갱신, 없으면 삽입
MERGE INTO member t
USING (SELECT 'mem1001' AS member_id, '홍길동' AS member_nm FROM dual) s
ON (t.member_id = s.member_id)
WHEN MATCHED THEN
UPDATE SET t.member_nm = s.member_nm
WHEN NOT MATCHED THEN
INSERT (member_id, member_nm)
VALUES (s.member_id, s.member_nm);
MERGE는 편하지만 ON 조건이 부정확하면 엉뚱한 행을 갱신할 수 있다. 운영 반영 전에는 ON 조건이 실제 키와 일치하는지, 갱신 대상이 의도한 행만인지 테스트 환경에서 먼저 확인한다.
4. 시퀀스로 채번하는데 중복이 난다면
PK를 시퀀스로 채번하는 구조인데 ORA-00001이 난다면, 과거에 수동 insert로 들어간 값이 시퀀스 현재값을 앞질러 있는 경우가 많다. 시퀀스 다음 값과 테이블의 실제 최대값을 비교한다.
-- 테이블의 현재 최대 키
SELECT MAX(seq_no) FROM board;
-- 시퀀스 현재값 확인 (currval은 같은 세션에서 nextval 호출 후 사용 가능)
-- 보통 데이터 사전으로 확인
SELECT sequence_name, last_number
FROM user_sequences
WHERE sequence_name = 'SEQ_BOARD';
테이블 max값이 시퀀스 last_number보다 크면 채번이 따라잡힐 때까지 계속 충돌한다. 이 경우 시퀀스를 재생성하거나 증가시켜 맞추는데, 운영 DB의 시퀀스 변경은 영향 범위가 크므로 반드시 백업과 검증 후 진행한다.
5. MyBatis/Spring에서 예외를 다루는 방법
애플리케이션 레벨에서는 중복을 미리 막거나, 발생한 예외를 사용자 메시지로 바꿔주는 처리가 필요하다. Spring은 ORA-00001을 DuplicateKeyException으로 변환해 주므로 이를 잡아 분기할 수 있다.
// 서비스 계층 예시
try {
memberMapper.insertMember(member);
} catch (DuplicateKeyException e) {
// 이미 존재하는 데이터 처리 (안내 메시지, update 분기 등)
throw new BizException("이미 등록된 회원입니다.");
}
다만 예외 처리는 마지막 안전장치다. 가능하면 insert 전에 존재 여부를 확인하거나, 화면에서 저장 버튼 중복 클릭을 막는 것이 먼저다. 트랜잭션 처리와 로그 기록도 함께 고려한다.
실무에서 자주 놓치는 부분
- 제약조건 이름만 보고 PK라고 단정하지 않는다. UNIQUE 인덱스에서도 ORA-00001이 난다.
user_constraints로 타입을 먼저 확인한다. - 저장 버튼 중복 클릭으로 같은 insert가 두 번 들어가는 경우가 의외로 많다. 화면단 중복 방지 처리가 빠졌는지 본다.
- 운영 데이터 정리를 위해 중복 행을 삭제할 때는
WHERE조건 없는 DELETE를 절대 돌리지 않는다. 먼저 SELECT로 삭제 대상을 확인하고, 백업 후 진행한다. - 로컬에서는 빈 테이블이라 문제없지만 운영에는 데이터가 쌓여 있어 중복이 나는 경우가 흔하다. 운영 데이터 기준으로 키 설계를 다시 본다.
운영 서버에서 확인할 부분
운영에서 ORA-00001이 반복된다면 단건 오류로 넘기지 말고 패턴을 본다. 특정 시간대에 몰린다면 배치나 스케줄러의 중복 실행을 의심한다. 특정 사용자/화면에서만 난다면 그 화면의 채번 또는 중복 체크 로직을 점검한다. 데이터를 직접 손봐야 한다면, 운영 DB는 변경 전 백업이 원칙이고, 변경 SQL은 SELECT로 대상을 먼저 확인한 뒤 트랜잭션 단위로 적용한다. 가능하면 테스트 환경에서 동일 데이터로 재현한 다음 반영하는 것이 안전하다.
FAQ
Q1. ORA-00001에서 어떤 컬럼이 문제인지 어떻게 아나요?
에러 메시지 괄호 안의 제약조건 이름을 user_cons_columns에서 조회하면 묶인 컬럼이 나온다. 그 컬럼이 중복 확인 대상이다.
Q2. PK도 아닌데 ORA-00001이 납니다.
UNIQUE 제약조건이나 UNIQUE 인덱스가 걸린 컬럼이라면 PK가 아니어도 동일하게 발생한다. user_constraints의 constraint_type으로 확인한다.
Q3. 시퀀스를 쓰는데도 중복이 나는 이유는 뭔가요?
과거 수동 입력 값이 시퀀스 현재값보다 커서 채번이 따라잡히는 동안 충돌하는 경우다. 테이블 max값과 시퀀스 last_number를 비교해 본다.
Q4. 중복을 무시하고 insert를 통과시킬 방법이 있나요?
업무 규칙상 "있으면 갱신"이 맞다면 MERGE를 쓴다. 무조건 무시하는 처리는 데이터 정합성을 깨뜨릴 수 있어 권하지 않는다. 신규/수정 여부를 먼저 판단하는 것이 우선이다.
Q5. MyBatis에서 ORA-00001을 어떻게 잡나요?
Spring 환경이면 DuplicateKeyException으로 변환되므로 서비스 계층에서 잡아 사용자 메시지로 바꾸거나 update로 분기할 수 있다. 다만 insert 전 존재 확인이 먼저다.
정리
Oracle ORA-00001은 PK 또는 UNIQUE 제약에 이미 있는 값을 넣어서 생기는 오류다. 해결의 시작은 에러 메시지의 제약조건 이름으로 대상 컬럼을 찾고, 실제 중복 데이터를 SELECT로 확인하는 것이다. 그다음 업무적으로 신규인지 수정인지 판단해 insert, update, MERGE 중 맞는 방식을 고른다. 시퀀스 채번 충돌이라면 테이블 max값과 시퀀스 값을 맞추되, 운영 DB 변경은 백업과 검증을 거친다. ORA-00001은 결국 데이터 정합성 문제이므로, 애플리케이션의 중복 체크와 DB 제약을 함께 점검하는 것이 재발을 막는 길이다.
'웹개발 > Oracle' 카테고리의 다른 글
| AL32UTF8 KO16MSWIN949 한글 깨짐, Oracle 캐릭터셋 차이 정리 (0) | 2026.07.10 |
|---|---|
| Oracle 날짜 비교 방법과 자주 하는 실수 정리 (0) | 2026.06.28 |
| Oracle INDEX 힌트 완벽 가이드 (INDEX, FULL, NO_INDEX 실전 예제 포함)👉 실행계획을 바꾸는 오라클 힌트 사용법 (0) | 2025.07.01 |
| Function-Based Index (FBI)란? Oracle 인덱스 최적화 실전 가이드 (0) | 2025.07.01 |
| Oracle에서 WHERE 조건에 인덱스가 안 타는 이유와 해결법 (실전 예제 포함) (0) | 2025.07.01 |