MySQL - Duplicate entry
오류 내용
1
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '...' for key 'PRIMARY'
특정 로직을 수행하는 과정에서 위의 오류가 발생했다. 오류가 난 테이블 (A_LOG) 은 아래와 같은 구조를 가진다.
USER_ID (PK) | DATE (PK) | OTHER |
---|---|---|
1 | 2025-05-01 11:11:11 | A |
2 | 2025-05-02 10:22:44 | B |
3 | 2025-05-03 08:33:11 | C |
USER_ID - 복합키이며 사용자의 고유값을 가진다.
DATE - 복합키이며, 데이터가 생성한 날짜를 가리킨다.
OTHER - 외의 데이터를 뭉뚱그렸다.
오류 발생 원인
정상 로직
- 사용자가 A 버튼을 누른다.
- A 버튼을 누른 사용자의 정보를 DB에서 호출한다.
- 누가, 언제 A 버튼을 눌렀는지를 기록하기 위해 A_LOG 테이블에 데이터를 INSERT 한다.
- 화면에 A 버튼을 누른 사용자의 정보를 출력한다.
오류 로직
- 사용자가 A 버튼을 누른다.
- A 버튼을 누른 사용자의 정보를 DB에서 호출한다.
- A_LOG 테이블에 데이터를 INSERT 한다.
- 오류가 발생하고 값이 반환되지 않고 데이터도 INSERT 되지 않다.
즉, 데이터가 INSERT 될 때 오류가 반환되는 것을 확인할 수 있었다.
오류 발생 이유
처음에는 왜 중복 키가 발생하는지 정확히 알기 어려웠다. DATE의 값은 초 단위였고, 사용자의 버튼 클릭 이벤트가 저장 동작을 발생시키는 구조였기 때문에, 겉보기에는 중복이 생길 이유가 없어 보였다.
하지만 서버 응답이 지연되는 상황에서 사용자가 버튼을 여러 차례 눌렀을 경우, 지연이 풀렸을 때 해당 요청이 여러 번 실행되기 때문에 같은 초 안에 여러 개의 데이터가 반복 생성될 수 있다는 것을 알게 되었다.
- 사용자가 A 버튼을 누른다.
- 서버가 지연되어 응답이 바로 반환되지 않는다.
- 사용자가 자신의 요청이 누락되었다고 판단, A 버튼을 한 번 더 누른다.
- 서버 지연이 풀렸을 때, 동일한 요청이 서버에 전달된다.
- INSERT를 두 요청이 동시에 시도 -> KEY가 초 단위이기 때문에 충돌이 발생하여 모두 실패한다.
- 로직이 실패한다.
오류 해결 방법
내 경우, 데이터를 INSERT할 때 밀리초까지 입력되도록 수정했다. 사용자가 버튼을 연타하더라도 요청 간에는 밀리초 차이가 생기기 때문에 같은 키가 두 번 생성될 일이 적을 것이라고 판단했기 때문이다.
수정한 쿼리는 아래와 같다.
1
2
3
4
5
6
7
INSERT INTO A_LOG (
USER_ID, DATE, OTHER
) VALUES (
#{USER_ID},
CONCAT(DATE_FORMAT(sysdate(), '%Y%m%d%H%i%s'), LEFT(MICROSECOND(NOW(3)), 3))
,#{OTHER}
)
마이크로초까지를 넣을까 고민했지만 단순 로그 테이블이다보니 데이터가 많이 적재될 것 같기도 하고, 자주 발생하는 오류도 아니고, 정밀한 요청 순서를 보장할 필요가 적다고 판단해서 밀리초로 결정했다. 만약 마이크로초를 넣을 거라면 아래와 같이 쿼리를 수정하면 된다.
1
2
3
4
5
6
7
INSERT INTO A_LOG (
USER_ID, DATE, OTHER
) VALUES (
#{USER_ID},
NOW(6) // CURRENT_TIMESTAMP(6)을 사용해도 된다.
,#{OTHER}
)
마이크로초까지 저장하더라도 성능 차이는 크지 않으며 정확성이 좀 더 올라간다. 또한, 마이크로초 저장은 MySQL 표준 기능이라 함수 하나로 간단하게 저장할 수 있다.
결론
이번 오류는 복합키 중 DATE 컬럼이 초 단위까지만 기록되어, 서버 지연 상황에서 사용자가 동일 요청을 중복 클릭할 경우 키 충돌이 발생해 Duplicate entry 오류가 난 것이 원인이었다.
이를 해결하기 위해 DATE 컬럼의 시간을 밀리초 단위까지 확장했고, 키의 고유성과 중복 키 충돌 문제를 방지할 수 있었다.
마이크로초 단위까지 확장할 수도 있으나, 로그 테이블 특성 상 데이터가 많고 정밀도가 크게 중요하지 않아서 밀리초 단위가 적절한 선택이라고 판단했다.
회고
- 초 단위가 완전히 고유한 값임이 아닌 것을 알았다.
- 서버 지연 시 사용자의 요청이 중복으로 들어갈 경우, 위와 같은 오류가 발생할 수 있음을 알았다.
- 밀리초와 마이크로초의 적절한 사용 기준을 고민하는 시간을 가질 수 있었다.