Transaction
isolation level
태그 기능 추가 및 해당 단어가 들어간 경매 게시글 검색.
WebSocket을 이용한 실시간 채팅을 구현하고 실시간으로 테이블을 조작하는 부분(호가 및 채팅 기록 저장)이 있어서 사용자 별로 정확한 데이터를 Response해 주고 싶었다.
PM님께서 동시성 및 isolation level과 관련해서 테이블을 다루는 Transaction을 공부해보는 것이 좋을 것 같다는 말씀을 듣고 이렇게 정리 한다.
--> 트랜잭션은 사용자로 하여금 자신이 정확한 데이터를 입출력 할 수 있다는 확신을 주게 하는 중요한 부분이다.
쿼리를 하나로 처리해서 만약 중간에 실행이 중단됐을 경우, 최종 커밋 시점으로 되돌리는 Rollback 을 수행하고, 오류없이 마치면 Commit 을 실행하는 단위이다.
커밋(COMMIT) : 트랜잭션을 메모리에 영구적으로 저장
롤백(ROLLBACK) : 오류가 발생했을 때, 오류 이전의 특정 시점 상태로 되돌려주는 제어어
체크포인트(CHECKPOINT) : 롤백을 위한 시점을 지정
⦁ 원자성(Atomicity) : 분해가 불가능한 작업의 최소단위, 연산 전체가 Commit 또는 Rollback이 반드시 되어야 한다.
⦁ 일관성(Consistency) : 트랜잭션이 실행 성공 후 항상 일관된 데이터베이스 상태를 보존해야 한다.
트랜잭션이 성공적으로 수행된 후에도 데이터베이스가 일관성 있는 상태를 유지해야 한다. 여기서 말하는 일관성이란, 크게 2가지 의미를 가진다.
첫 번째로, 트랜잭션이 커밋되면 데이터 베이스에 적용한 제약조건(PRIMARY KEY, UNIQUE, NOT NULL 등)을 위반하지 않는다는 보장을 의미한다.
→ 예시) 사용자가 번호를 저장하려고 하는데, 이 번호에 UNIQUE 제약이 설정되어 있으면 중복되니 사용자 번호를 저장할 수 없어야 한다.
두 번째로, 트랜잭션의 작업이 애플리케이션에서 의도하고자 한 작동이 정상적으로 일어난다는 보장을 의미한다.
→ 재고가 떨어졌을 때, 더 이상 판매를 할 수 없도록 제한해야 한다.
⦁ 격리성(Isolation) : 트랜잭션 실행 중 연산의 중간 결과를 다른 트랜잭션이 접근할 수 없어야 한다.
→ 아래에 후술 함.
⦁ 영속성(Durability) : 성공 완료(Commit)된 트랜잭션의 결과는 영속적으로 데이터베이스에 저장되어야 된다.
다수 사용자 환경에서 여러 트랜잭션을 수행할 때, 데이터베이스의 일관성 유지를 위해 상호작용을 제어하는 기법
⦁ 로킹(Locking) : 일관성과 무결성을 유지하기 위한 트랜잭션의 순차적 진행을 보장하는 직렬화 기법
⦁ 낙관적 검증 : 일단 트랜잭션을 수행하고, 트랜잭션 종료 시 검증을 수행
⦁ 타임 스탬프 순서 : 타임 스탬프를 부여해 부여된 시간에 따라 트랜잭션 수행
⦁ 다중버전 동시성 제어(MVCC) : 타임스탬프를 비교해 직렬가능성이 보장되는 적절한 버전을 선택해 접근하도록 함
왜 격리 수준이 있는가?
⦁ 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미한다.
⦁ 예를 들면, 부산의 지정 호텔에 남은 싱글룸 수가 10개였을 때 실제로 숙박하는 로직은 다음과 같다.
현재 빈 싱글룸의 수를 확인한다(SELECT)
빈 싱글룸 수에서 1을 빼고 결과를 빈 싱글룸 수로 되돌려 쓴다(UPDATE)
이것을 사용자 A와 사용자 B가 동시에 수행하면 어떻게 될까? 2명이 방을 확보한다면 원래 빈 싱글룸 수는 2개가 줄어야 하지만, 같은 방을 동시에 확보하게 되면 빈 싱글룸 수는 1개만 줄어들게 된다.
⦁ 이런 사태가 발생하는 것을 막기 위해, 데이터베이스에는 테이블에 대해 Lock(잠금)을 걸어서 후속처리를 Block하는 방법이 있다.
⦁ 잠금 단위에는 테이블 전체, 블록, 행 등이 있는데, MySQL에서는 트랜잭션 처리를 할 때 주로 행 단위의 잠금 기능을 이용한다.
⦁ 예를 들어, 앞의 두 가지 중 ‘1. 현재 빈 싱글룸 수를 확인한다’를 처리할 때 ‘SELECT ~ FOR UPDATE’를 실행하면 SELECT한 행에 잠금이 걸린다.
⦁ 이렇게 되면 후속 처리는 해당 잠금이 해제될 때(COMMIT 또는 ROLLBACK)까지 대기하게 되며 올바른 처리를 계속할 수 있게 된다.
사용하는 DB의 디폴트 격리 단계를 따른다. mysql의 경우REPEATABLE_READ이다.
가장 낮은 단계로 트랜젝션에 처리중이거나, 아직 commit 되지 않은 데이터를 다른 트랜젝션이 읽는 것을 허용한다. 데이터베이스의 일관성을 유지할 수 없기 때문에 Dirty Read가 발생한다.
UPDATE, INSERT 반영 전에 데이터를 읽어 생기는 오류이다.
⦁ 트랜잭션 1이 시작한다.
⦁ 트랜잭션 2가 시작한다.
⦁ 트랜잭션 1이 데이터를 수정 후 UPDATE 혹은 INSERT를 진행한다.
⦁ 트랜잭션 2가 UPDATE 혹은 INSERT 된 데이터를 읽는다.
⦁ 트랜잭션 1에 문제가 생겨 롤백을 진행한다.
⦁ 그러나 트랜잭션 2는 여전히 UPDATE 혹은 INSERT 가 된 상태의 데이터를 인식하고 있다.
⦁ Dirty Read 현상을 방지할 수 있다.
⦁ 트랜젝션이 수행되는 동안 다른 트랜젝션은 접근할 수 없고 대기 상태가 된다.
⦁ commit 된 트랜잭션에 대해서만 조회가 가능하다.
⦁ Non-Repeatable Read가 발생한다.
⦁하나의 트랜잭션이 같은 값을 조회할 때 다른 값이 조회되는 현상이다. 커밋된 데이터만 읽어오기 때문에 발생하는 현상이다.
⦁ 트랜잭션 1이 시작한다.
⦁ 트랜잭션 1이 Id=1인 데이터의 값을 A에서 B로 변경한다.
⦁ 트랜잭션 2가 시작한다.
⦁ 트랜잭션 2가 Id=1인 데이터를 조회하면A로 조회된다.
⦁ 트랜잭션 1이 커밋하고 종료한다.
⦁ 트랜잭션 2가 Id=1인 데이터를 다시 조회하면 B로 조회된다.
⦁ 트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회횐다. 즉 조회한 데이터 내용의 동일함을 보장한다.
⦁ 다른 사용자는 트랜잭션 영역에 해당되는 데이터에 대한 수정이 불가능하다.
⦁ 처음 트랜잭션이 시작되면 처음의 데이터에 대해 스냅샷을 남기고, 이후에 값을 변경 후 조회해도 처음에 남긴 스냅샷에서 데이터를 가져와 데이터의 동일함을 보장해준다.
⦁ 레벨 2의 격리 단계에서는 UPDATE한 데이터에 관해서는 정합성을 보장하지만, INSERT/DELETE에 대해서는 보장하지 않는다. 때문에 Phantom Read 문제가 발생한다.
⦁ 이름처럼 있던 데이터가 사라지거나 없던 데이터가 생기는 현상을 뜻한다.
⦁ 트랜잭션 1이 시작한다.
⦁ 트랙잰셕 1이 Id=1(value = A)인 값을 조회한다.
⦁ 트랜잭션 2가 시작한다.
⦁ 트랜잭션 2가 Id=1인 데이터를 B로 변경한다.(UPDATE)
⦁ 트랜잭션 1이 Id=1인 데이터를 조회하지만 데이터는 여전히 A로 나온다.
⦁ 트랜잭션 2가 Id=2(value = C)인 데이터를 삽입한다.(INSERT)
⦁ 트랜잭션 2가 커밋 후 종료한다.
⦁ 트랜잭션 1이 Id=2인 데이터를 조회하면 C를 확인할 수 있다.
⦁ 가장 강력한 격리 수준으로 데이터의 일관성을 완벽하게 보장한다.
⦁ 그만큼 동시 처리 성능이 가장 떨어진다.
⦁ 위에서 발생한 현상들은 하나도 발생하지 않는다.
⦁ 트랜잭션이 특정 테이블을 읽으면 다른 트랜잭션은 그 테이블의 데이터를 변경, 수정할 수 없다.
⦁ isolation level이 올라갈 수록 정확성과 신뢰성이 상승하나, 처리 속도가 낮아진다.
⦁ 우리가 구현할 목적에 맞게 isolation level에 따른 트랜잭션 처리를 해주자.
DB와 관련해서 트랜잭션이 굉장히 중요하다는 사실을 알게되었다.
jpa 쿼리메서드가 생각보다 잘되어있다는 것을 알게되었다.