• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

lock 이 걸리는 시점에 대한 의문

23.03.06 19:51 작성 23.03.06 19:55 수정 조회수 581

0

강의를 듣던중 궁금한 점이 생겨 질문드립니다.

아래의 코드는 MemberServiceV2의 두 함수 입니다.

public void accountTransfer(String fromId, String toId, int money) throws SQLException {

        Connection con = dataSource.getConnection();

        try {
            con.setAutoCommit(false); // ! 트랜잭션 시작

            // * 비즈니스 로직
            bizLogic(fromId, toId, money, con);

            con.commit(); // * 성공시 commit

        } catch (Exception e) {
            con.rollback(); // ! 실패시 rollback
            throw new IllegalStateException(e);
        } finally {
            release(con);
        }

    }

private void bizLogic(String fromId, String toId, int money, Connection con) throws SQLException {
        Member fromMember = memberRepository.findById(toId, con);
        Member toMember = memberRepository.findById(toId, con);

        ---<다른 db 커넥션에 의해 침범될 수 있는 구간>---

        memberRepository.update(fromId, fromMember.getMoney() - money, con);
        validation(toMember);
        memberRepository.update(toId, toMember.getMoney() + money, con);
    }

트랜잭션을 사용하기 위해서 con.setAutoCommit(false) 를 사용하고 있는데,

강의를 듣고 이해한 내용에 의하면, accountTransfer에 의해서 db에 요청되는 sql 쿼리는 총 4개입니다.

memberRepository.findById -> select 문 (2번)

memberRepository.update -> update 문 (2번)

그런데, select for update가 아닌 select문은 선택된 row에 대해 lock을 걸지 않기 때문에, 위의 코드에 적어놓은 것 처럼 update문이 수행되기 전에 다른 db 세션에 의해서 동일한 데이터가 수정될 여지가 있는 것 같아 보입니다.(update 문이 수행될 때 lock이 걸리는게 맞다면)

그럼, findById 함수 내의 sql문을 select for update로 변경하거나, 코드에 적어놓은 select문과 update 문 사이에 또다른 lock을 설정해주어야하는 건가요? + 만약, 또다른 lock을 설정해야한다면 어떻게 해야하나요?

 

P.S

https://www.inflearn.com/questions/653523

아래의 유사한 질문을 발견했는데, 해당 질문의 답변만으로 충분히 이해가 되지 않아서 추가적으로 질문드린 것입니다.

답변 1

답변을 작성해보세요.

0

codesweaver님의 프로필

codesweaver

2023.03.08

안녕하세요, 세승 님! 공식 서포터즈 codesweaver 입니다.

동시성에서 완전히 보호해야 하는 데이터라면 트랜잭션 격리 레벨을 최대(Serializable)로 설정하는 방법을 고려할 수 있습니다. 그러면 한 트랜잭션이 고객의 예금에 접근하면, 다른 트랜잭션은 이 예금에 접근이 불가합니다.

웹 쇼핑몰에서는 이처럼 극단적인 격리를 사용해본적은 없습니다만, 은행권 처럼 민감한 데이터 격리가 필요하다면 고려할 수 있을것 같습니다.



감사합니다.

세승님의 프로필

세승

질문자

2023.03.09

안녕하세요. 답변 감사드립니다.

적어주신 답변으로는 해결이 안되는 의문이 있어 다시 질문드립니다.

'답변에서 웹 쇼핑몰에서는 이처럼 극단적인 격리를 사용해본적이 없다' 라고 답변해주셨었는데, 아래의 예시상황이 갑자기 떠올라서 질문드립니다.

예시)

고객 : A , B (2명) , 물품 : P (재고 5개)

<시나리오>
1. 고객 A가 P를 3개를 구매하려고 주문
2. select 문을 통해서 재고가 몇개 남아있는지 확인 (재고 5개)
3. A가 주문을 시도 (실제 재고 5개에 구매한 갯수 3개 차감하는 행위)
3-1. A가 주문을 완료하기 전에, B가 P를 3개 구매하기 위해 재고 갯수 확인
4. A의 주문완료로 재고가 2개로 변경
5. B는 재고가 5개인줄 알고 3개를 주문하려 했으나, 실제 재고가 2개로 변경되어 주문 불가

그럼 이 경우에는 A가 주문을 완료(db상에서는 update 쿼리)하기 전에, B가 재고 갯수를 확인(db상에서 select 쿼리)하는 것을 허용했기 때문에, B에는 업데이트 전 값이 5개가 들어가게 되는데요. 이 경우에서 A가 재고를 확인할 때 select for update를 사용하여 lock을 이용해 물품의 데이터를 접근하지 못하게 할 필요는 없나요?

 

 

 

codesweaver님의 프로필

codesweaver

2023.03.09

안녕하세요 세승님!

사이트 동시접속자가 매우 많은 상황(1 만 이상)이라면 말씀하신 방법을 고려할 것 같아요. 보통 구매 프로세스는 매우 긴 프로세스 이기에 수십, 수백 단위의 동접자는 동시성을 고려하지 않아도 괜찮은 수준입니다.

 

특히 상품 재고와 결제 금액은 구매 프로세스 안에서 여러번 유효한지 확인합니다. 가령 결제페이지 진입 시, 결제 완료 후 등의 포인트에서 반복 확인하고, 보통은 이정도면 동시성 문제가 발생하지 않습니다.

 

그리고 어떤 경우에는 고객이 주문 페이지로 진입하면, 재고를 미리 분리해 놓은적도 있습니다. 상품재고가 10개고 고객이 3개를 구매하려는 경우, 이 고객이 3개를 구매할 수 있는지 확인하고, 가능하다면 3개를 임시재고로 빼놓는 방식을 사용하기도 했습니다. 재고를 하나로 관리하는 것이 아닌 가용재고, 임시재고 등으로 분리하여 관리하여 동시성 문제를 피해가는 방법도 있습니다.

 

감사합니다.

세승님의 프로필

세승

질문자

2023.03.09

빠르고 친절한 답변 감사합니다.

그럼 콘서트 티켓팅 서비스 같은 곳에서는 select for update 같은 쿼리를 종종 사용하기도 하겠네요! :)