diff --git a/app/api/study_questions.py b/app/api/study_questions.py index 86ed340..fcef421 100644 --- a/app/api/study_questions.py +++ b/app/api/study_questions.py @@ -1009,7 +1009,16 @@ async def submit_attempt( # PR-10: 세션 연동. 기본은 None. quiz_session: StudyQuizSession | None = None if body.quiz_session_id is not None: - quiz_session = await session.get(StudyQuizSession, body.quiz_session_id) + # FOR UPDATE 로 행 잠금 (R9) — 모바일 더블탭/재시도로 같은 세션에 동시 제출이 들어오면 + # 둘 다 cursor=N 을 읽고 둘 다 cursor+1·count 가산하는 race(이중 가산). 잠금으로 직렬화 → + # 두 번째 제출은 첫 commit 후 cursor=N+1 을 보고 cursor 불일치 409 로 거부된다. + quiz_session = ( + await session.execute( + select(StudyQuizSession) + .where(StudyQuizSession.id == body.quiz_session_id) + .with_for_update() + ) + ).scalar_one_or_none() if quiz_session is None or quiz_session.user_id != user.id: raise HTTPException(status_code=404, detail="quiz_session 을 찾을 수 없습니다") if quiz_session.study_topic_id != q.study_topic_id: