본문으로 건너뛰기
개발 머꼬
개발 노트SQL
hohyeon.dev19

Duplicate entry ... for key가 났을 때 어느 제약이 걸렸는지 찾는 법

  • #Debugging
  • #Engineering Note
  • #SQL

문제 발생

가입 처리에서 간헐적으로 오류가 났습니다.

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). 유니크 제약이 그 틈을 막는 최후의 방어선이고, 그래서 이 오류는 막아야 할 대상이 아니라 처리해야 할 신호입니다.

해결 방안

  1. 의도한 중복이면 흡수해서 멱등하게 만듭니다. 좋아요·북마크·신고처럼 "두 번 눌러도 한 번"인 동작이 여기 해당합니다. 이 저장소도 중복 키 오류를 잡아 같은 결과로 되돌립니다.
try {
  await db.insert(likes).values({ postId, userId });
  return "created";
} catch (error) {
  if (isDuplicateKeyError(error)) return "duplicate";
  throw error;
}
  1. 드라이버가 오류를 감싸는지 확인합니다. ORM을 거치면 실제 코드가 error.code가 아니라 error.cause.code에 있을 수 있습니다. 두 곳을 다 보게 만들어 두면 안전합니다.
  2. 덮어써야 하면 업서트로 바꿉니다. 다만 AUTO_INCREMENT 소비와 유니크 인덱스가 둘 이상일 때의 불확실성은 알고 써야 합니다.
INSERT INTO notes (slug, title) VALUES (?, ?) AS new
ON DUPLICATE KEY UPDATE title = new.title;
  1. 사용자에게는 존재 여부를 흘리지 않습니다. "이미 가입된 이메일입니다"는 계정 존재를 알려 주는 정보이기도 합니다. 서비스 성격에 따라 문구를 정합니다.
  2. 키 이름을 읽을 수 있게 지어 둡니다. users_email_unique처럼 테이블·컬럼·종류가 드러나면 로그만 보고 원인을 알 수 있습니다. 자동 생성된 이름에 의존하면 이 오류가 났을 때 스키마를 다시 열어 봐야 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.