읽기 격리 수준이 'read committed'인 경우, '반복된 읽기'가 불가능합니다.
각각의 독립된 트랜잭션에서, 다른 트랜잭션의 커밋 여부가 현재 트랜잭션의 읽기에 영향을 미치기 때문입니다.
이러한 반복적인 읽기가 불가능한 상황은 데이터의 정합성을 보장하지 못하는 결과를 초래할 수 있는데, 이는 서비스의 성격에 따라 심각한 문제를 발생시킬 수 있습니다.
그러나 이러한 read committed의 특징을 활용하면, 이중 예약과 같은 lost update 문제를 해결할 수도 있습니다.
즉, read committed 격리 수준은 데이터 정합성과 성능 간의 균형을 잡는 데 활용될 수 있습니다.
열 구성이 다음과 같은 seats 테이블이 있습니다. 이 테이블에서 이중 예약 문제를 발생시켜보도록 하겠습니다.
두 개의 터미널을 사용해 동시에 트랜잭션을 실행하고, 먼저 첫 번째 터미널(이하 a터미널) 에서 다음과 같이 업데이트를 진행합니다.
postgres=# begin;
BEGIN
postgres=*# update seats set is_booked = '1', name = 'a' where id = 1;
UPDATE 1
b 터미널에서도 다음과 같이, 같은 열의 좌석 번호를 예약하는 업데이트를 진행합니다.
postgres=# begin;
BEGIN
postgres=*# update seats set is_booked = '1', name = 'b' where id = 1;
a 터미널에서 진행중인 트랜잭션이 업데이트를 통해 먼저 락을 획득했기 때문에, b 터미널에서 수행중인 트랜잭션은 락을 획득할 때 까지 대기하게됩니다.
다음으로 a 터미널에서 commit을 진행합니다.
postgres=*# commit;
COMMIT
a 터미널에서 커밋 후, b 터미널에서 락을 획득하게 됩니다.
UPDATE 1
이 후 b 터미널에서 commit하게 되면, 예상하셨듯 lost update 문제가 발생합니다.
a 터미널에서 업데이트된 내역은 b 터미널에서의 업데이트로 인해 덮어씌워지게되며,
해당 업데이트 내역은 영구적으로 잃게됩니다.
이런 상황을 대처하기 위해 SELECT ... FOR UPDATE를 사용해, 조회 시점에 배타적 락을 얻거나, 낙관적 락 등을 사용해서 동기화 시켜주는데,
격리 수준 read committed의 특성을 사용해서 한 번 lost update를 대처해보도록 하겠습니다.
postgres=# begin;
BEGIN
postgres=*# update seats set is_booked = '1', name = 'a' where id = 1 and is_booked = '0';
UPDATE 1
다시 한 번 a 터미널, b 터미널에서 동시에 트랜잭션 실행 후, a 터미널에서 먼저 업데이트를 진행해줍니다.
여기서 update 쿼리에서 달라진 것은 is_booked 컬럼의 값이 '0'인 것을 and 조건으로 필터링 해줬다는 것인데, 추가한 이유는 이후에 설명하도록 하겠습니다.
postgres=# begin;
BEGIN
postgres=*# update seats set is_booked = '1', name = 'b' where id = 1 and is_booked = '0';
마찬가지로 b 터미널에서도 같은 행에 대한 업데이트를 진행해주면, 락을 획득하지 못해 대기하는 것을 볼 수 있습니다.
a 터미널에서 커밋을 진행해보도록 하겠습니다.
postgres=*# commit;
COMMIT
커밋으로 인해 해당 행에 대한 락이 풀렸을테니, b 터미널을 보면 마찬가지로 해당 행이 업데이트 되며 lost update가 발생할 것을 예상할 수 있는데, 결과는 다음과 같습니다.
UPDATE 0
postgres=*# select * from seats;
id | name | is_booked
----+------+-----------
2 | | 0
3 | | 0
4 | | 0
1 | a | 1
(4 rows)
name이 b로 업데이트 되지 않았습니다. 이유가 뭘까요?
이유는 바로 read committed 격리 수준에서는 '반복된 읽기'가 불가능하기 때문입니다.
b 터미널에서 수행 중인 트랜잭션에서 update 구문을 실행했을 때, a 터미널의 트랜잭션에서 락이 이미 획득된 상태였기 때문에 해당 행에 접근할 수 없었습니다.
a 터미널에서 트랜잭션이 정상적으로 수행되고 commit 된 후에야 b 터미널에서 락을 획득할 수 있었습니다.
b 터미널에서의 트랜잭션 격리 수준은 'read committed' 이기 때문에, a 터미널에서 커밋된 결과를 b 터미널에서 읽을 수 있었습니다.
결과적으로 b 터미널에서는 is_booked 조건이 0에 해당하는 행을 가져올 수 없었고, lost update 문제가 해결되었습니다.
이와 같이 a 터미널의 트랜잭션이 먼저 수행되고 커밋된 후에야 b 터미널에서 락을 획득할 수 있었고, 이를 통해 데이터의 일관성이 유지되었습니다.
'DB' 카테고리의 다른 글
[Database] B tree가 데이터베이스 인덱스로 사용되는 이유 (1) | 2024.06.12 |
---|---|
[Database] 함수적 종속성을 활용한 테이블 정규화 과정 (0) | 2024.06.10 |
[Database] 함수적 종속성 (Functional Dependency) (1) | 2024.06.07 |
PostgreSQL, MySQL 에서의 Lost update 대처 방안 (0) | 2024.06.06 |
RDBMS에서의 MVCC(MutliVersion Concurrency Control) 도입 배경과 원리 + 예제 (1) | 2024.06.06 |