diff --git a/migrations/361_attempt_session_question_unique.sql b/migrations/361_attempt_session_question_unique.sql new file mode 100644 index 0000000..5670886 --- /dev/null +++ b/migrations/361_attempt_session_question_unique.sql @@ -0,0 +1,14 @@ +-- 361: quiz 세션 내 같은 문제 이중 attempt 방지 partial UNIQUE (R9). +-- submit_attempt 의 FOR UPDATE 행잠금이 1차 방어이고, 이 제약은 DB 레벨 belt-and-suspenders +-- (모바일 더블탭/재시도가 어떤 경로로든 이중 INSERT 에 도달해도 차단). prod 실측 중복 0 건 +-- (SELECT ... GROUP BY HAVING count>1 = 0) — dedup DELETE 는 멱등 precaution, UNIQUE 는 안전. +-- quiz_session_id IS NULL(세션 외 직접 입력)은 대상 아님 → partial index. +DELETE FROM study_question_attempts a USING study_question_attempts b +WHERE a.quiz_session_id IS NOT NULL + AND a.quiz_session_id = b.quiz_session_id + AND a.study_question_id = b.study_question_id + AND a.id > b.id; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_attempt_session_question +ON study_question_attempts (quiz_session_id, study_question_id) +WHERE quiz_session_id IS NOT NULL;