Transaction, Optimistic Lock, Pessimistic Lock

권태용·2020년 9월 23일
3

Database

목록 보기
1/3

Day1.
Q1. Transaction은 무엇인가요?
Q2. Lock은 먼가요?(조금 다른 질문이긴한데)

Day2.
Q3. Optimisitc Lock과 Pessimistic Lock을 어떻게 다른가?

이틀에 거쳐 받았던 질문입니다. 역시 연관성이 있는 질문들이였다 생각됩니다. 질문에 대한 답을 찾아가면서 개념을 하나하나 쌓아가보려합니다.

Transaction은 무엇인가요?

트랜잭션은 쿼리문으로 보면 쿼리 집합체라고 생각됩니다. 하지만 이는 트랜잭션을 설명하기에 부족합니다. 가장 중요한 의미는 'All Or Nothing' 즉 각 쿼리의 결과가 모두 성공하거나 하나라도 실패하면 전부 실패처리를
해야 합니다. 이를 다시 정리하면
Transaction이란 '데이터베이스의 상태를 변환시키는 연산들의 결과 값이 전체성공 혹은 전체실패인 연산집합체' 라고 생각됩니다.

Transaction의 성질 ACID

A가 B에게 폰뱅킹으로 돈을 보내는 상황을 예시로 본다면

  1. A = A - money
  2. B = B + money

가 되어야 합니다. 그리고 위 두개의 연산은 하나의 트랜잭션으로 수행됩니다. 이때 ACID 성질을 나타내야 합니다.

Atomicity(원자성) - A계좌에서 돈을 뺏지만 네트워크 오류로 B계좌에 돈을 넣지 못 했다면 2개의 연산중 하나가 실패했기 때문에 모두 실패처리 되서 A계좌에 돈이 빠져나가지 않아야 합니다.

Consistency(일관성) - A계좌의 조건이 잔액은 0보다 커야한다고 할때 A는 B에게 잔액보다 큰 돈을 보낼 수 없습니다.

Isolation(고립성) - A계좌에서 B계좌에 1000원을 보내는 과정중에 A계좌에 누군가 5000원을 넣는다면 A계좌에서 1000원이 빠진뒤에 5000원이 들어가야 합니다. ( 트랜잭션 수행중에 다른트랜잭션이 같은 자원을 공유할 수 없다.)

Durability(영속성) - A계좌에서 B계좌로 돈이 완전히 보내졌다면(Commit되었다면) A는 다시 B에게 돈을 가져갈 수 없습니다.

ACID는 Transaction의 기능(연산결과의 통일성)을 수행하기위해 지켜져야할 원칙이라 생각됩니다. 하지만 완전히 지켜진다고는 볼 수 없을 것 같습니다.

어떻게 하면 Consistency(일관성)과 Isolation(고립성)을 유지 할 수 있을까?

같은 데이터를 수정하는 두개의 트랜잭션 TA와 TB가 있을때 근소한 차이로 TA가 먼저 왔다면 어떻게 TB의 접근을 막을 수 있을까? 이때 Lock의 개념이 등장합니다. OS의 세마포어에서 CPU자원에 Lock을 걸었다면 Database에서는 데이터에 Lock을 걸고 해당 데이터를 읽고 쓰는 트랜잭션은 대기하게 됩니다.

Lock은 먼가요?(조금 다른 질문이긴한데)

그렇기에 Lock은 Transaction를 순차적으로 진행되게 할 수 있는 장치라 생각됩니다. 이러한 Lock의 단위는 Column, Row, Table, Database등으로 걸 수 있고 이에 따라 Transaction의 Isolation Level(고립수준)이 올라갑니다. (이에 대해선 다른 포스팅으로 다뤄볼까 합니다.)

Optimistic Lock과 Pessimistic Lock을 어떻게 다른가?


설명에 앞서 PC라는 테이블의 name을 바꾸는 상황을 가정하고
Transaction T1과 T2가 있고 각 트랜잭션은 PC의 이름을 "mac"과 "window"로 바꾸려 합니다.
이때 Transaction 시작과 Commit 시간이 다르다면 PC의 이름은 mac일까 윈도우 일까? 이는 트랜잭션 처리 방식에 따라 다릅니다. Optimistic 방법으로 먼저 생각해 보겠습니다.

Optimistic Lock으로 처리하기

우선 Optimistic Lock 방법은 commit시에 부수적인 컬럼 데이터 값을 확인하고 업데이트하여 결과를 반영합니다. 때문에 커밋을 하기전까지는 트랜잭션의 충돌 상황을 알 수 없습니다.

위 상황을 Optimistic Lock방법으로 처리하기 위해선 PC에 V(version)이라는 컬럼을 추가하여야 합니다.

TS2가 먼저 Commit되면서 name이 'window'가 되고 V값이 2로 변합니다. 그러면 TS1이 커밋 시점에 PC의 V는 1이 아니기 때문에 변경 사항은 없습니다.

Pessimistic Lock으로 처리하기

Pessimistic Lock은 Update쿼리를 시작하면서 데이터에 락을 걸어 해당 데이터를 참조하려는 트랜잭션을 대기 상태로 만듭니다.

위 상황에선 TS1이 먼저 Update를 하면서 PC에 대한 Lock을 걸었기 때문에 TS2는 대기상태에 빠지게 됩니다. 이때 TS1이 name을 mac 바꾸고 lock을 풀게 되면서 TS2는 name을 window로 덮어쓰게 됩니다.

이 부분을 이해하는데 시간이 걸렸습니다... Workbench로 테스트를 좀 더 빨리 했다면 시간을 줄였을 텐데;;

테스트 방법은 아래 PC테이블을 생성하고

CREATE TABLE `pc` (
  `id` int(11) NOT NULL,
  `name` varchar(45) DEFAULT NULL,
  `version` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Workbench를 두개 띄운뒤 (https://stackoverflow.com/a/39964335 참조)

# TS1					#TS2
set autocommit = 0;			set autocommit = 0;    
start transaction;			start transaction;
					# PC에 락걸기
					select * from pc for update;
update pc set name = 'mac';         	# UPdate
commit;                                	update pc set name = 'window';
					commit;

각 쿼리를 실행하면서 테스트를 진행합니다. TS2쿼리에서 select for update 쿼리를(mysql에서 지원하는 lock기능) 통해 락을 걸고
TS1의 update 쿼리를 실행하면 pending상태가 됩니다. 이때 TS2를 Update후에 Commit하면 name이 window로 바뀜과 동시에 TS1의 Update 쿼리의 pending 상태가 풀리면서 name을 mac으로 덮어쓰게 됩니다.

이러한 문제를 SecondLostUpdatesProblem(이중 갱신 분실)이라고 합니다.

Optimistic Lock, Pessimistic Lock 중 어떤걸 써야 할까?

어떤 로직에서 이를 활용하느냐에 따라 다른것 같다. 위에 예시는 PC의 이름을 바꾸는 상황이기 때문에 첫 번째 업데이트가 보존 되는것이 맞다고 생각 될 수 있다. 하지만 계좌의 돈을 처리하는 로직이라면 순차적으로 처리하기에 이는 적합한 방법이라 볼 수도 있다. 왜냐하면 늦게 온 트랜잭션에 대해서도 처리가 가능하기 때문이다.

# TS1					#TS2
Select Money from Account for update
Update Account money = money + 3000 	Update Account money = money - 2000
profile
개발일기장

1개의 댓글

comment-user-thumbnail
2021년 4월 28일

잘 보고 갑니다. ㅎㅎ 좋은 글이에요

답글 달기