웹 개발
UNIQUE 제약 위반은 항상 예상 밖에서 터진다
데이터베이스의 UNIQUE 제약이 모르는 사이에 깨지는 상황과, 그걸 디버깅하는 차근차근한 과정.
제약이 깨진 시점을 특정한다
UNIQUE 제약 위반은 애플리케이션 에러로 나타나기도 전에 데이터가 일부만 저장되는 상황을 만든다. 먼저 스키마를 확인한다.
# PostgreSQL에서 테이블의 제약 확인
\d+ table_name
# 인덱스와 제약 모두 보기
SELECT * FROM information_schema.table_constraints
WHERE table_name = 'your_table' AND constraint_type = 'UNIQUE';
제약이 어떻게 정의되어 있는지 확인하는 것부터 시작한다. 단일 컬럼인가, 복합 키인가, NULL을 허용하는가.
데이터 상태를 먼저 본다
# 중복값 찾기
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
# NULL 처리 확인 (복합 UNIQUE의 경우)
SELECT * FROM table_name WHERE unique_column IS NULL;
DELETE나 INSERT 실패 에러가 나기 전에 이미 일부 중복이 쌓여 있을 수도 있다. 데이터를 먼저 정리한 후 제약을 재설정해야 할 수도 있다.
마이그레이션 순서 재확인
ORM 마이그레이션이 부분적으로 실행되었을 수 있다.
# Prisma의 경우
npx prisma migrate status
# 적용된 마이그레이션 목록
npx prisma migrate resolve --rolled-back <migration_name>
마이그레이션 중간에 중단되었거나 롤백되었다면, 스키마와 실제 데이터가 불일치한 상태가 된다.
권한 확인
여러 애플리케이션이 같은 테이블에 접근한다면 권한 설정도 확인한다.
# 테이블 소유자와 권한
SELECT * FROM information_schema.role_table_grants
WHERE table_name = 'your_table';
예상과 다른 계정이 쓰기 권한을 가지고 있거나, 트리거가 자동으로 데이터를 수정할 수도 있다.
직접 테스트한다
문제 재현을 위해 작은 실험부터 한다.
# 같은 값을 두 번 INSERT 시도
BEGIN;
INSERT INTO users (email) VALUES ('[email protected]');
INSERT INTO users (email) VALUES ('[email protected]');
COMMIT;
첫 번째는 성공하고 두 번째는 실패해야 정상이다. 만약 두 값이 다른데도 충돌한다면, 코드나 트리거에 숨은 로직이 있을 수 있다.
최종 점검
제약을 재구성했으면 몇 가지 더 확인한다.
- 원래 실패하던 작업이 지금은 성공하는가
- 데이터 일관성은 유지되는가
- 다른 테이블의 외래 키 제약에 영향은 없는가
이 정도 확인이 끝나면 제대로 정리된 상태다.