Duplicate entry ... for key가 났을 때 어느 제약이 걸렸는지 찾는 법
문제 발생
가입 처리에서 간헐적으로 오류가 났습니다.
ERROR 1062 (23000): Duplicate entry '[email protected]' for key 'users.users_email_unique'원인 분석
오류 1062(ER_DUP_ENTRY, SQLSTATE 23000) 는 유니크 인덱스나 기본 키의 제약을 어겼다는 뜻입니다. 메시지 형식이 Duplicate entry '값' for key '키 이름'이라 어떤 값이 어떤 제약에 걸렸는지가 그대로 나옵니다. 테이블에 유니크 인덱스가 여러 개면 이 이름으로 구분합니다.
중요한 건 이 오류가 버그일 수도, 정상 흐름일 수도 있다는 점입니다.
- 사용자가 이미 가입된 이메일로 다시 가입 → 정상 흐름. 안내 메시지를 보여 주면 됩니다.
- import 스크립트가 같은 데이터를 두 번 넣음 → 재실행 가능해야 할 코드가 그렇지 못한 것
- 요청이 중복 도착해 같은 행을 두 번 만들려 함 → DB가 막아 준 것. 제약이 제 역할을 한 경우입니다
애플리케이션 코드로 "먼저 조회해서 없으면 넣기"를 하면 두 요청 사이에 틈이 생깁니다(TOCTOU). 유니크 제약이 그 틈을 막는 최후의 방어선이고, 그래서 이 오류는 막아야 할 대상이 아니라 처리해야 할 신호입니다.
해결 방안
- 의도한 중복이면 흡수해서 멱등하게 만듭니다. 좋아요·북마크·신고처럼 "두 번 눌러도 한 번"인 동작이 여기 해당합니다. 이 저장소도 중복 키 오류를 잡아 같은 결과로 되돌립니다.
try {
await db.insert(likes).values({ postId, userId });
return "created";
} catch (error) {
if (isDuplicateKeyError(error)) return "duplicate";
throw error;
}- 드라이버가 오류를 감싸는지 확인합니다. ORM을 거치면 실제 코드가
error.code가 아니라error.cause.code에 있을 수 있습니다. 두 곳을 다 보게 만들어 두면 안전합니다. - 덮어써야 하면 업서트로 바꿉니다. 다만 AUTO_INCREMENT 소비와 유니크 인덱스가 둘 이상일 때의 불확실성은 알고 써야 합니다.
INSERT INTO notes (slug, title) VALUES (?, ?) AS new
ON DUPLICATE KEY UPDATE title = new.title;- 사용자에게는 존재 여부를 흘리지 않습니다. "이미 가입된 이메일입니다"는 계정 존재를 알려 주는 정보이기도 합니다. 서비스 성격에 따라 문구를 정합니다.
- 키 이름을 읽을 수 있게 지어 둡니다.
users_email_unique처럼 테이블·컬럼·종류가 드러나면 로그만 보고 원인을 알 수 있습니다. 자동 생성된 이름에 의존하면 이 오류가 났을 때 스키마를 다시 열어 봐야 합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.