Transaction (트랜잭션)

박재하·2023년 11월 20일
0

트랜잭션

Jim Gray

  • 이 사람이 최초로 트랜잭션이라는 개념을 제안함
  • 1070쪽 분량의 책 내내 트랜잭션에 대해서만 얘기함. 관계형 DB가 트랜잭션을 잘 support해주긴 하지만 직접적으론 상관없다.
  • 이 책을 당연히 읽을 건 아니니 DB 전공서적 내 Transaction 챕터 50~60페이지 정도 분량정도는 꼭 읽어주세요. 아주 중요해

트랜잭션의 성질

  • A: Atomicity (원자성)
    • 트랜잭션이 수행되면 ALL or NOTHING. 중간은 없다
  • C: Consistency (일관성)
    • 트랜잭션 전후에 시스템 제약조건을 완전히 만족해야 함
    • 중간엔 제약조건 깨져도 되나? 된다! 왜?
      • Atomicity (원자성)이 있기 때문.
  • I: Isolation (고립성)
    • 동시성이 있더라도 혼자 할때랑 똑같아야 한다.
  • D: Durability (내구성)
    • 반드시 영구적으로 잘 저장되어야 한다.

분산 시스템의 CAP 이론

서버가 여러 대로 분산되어 있을때.

서로가 살아있는지 보는걸 헬스체크라고 하고, 내가 살아있다고 주기적으로 보내는 걸 하트비트라고 함.

살았는지 죽었는지를 투표를 해서 결정하는데, 거기서 반반이 안나오게 하려고 분산된 서버는 보통 홀수개임.

  • C: Consistency
    • 여러 서버 중 어디로 요청을 보내던지 늘 똑같은 응답을 받아야 한다.
  • A: Availability
    • 서버가 24시간 늘 응답이 가능한 상태여야 한다.
  • P: Partition Tolerence
    • 시스템의 일부가 죽더라도 전체 기능에는 영향을 줄 수 없어야 한다.

안타깝게도 이 3개가 동시에 만족되기는 어렵다는 것이 네트워크 이론에서 증명되어 있음.

그래서 대부분의 경우는 Consistency(일관성)을 포기함. 대신 Eventual Consistency를 보장해 줌.

Eventual Consistency

  • 언젠가는 일관성이 보장이 되는데, 그 시점이 정해지지 않은 경우

NoSQL의 특징

  • BA: Basically Available
  • S: Soft State
  • E: Eventual Consistency

일부러 ACID(산) 반대로 BASE(염기). 슥 보고 넘어가

트랜잭션과 Serial Schedule

  • 한 번에 하나만 트랜잭션

Serial Schedule

트랜잭션이 A, B, C 3개가 한 번에 하나씩 실행됐을 때, 가능한 결과들의 집합 ⇒ 3! 이하임!

  • 순서에 따라 결과가 같을수도 다를수도 있으니

Serializable

진짜로 한 번에 하나씩 실행한 게 아니라, 동시에 실행했는데, 한 번에 하나씩 실행한 것 처럼 결과가 나올때를 Serializable이라고 함.

  • 이걸(동시성) 보장하기 위해서 우리는 Lock을 사용해

Lock

한 사용자가 특정 리소스에 접근하는 동안 다른 사용자가 접근 못하도록 막는 것.

화장실 자물쇠가 가장 적절한 비유.

내가 싸는동안 다른 사람들이 못 싸게 막는 역할.

대신 열고 잠그는 동안 화장실 변기 리소스가 낭비됨.

낙관적 잠금이란 게 있음. 야산 화장실엔 잠금장치가 없음.

낮은 확률로 쓰고있는 데 누가 열어버릴 수 있지. 이것이 충돌!

Lock의 두 가지 방식?

  • 낙관적 잠금 : Optimistic Concurrency Control (낙관적 동시성 제어)
  • 비관적 잠금 : Pessimistic Lock
  • 충돌 확률이 낮을 땐 낙관적 동시성 제어, 높을 땐 비관적 잠금을 쓰는 게 좋다.

4가지 문제 중 우리가 해결해야 할 3가지 문제

Lost Update Problem

  • 두 개의 트랜잭션이 동시에 한 아이템의 데이터를 발생하는 문제!
  • 이건 우리가 신경쓸 게 아니고 DBMS 어플리케이션 개발자들이 신경써줘야 함. (트랜잭션을 지원하는 DB에선 발생하면 안된단 뜻)

다음 3개의 문제는 DB 사용자가 해결해줘야 할 문제

P1: Dirty Read Problem

  • 한 트랜잭션에서 변경한 값을 다른 트랜잭션에서 읽을 때 발생하는 문제

https://user-images.githubusercontent.com/138586629/284137372-fd8c6ccd-fefa-4a74-b672-d394bfd15df1.png

  • A가 쓰고, B가 읽고, A가 그걸 롤백하면 잘못된 값을 읽은게 됨

P2: Non-repeatable Read Problem

  • 한 트랜잭션에서 같은 값을 두 번 읽었을 때 각각 다른 값이 읽히는 경우

https://user-images.githubusercontent.com/138586629/284137379-f76853b9-b620-4f49-909b-7bc27cb2f301.png

  • A 읽고, B 쓰거나 지우고, B 커밋하고, A가 또 읽으면 A 입장에서 똑같이 읽은 결과가 달라져버림.

P3: Phantom Read Problem

  • 주로 통계나 분석, aggregation function 등을 수행하는 쿼리에서 잘못된 값이 들어오는 경우

https://user-images.githubusercontent.com/138586629/284137385-6fcbb866-15a8-4ab4-9164-6dcdf69dfd0f.png

  • A에서 Select, B Update/Create, A 다시 Select.
  • 그러면 A 입장에서 갑자기 없던 값을 읽게되는 문제가 생겨버림

P1, P2, P3 다 Isolation에 문제가 생긴거임. TIL(Transaction Isolation Level) 설정을 잘 하면 선택적으로 이걸 해결할 수 있다!

Transaction Isolation Level

https://user-images.githubusercontent.com/138586629/284143444-3f3147b3-9cd9-49c4-bfa4-b168df59074a.png

  • P1, P2, P3 문제를 해결하는지 여부에 따라 서로 다른 Isolation Level로 나누어둠!
  • MVCC(Multi Version Concurrency Controller)를 통해 MySQL은 4가지 모두 지원한다. MySQL 아주 좋은 DB임!

4 가지 옵션의 존재 이유

Serializable로 통일하지 않고 왜 4개의 옵션을 다 주는걸까?

  • 트랜잭션 자체는 별로 안중요할 때가 있어
  • 게시판 이런거 어떨까? 읽는 데 중간에 값 바꼈어. 상관 있어?
    • 그래서 이럴 땐 Read Uncommitted 써요 성능 때문에.
    • 그게 아니라도 비즈니스 로직상 필요해서 Read Uncommitted 쓸 일 많아요
  • 반대로 Serializable 엄청 느린데 왜 써요?
    • 비즈니스 로직상 이게 필요하기 때문이지!

이 옵션은 세션별로 지정을 하는 거임.

SHOW VARIABLES LIKE 'tx_isolation'; -- 이렇게 확인할 수 있고
SET TRANSACTION ISOLATION LEVEL 레벨; -- 이렇게 지정해서 transaction 단위로 사용
START TRANSACTION;
-- 원하는 쿼리, 정해둔 TIL로 실행
COMMIT | ROLLBACK;

MYSQL 기준 가능한 레벨은 앞서 말했듯 다음 4가지

  • read-uncommitted
  • read-committed
  • repeatable-read
  • serializable

실습

read-uncommitted

-- session A
set session transaction_isolation='read-uncommitted';
start transaction;
select * from sample;
update sample set money = 500 where nickname='abc'; -- B의 두 번째 select 전에 update
rollback;
-- session B
set session transaction_isolation='read-uncommitted';
start transaction;
select * from sample; -- 이 값이랑
select * from sample; -- A에서 업데이트 하고 다시 확인해보면 바뀌어있음
commit;

P1: Dirty Read Problem 문제도 해결이 안된 것을 확인할 수 있음.

read-committed

ㅇㅋ 그럼 read-committed 레벨에서는 어떨까?

-- session A
set session transaction_isolation='read-committed';
start transaction;
select * from sample;
update sample set money = 9999999 where nickname='def'; -- B의 두 번째 select 전에 update
commit; -- B의 세 번째 select 전에 commit
-- session B
set session transaction_isolation='read-committed';
start transaction;
select * from sample; -- 이 값이랑
select * from sample; -- A에서 업데이트 하고 다시 확인해도 안바뀜! P1 문제 안생김
select * from sample; -- A에서 commit하면 바뀌어버림! P2 문제는 여전히 생김
commit;

read-committed는 P1문제는 해결되고 P2(,P3)문제는 여전히 있다는 것을 확인 수 있음.

repeatable-read

참고로 repeatable-read는 global default임

https://user-images.githubusercontent.com/138586629/284146339-46cc44c7-f583-4f20-bc63-4b57b4c3d83b.png

-- session A
set session transaction_isolation='repeatable-read';
start transaction;
select * from sample;
update sample set money = 0 where nickname='ghi';
select * from sample; -- 트랜색션 안에서는 0원 됨
commit;
select * from sample; -- 0원 됨
-- session B
set session transaction_isolation='repeatable-read';
start transaction;
select * from sample;
select * from sample; -- A에서 update했어도 0원 안됨
select * from sample; -- A에서 커밋했어도 0원 안됨
commit;
select * from sample; -- 0원 됨

왜냐? repeatable-read 모드에서는 transaction이 끝날 때까지 다른 transaction의 영향을 안 받고 같은 값으로 유지가 되거든! P1, P2 문제 해결, P3는 아직 미해결!

serializable

이제 마지막으로 serializable

-- session A
set session transaction_isolation='serializable';
start transaction;
select * from sample;
update sample set money 100 where nickname='ghi'; -- B가 읽고 나서 쓰면 여기서 에러 뜸!
-- 왜냐? serializable에서는 읽기 lock이 걸리면 쓰기 lock 걸릴 수 없어서 쓰기 자체가 안됨
update sample set money 100 where nickname='ghi'; -- B transaction이 끝나고 쓰면 잘됨!
rollback;
-- session B
set session transaction_isolation='serializable';
start transaction;
select * from sample; -- A update 전에 한번 읽어줌
rollback; -- 얘 transaction을 종료해줌 그러면 읽기 lock 해제됨

P1, P2, P3 문제 모두 해결되지만 lock 때문에 동시에 요청하면 에러가 걸리는 경우가 많음.

shared lock, exclusive lock, 읽기 lock, 쓰기 lock 이 4개의 개념에 대해서 공부를 한 번 해보세요

필수필수!

[DB] Exclusive lock과 Shared lock의 차이

  • Shared Lock (공유 잠금)
    • 읽기 Lock
    • Shared Lock 걸린 상태로 다른 트랙잭션에서 데이터에 접근하면
      • Shared Lock은 허용 (읽기는 허용)
      • Exclusive Lock은 불가능 (쓰기는 불가능) → 그래서 위에서 에러가 뜬거임
  • Exclusive Lock (배타적 잠금)
    • 쓰기 Lock
    • Exclusive Lock이 걸린 상태로 다른 트랜잭션에서 데이터에 접근하면
      • Shared Lock, Exclusive Lock 불가능 (읽기, 쓰기 모두 불가능)
      • 그럼 TIL이 repeatable-uncomitted라서 Lock을 안거는 요청이면 가능할까? 이건 알아봐야 할 듯
-- session A
set session transaction_isolation='serializable';
start transaction;
select * from sample;
update sample set money=100 where nickname='kay'; -- B가 읽기 전에 쓰면 에러 안뜸!
select * from sample; -- 잘 반영됨
select * from sample; -- 계속 잘 됨 왜? session A가 락을 걸었으니까. 
-- 쓰기 lock. 다른 말로는 exclusive lock을 걸었다.
commit;
-- session B
set session transaction_isolation='serializable';
start transaction;
select * from sample; -- A update 후에 읽으면 안됨! 
-- 왜? Exclusive Lock(쓰기 Lock)이 걸려있으니까!
-- 30초가 지나서 timeout 걸림. 
select * from sample; -- A가 commit 하자마자 잘 나옴. Lock이 풀렸으니 Shared Lock 걸 수 있음
commit;

위 예제는 Exclusive Lock을 먼저 거는 경우임.

어디다 써먹을까? 일반적인 경우의 transaction에서 쓸까요 안쓸까요?

잘 안쓰겠죠 쓰기가 한 번에 1개밖에 안돼요. 엄청 위험하죠. 에러도 많이 뜨고 ㅇ ㅇ

MVCC (Multi Version Concurrency Controller)

이렇게 돌아가는 이유는 MySQL의 InnoDB가 오라클과 같이 내부적으로 MVCC를 사용하기 때문임

어떻게 위와 같은 동시성 제어가 가능한가?

⇒ 트랜잭션마다 자기 자신의 사본(Multi Version)을 저장해두고 사용함. 이걸로 Isolation을 보장함.

GAP Lock이라는 게 있어요. 굉장히 중요한거니까 공부해주세요.

MySQL Gap Lock 다시보기

이 어려운 걸 어떻게 정리했나 봤더니 Real MySQL 저자임 ㅋ ㅋ

Real MySQL은 참고로 많이 어려워 DBA를 위한 책

그치만 우리가 갈 회사엔 DBA가 없을 가능성이 높아요 ㅎ

그니까 배워야함

분산 환경에서의 트랜잭션

이 “분산 트랜잭션”이 인기가 있다가 없다가 다시 많아졌대

MSA(Micro Service Architecture) 때문임.

DT(Distributed Transaction) = 분산 트랜잭션 어떻게 할건데? 이런 질문 무조건 들어옴.

마이크로서비스 - 분산 트랜잭션 처리 패턴

보통 MSA로 분산된 서비스에 걸쳐서 비즈니스 처리를 수행하면,

비즈니스 정합성이나 데이터 일관성 보장에 대한 여러 문제가 생기는데,

대표적인 해결 방법이 분산된 서비스를 하나의 트랜잭션으로 묶는 것이라고 함.

어렵다.. 천천히 알아가자..

우대사항 다 안다고 쓰지 말고 한개만 잘 한다고 하고 파는 게 좋음.

백엔드 보면 React 우대가 젤 많아요. 그게 젤 좋음. React 잘한다고 쓰세요ㅋ

여러분 진행중인 프로젝트에 트랜잭션 신경 써야해요.

우리 서비스가 중요한지 안중요한지 보고 한번쯤 신경써보세요.

오늘은 여기까지~

profile
해커 출신 개발자

0개의 댓글