Lost Update(갱신 손실)
Lost Update는 Committed Read 와 보다 낮은 수준의 격리 수준에서 나타나는 현상으로 하나의 DB 값에 대해 두가지 이상의 쓰기가 발생 했을 때 마지막의 쓰기만 반영이 되어 그 전의 쓰기는 사라지는 현상이다.
그림 1-1에서 A가 조회(read)를 하고 갱신(modify-write)하기전 B도 잔고를 조회(read)하고, 그 후 A가 300을 추가(modify-write)해도 마지막에 B가 100을 추가(modify-write)하면 최종적으로 잔고는 600이 되고 중간에 A가 갱신한 800은 사라지게 된다. 이러한 현상을 lost update라고 한다.
lost update를 해결 할 수 있는 방법은 여러 가지가 있다.
원자적 쓰기 연산
2-2와 같은 방법은 read-modify-write와 같이 여러 단계를 거치는 수정 방법이다. 많은 데이터 베이스는 다음과 같이
UPDATE acount SET money = money + 100 WHERE id = 1
read-modify-write를 UPDATE라는 하나의 연산으로 처리해주며 적절한 잠금을 걸어준다(대부분의 경우 row 수준의 잠금을 획득한다.)
명시적 작금
read-modify-write 주기의 트랜젝션에서 트랜잭션 시작시 row 혹은 table을 명시적으로 잠금을 획득하고 read-modify-write를 진행하면 lost update를 막을 수 있다.
Lost Update 자동 감지
특정 DB들은 lost update가 일어나면 자동으로 감지해 트랜잭션을 abort해주는 기능이있다.(Oracle Serializable, PostgreSQL repeatable read, MSSQL snapshot 등등)
Compare-and-set
이 방법은 갱신을 하기 전에 read한 값과 현재 DB의 값에 변경이 없을 때만 갱신을 해주는 방법이다.
Write Skew
Phantom Read
어떤 트랜잭션에서 실행 한 쓰기 결과가 다른 트랜잭션의 검색 쿼리 결과를 바꾸는 것을 팬텀이라 한다.
쓰기 스큐는 이러한 팬텀 현상 때문에 생기는 동시성 문제이다.
특정 조건에서만 쿼리가 동작할 수 있을 때, A와 B라는 2개의 트랜잭션이 조회를 통해 조건을 만족하고 있다고 알고있다. 이런 상황에서 A가 쿼리를 실해하면서 B는 쿼리를 실행 할 수 없는 조건이 되었지만, 이전에 만족한 조건이라고 알고 있기에 B또한 쿼리를 실행버린다. 이것은 명백히 원하던 결과가 아니며 이런 현상을 Write Skew라고 한다.
예를 들자면 21시에 예약 할 수 있는 회의실이 있다. A 와 B가 회의실 예약 사이트에 동시에 접속한다. A 와 B가 동시에 21시에 예약을 한다면 하나의 시간에 하나의 회의실에 2명이 예약을 해버린 결과가 나오게 된다.
이러한 경우에는 21시의 회의실에 잠금을 걸면 된다. 하지만 21시의 회의실은 DB에 존재하지 않고 예약을 하는 순간에 DB에 기록되는 구조라면 이야기가 달라진다. 그렇다면 테이블에 대한 잠금을 얻을 수 없다. 이러한 경우에는 "충돌 구체화"를 통해서 문제를 해결 할 수 있다.
충돌 구체화
데이터 저장용이 아닌 DB에 락을 걸기위한 테이블을 만든다. 예를 들자면 00시 부터 24:00 까지의 테이블을 만들고 회의실을 예약하기 전에 그 시간에 해당하는 테이블에 잠금을 미리 획득 해야 한다.
이 Write Skew에는 다음과 같은 상황도 예시로 들 수 있다.
야간 근무를 서는 회사에서는 본인을 제외하고 회사에 남아 있는직원이 1명 이상이라면 퇴근을 할 수 있다. A와 B 2명이 회사에 남아 있을 때 두사람이 동시에 퇴근을 하기 위해 조건을 확인해 보면 두사람 모두 퇴근을 할 수 있는 상태에서 두명이 동시에 퇴근을 하게 된다면, 테이블에 잠금을 걸어도 두 트랜잭션이 다른 로우를 잠그기 때문에 두 명이 모두 퇴근을 해서 회사에는 0명이 남아있는 상태가 될 수 있다.
이러한 문제는 Serializable로 해결 할 수 있다.
Serializable(직렬성)
Serializable 수준의 격리 단계 에서는 "서술 잠금" 과 "색인 범위 잠금"을 통해서 Phantom Read를 막는다.
Predicate Locking(서술 잠금)
서술 잠금은 Lock을 특정 객체(특정 row)에 잡는 것이 아니라
SELECT * FROM member
WHERE status = True
위 와 같이 특정 조건에 부합하는 모든 객체(row)에 잠금을 거는 것이다. 이렇게 되면 동시에 접근하려고 할 때 조금이라도 먼저 객체에 접근한 트랜잭션이 잠금을 획득하게 되고 두번째 트랜잭션은 서술 잠금을 획득할 때 바뀐 조건을 보게 된다.
Index-Range Locking(색인 범위 잠금)
진행 중인 트랜잭션들이 획득한 잠그이 많으면 조건에 부합하는 잠금을 확인하는 데 시간이 오래걸리다. 따라서 Index-Range Locking은 직접 적인 DB 객체에 잠금을 거는 것이 아니라 Index에 바로 잠금을 걸어 서술 잠금보다 큰 범위를 잠글 수 있지만, 오버헤드가 훨신 적게 발생한다.
'computer science > DB' 카테고리의 다른 글
#2 Redis Pub/Sub (0) | 2024.08.12 |
---|---|
[데이터 중심 애플리케이션 설계] 7장. Isolation Level (0) | 2024.06.30 |
하나의 트랙잭션에서 insert 다음에 나오는 select 쿼리는 insert된 결과를 가지고 있을까? (0) | 2024.06.23 |
[데이터 중심 애플리케이션 설계] ACID에서 애플케이션과 DB의 책임 분리 (1) | 2024.06.23 |
#1 Redis type (0) | 2024.03.04 |