티스토리 뷰
트랜잭션이란?
데이터베이스 관리 시스템 (DBMS)에서 DB의 상태를 변화시키는 작업의 논리적 최소 단위를 의미한다.
트랜잭션 특징 : ACID
Atomicity
- 원자단위로서 더이상 쪼갤 수 없는 논리적 최소 단위임을 말한다.
- 예를 들어 계좌이체를 하는 경우 송금/입금이 존재하는데 두 작업은 쪼갤 수 없다는 것이다. 송금이 성공하고 입금이 실패하는 트랜잭션은 존재하지 않도록 하는 것이 목적.
Consistency
- 트랜잭션이 성공했다면 데이터베이스는 항상 일관성 있는 상태로 유지 되어야 하는 것을 말한다.
- 만약 도메인 무결성 제약조건이 잔고는 0미만이 될 수 없다는 것이라면 이를 위반하는 트랜잭션은 존재할 수 없는 것이다.
Isolation
- 트랜잭션을 수행중에 다른 트랜잭션이 끼어들 수 없음을 말한다.
- 송금/입금 트랜잭션이 nested되어 실행될 경우 post 트랜잭션이 pre 트랜잭션의 커밋 전에 잔고를 보고 송금을 진행했지만 post 커밋 시점에는 pre 트랜잭션의 커밋으로 잔고가 0원이 되어 중단될 수 있다.
- 이러한 트랜잭션 격리 수준은 4단계로 나뉜다. (Isolation Level)
Durability
- 성공적으로 수행된 트랜잭션은 영구적으로 적용되어야 함을 말한다.
트랜잭션의 격리
트랜잭션 간 서로 중첩이 자유롭다면 온전한 데이터를 얻는게 힘들것이고 완전 배타적으로 작동된다면 동기적으로 수행되어 성능상에서 불이익을 얻을 수 있다. 이러한 접근 제어를 위해 사용되는 것이 LOCK이다.
LOCK : 트랜잭션의 동시성 제어를 위한 락
Shared Lock (Read Lock), 공유락
- 데이터를 읽을 때 사용되는 Lock이다.
- 공유 락은 공유 락끼리 동시에 접근이 가능하다.
- 운영체제에서 Readers-Writers Problem 와 비슷하다. Write는 불허하고, Read는 해당 Critical Section에
접근이 허용된다는 것이다. - Read Lock은 Read에게만 열려있는 Lock인 것.
- 운영체제에서 Readers-Writers Problem 와 비슷하다. Write는 불허하고, Read는 해당 Critical Section에
- 공유 락이 설정된 데이터에 배타 락을 적용할 수 없다. 따라서 공유락이면 공유락이지 배타락일 순 없다.
T1이 r 행에 대한 s Lock을 갖고 있을 경우 (T : 트랜잭션, s : shared lock, x : exclusive lock)
- T2가 s Lock을 요청할 경우 즉시 승인된다. 결과적으로 T1, T2 둘다 r 행에 대해 s Lock을 갖게 된다.
- 반면에 T2가 x Lock을 요청한다면 T1이 해당 r행의 s Lock을 풀 때까지 기다려야 한다.
Exclusive Lock (Write Lock), 배타락
- 데이터를 변경할 때 사용되는 Lock이다.
- 트랜잭션이 완료 될 때까지 유지된다.
- Lock이 해제 될 때 까지 다른 트랜잭션(조회 포함)은 해당 리소스에 접근이 불가능하다.
T1이 r 행에 대한 x Lock을 갖고 있을 경우 (T : 트랜잭션, s : shared lock, x : exclusive lock)
- T2는 x Lock을 갖고 있어도 참여할 수 없다. T1이 r 행에 대한 Lock을 풀 때까지 기다려야 한다.
트랜잭션 격리 수준
1. READ UNCOMMITTED (LEVEL 0)
- 사실상 격리가 없다고 봐야하며 대부분의 동시 접근을 허용한다.
이로 인해 Dirty read, Non Repeatable read, Phantom read가 모두 발생할 수 있으며
이로인해 같은 쿼리를 다시 수행해도 다른 결과를 얻을 수 있다. -> DB 일관성 유지가 불가능하다. - SELECT 쿼리가 수행되는 동안 해당 데이터에 Shared Lock이 걸리지 않는 단계.
- 트랜잭션에서 처리중이거나, 아직 Commit 되지 않은 데이터를 다른 트랜잭션에서 읽는 것을 허용한다.
Dirty read 문제
- A 트랜잭션에서 회원 A의 구매 목록을 추가하여 결제금액이 4 -> 5만원으로 변경
- 아직 Commit 하지 않음
- B 트랜잭션에서 회원 A의 결제금액을 조회
- 결제금액이 5만원으로 조회된다
- -> 이를 Dirty read라고 한다.
- A 트랜잭션에서 문제가 발생하여 Rollback 처리, 결제금액도 다시 4만원으로 변경됨
- B 트랜잭션은 조회한 5만원을 가지고 그대로 진행
결과적으로 데이터 정합성의 문제가 생긴다.
2. READ COMMITTED (LEVEL 1)
- SELECT 쿼리가 수행되는 동안 해당 데이터에 Shared Lock이 걸리는 단계.
- 트랜잭션의 변경내용이 Commit이 완료된 경우에만 다른 트랜잭션에서 조회가 가능하다.
- Dirty read를 방지한다. 하지만 Non Repeatable read, Phantom read는 여전히 발생한다.
- 동시 Transaction에서 Commit 되지 않은 변경은 영향을 주지 않게 되었다.
하지만 Commit이 되면 다시 읽었을 때 결과값이 변경될 수 있다.
Non Repeatable Read 문제
- B 트랜잭션에서 회원 A의 결제금액을 조회
- 4만원이 조회됨
- A 트랜잭션에서 회원 A의 결제금액을 4 -> 5만원으로 변경하고 Commit
- B 트랜잭션에서 회원 A의 결제금액을 다시 조회
- 5만원이 조회됨
이는 하나의 트랜잭션내에서 똑같은 SELECT를 수행했을 경우 같은 결과를 반환해야 한다는 Repeatable Read 정합성에 어긋나는 결과를 가져온다.
만약 입금/출금이 이뤄지는 트랜잭션이 존재하고 회원의 당일 입금 총액을 보여주는 트랜잭션이 있다고 하면, 입금 총액을 조회할 때마다 다른 결과를 가져오게 된다.
3. REPEATABLE READ (LEVEL 2)
- 트랜잭션이 완료 될 때까지 SELECT 쿼리가 사용되는 모든 데이터에 Shared Lock이 걸리는 단계.
- 이때 Lock은 조회 조건에 따라 달라질 수 있다.
- 특정 행만 조회하는 unique index 또는 unique search 조건을 사용하는 경우 InnoDB는 해당 index record만 Lock하게 된다.
- 그렇지 않은 조건의 경우 InnoDB는 검색된 범위의 인덱스에 대해 Lock을 걸게 된다. (gap lock 또는 next-key lock)
- 이렇게 함으로서 해당 범위내로의 삽입을 막는다.
- 트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있다.
- 트랜잭션이 범위 내에서 조회한 데이터 내용이 항상 동일함을 보장한다. 단, 범위 쿼리를 다시 실행하면 새로 추가된 행이나 제거된 행이 생길 수 있다.
- 다른 사용자는 트랜잭션 영역에 해당되는 데이터에 대해서 수정이 불가능하다.
- Phantom Read 문제가 발생할 수 있다.
Non Reapeatable read 문제 해결
- A 트랜잭션이 3번 회원을 조회
- B 트랜잭션이 3번 회원의 이름을 변경하고 커밋완료 함
- A 트랜잭션에서 3번회원을 다시 조회
- Undo 영역에 백업된 데이터를 반환. 변경되기 전 이름을 봄
즉, 자신의 트랜잭션 번호보다 낮은 트랜잭션 번호의 변경 & 커밋만 보는 것이다.
- 회원 A가 장바구니에 구매 목록을 담았다.
- 회원 A가 결제하기 위해 결제금액을 조회 -> 4만원
- 회원 A가 장바구니에 물건을 추가 -> 5만원
- 회원 A의 결제금액 -> 4만원만 결제됨. 나중에 담은 것은 결제를 위한 조회 이전에 발생된 것으로 취급하지 않는다.
Phatom read 문제
- Phantom = 유령을 말한다.
- 하나의 트랜잭션 내에서 같은 쿼리를 두번 실행했는데 첫번째 쿼리에는 없는 레코드가 두번째 쿼리에서 나타나는 현상.
(유령 레코드) - Repeatable read 이하 계층에서만 발생하며 INSERT에 대해서만 발생하는 문제이다.
문제 상황
start transaction; -- transaction id = 1
select * from customer; -- 0건 조회
start transaction; -- transactionid = 2
insert into customer values(1, 'woonsik', 27);
commit;
select * from customer; -- 0건 조회, REPEATABLE READ 단계이므로 자신보다 낮은 트랜잭션의 변경에만 영향을 받는다.
update customer Set name = 'handsome woonsik' where id = '1'; -- 1 row updated
select * from customer; -- 1건 조회
commit;
앞선 트랜잭션이 추후 트랜잭션에 영향을 받지 않아야 하는데 INSERT가 발생하고 UPDATE 시 조회되지 않던 row가 조회되는 일이 발생한다.
4. SERIALIZABLE (LEVEL 3)
- 매우 엄격한 격리 수준이다.
- DBMS는 SELECT 쿼리(조회) 시에는 아무런 잠금을 걸지 않지만 해당 단계에서는 단순 읽기 작업에도 Shared Lock을 걸게 된다.
- 이렇게 되면 조회가 발생될 때 다른 트랜잭션에서 이 레코드를 변경하지 못하게 된다. 결과적으로 동시 처리 능력이 매우 떨어지게 되고 성능저하가 발생하게 된다.
Isolation Level에 따른 문제점 정리
Dirty Read (발생 : Uncommitted read)
- 동시 트랜잭션이 커밋을 완료하지 않은 변경된 데이터를 조회하여 발생하는 현상
Non Repeatable Read (발생 : Uncommitted read, Committed read)
- 동시 트랜잭션이 동일한 행을 보고 있을 때 변경 & 커밋을 하게 되면 조회 결과가 달라지는 현상
Phantom Read (발생 : Uncommitted read, Committed read, Repeatable read)
- 한 트랜잭션 내에서 다른 트랜잭션이 일부 행을 추가/삭제 & 커밋을 하게 되면 조회 때 새로운 행이 생기거나 없어지는 현상
트랜잭션 Propagation
트랜잭션 간 전파정도를 설정한다. spring은 Propagation 설정에 따라 트랜잭션의 시작과 일시 중지를 관리한다.
(propagation : 번식)
1. REQUIRED (default 값)
- 부모 Transaction이 존재하면 참여하고, 없으면 새로 만든다.
2. SUPPORTED
- 부모 Transaction이 존재하면 참여하고, 없으면 Transaction 없이 실행된다.
3. MANDATORY
- 부모 Transaction이 존재하면 참여하고, 없으면 예외가 발생한다.
4. NEVER
- 부모 Transaction이 존재하면 예외가 발생하고, 없으면 Transaction 없이 실행된다.
5. NOT SUPPORTED
- 부모 트랜잭션이 있으면 일시중지하고, 비지니스 로직을 Transaction 없이 실행한다.
6. REQUIRES NEW
- 부모 트랜잭션이 있으면 일시중지하고, 새로운 Transaction을 생성하여 실행한다.
7. NESTED
- 부모 트랜잭션이 없다면 새로 만들고, 있다면 중첩 Transaction을 만든다.
- 중첩 Transaction은 앞선 부모의 commit과 rollback에는 영향을 받지만, 자신의 commit이나 rollback은 부모의 Transaction에 영향을 주지 않는다.
REQUIRED_NEW를 사용하여 문제를 해결했던 경우
Reference
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/
'Web > 정리글' 카테고리의 다른 글
멀티 쓰레드 정리 (0) | 2021.12.20 |
---|---|
HTTPS, TLS 정리 (0) | 2021.10.19 |
Exception & Transaction rollback 정리 (0) | 2021.09.23 |
WAS와 웹서버 차이 정리 (0) | 2021.09.10 |
스프링 AOP 정리 (2) | 2021.09.08 |
- Total
- Today
- Yesterday