27. 낙관적 버전 충돌 재시도 시 재조회(refresh) 누락으로 갱신 유실 지속 (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: 낙관적 잠금(version 컬럼) 기반 잔액 갱신 (C#)
// - player 테이블: (id, balance, version). 동시 갱신 충돌은 낙관적 버전으로 감지한다.
// - UPDATE ... SET balance=@new, version=version+1 WHERE id=@id AND version=@ver
//   가 0행이면 그 사이 누가 커밋한 것이므로 재시도한다.
//
// 요구사항: 동시 충전/차감이 겹쳐도 최종 잔액은 모든 변경을 반영해야 한다
//           (갱신 유실 없음).
//
// 과제: 부하 중 두 요청이 겹치면 한쪽 변경이 사라진다(잔액이 덜 반영됨).
//       충돌 재시도가 도는데도 유실이 지속된다. (A),(B) 를 근거로 원인·수정안을 설명하라.

using System.Data.Common;

public sealed class WalletRepo
{
    private readonly DbConnection _db;
    public WalletRepo(DbConnection db) { _db = db; }

    public void ApplyDelta(int playerId, long delta)
    {
        // 최초 로드
        (long balance, long version) = Load(playerId);
        long newBalance = balance + delta;

        while (true)
        {
            int affected = Exec(
                "UPDATE player SET balance=@b, version=version+1 WHERE id=@id AND version=@v",
                newBalance, playerId, version);

            if (affected == 1)
                return;

            // 충돌: 누군가 먼저 커밋함 → 재시도
            version = LoadVersion(playerId);   // (A) 버전만 새로 읽음
            // newBalance 는 그대로 재사용        // (B)
        }
    }

    // --- 아래는 시나리오용 스텁 ---
    private (long, long) Load(int id) => (0, 0);
    private long LoadVersion(int id) => 0;
    private int Exec(string sql, long b, int id, long v) => 1;
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.