DB 트랜잭션(Transaction)
트랜잭션이란?
데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 완전성 을 보장해주는 작업 단위
즉, 논리적인 작업 셋을 모두 완벽하게 처리하거나 또는 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능
트랜잭션을 통해 사용자의 입장에서는 작업을 논리적 단위로 이해를 할 수 있고 시스템의 입장에서는 그것이 데이터들을 접근 또는 변경하는 프로그램의 단위가 된다.
상태를 변화시킨다는 것 → SQL 질의어를 통해 DB에 접근하는 것
```SELECT
INSERT
DELETE
UPDATE
작업 단위 → 많은 SQL 명령문들을 사람이 정하는 기준에 따라 정하는 것
예시) 사용자 A가 사용자 B에게 만원을 송금한다.
이때 DB 작업
- 사용자 A의 계좌에서 만원을 차감한다 : UPDATE 문을 사용해 사용자 A의 잔고를 변경
- 사용자 B의 계좌에 만원을 추가한다 : UPDATE 문을 사용해 사용자 B의 잔고를 변경
현재 작업 단위 : 출금 UPDATE문 + 입금 UPDATE문
→ 이를 통틀어 하나의 트랜잭션이라고 한다.
- 위 두 쿼리문 모두 성공적으로 완료되어야만 "하나의 작업(트랜잭션)"이 완료되는 것이다.
Commit
- 작업 단위에 속하는 쿼리 중 하나라도 실패하면 모든 쿼리문을 취소하고 이전 상태로 돌려놓아야한다.
Rollback
``` - 즉, 하나의 트랜잭션 설계를 잘 만드는 것이 데이터를 다룰 때 많은 이점을 가져다준다.*
트랜잭션 특징
원자성(Atomicity)
트랜잭션이 DB에 모두 반영되거나, 혹은 전혀 반영되지 않아야 된다.
즉, 만약 트랜잭션 중간에 어떠한 문제가 발생한다면 트랜잭션에 해당하는 어떠한 작업 내용도 수행되어서는 안되며 아무런 문제가 발생되지 않았을 경우에만 모든 작업이 수행되어야 한다.일관성(Consistency)
트랜잭션의 작업 처리 결과는 항상 일관성 있어야 한다.
즉, 트랜잭션이 완료된 다음의 상태에서도 트랜잭션이 일어나기 전의 상황과 동일하게 데이터의 일관성을 보장해야 한다.독립성(Isolation)
각각의 트랜잭션은 서로 간섭없이 독립적으로 수행되어야 한다.
즉, 둘 이상의 트랜잭션이 동시에 병행 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.지속성(Durability)
트랜잭션이 정상적으로 종료된 다음에는 영구적으로 데이터베이스에 작업의 결과가 저장되어야 한다.
Commit
하나의 트랜잭션이 성공적으로 끝났고, DB가 일관성있는 상태일 때 이를 알려주기 위해 사용하는 연산
Rollback
하나의 트랜잭션 처리가 비정상적으로 종료되어 트랜잭션 원자성이 깨진 경우
transaction이 정상적으로 종료되지 않았을 때, last consistent state (예) Transaction의 시작 상태) 로 roll back 할 수 있음.
트랜잭션의 상태
Active
트랜잭션의 활동 상태. 트랜잭션이 실행중이며 동작중인 상태를 말한다.
Failed
트랜잭션 실패 상태. 트랜잭션이 더이상 정상적으로 진행 할 수 없는 상태를 말한다.
Partially Committed
트랜잭션의 Commit
명령이 도착한 상태. 트랜잭션의 commit
이전 sql
문이 수행되고 commit
만 남은 상태를 말한다.
Committed
트랜잭션 완료 상태. 트랜잭션이 정상적으로 완료된 상태를 말한다.
Aborted
트랜잭션이 취소 상태. 트랜잭션이 취소되고 트랜잭션 실행 이전 데이터로 돌아간 상태를 말한다.
Partially Committed 와 Committed 의 차이점
Commit
요청이 들어오면 상태는 Partial Commited
상태가 된다. 이후 Commit
을 문제없이 수행할 수 있으면 Committed
상태로 전이되고, 만약 오류가 발생하면 Failed
상태가 된다. 즉, Partial Commited
는 Commit
요청이 들어왔을때를 말하며, Commited
는 Commit
을 정상적으로 완료한 상태를 말한다.
트랜잭션을 사용할 때 주의할 점
트랜잭션의 범위를 최소화하라
즉, 트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋다.
일반적으로 데이터베이스 커넥션은 개수가 제한적이다. 그런데 각 단위 프로그램이 커넥션을 소유하는 시간이 길어진다면 사용 가능한 여유 커넥션의 개수는 줄어들게 된다. 그러다 어느 순간에는 각 단위 프로그램에서 커넥션을 가져가기 위해 기다려야 하는 상황이 발생할 수도 있는 것이다.
교착상태
교착상태란 무엇인가
복수의 트랜잭션을 사용하다보면 교착상태가 일어날수 있다. 교착상태란 두 개 이상의 트랜잭션이 특정 자원(테이블 또는 행)의 잠금(Lock)을 획득한 채 다른 트랜잭션이 소유하고 있는 잠금을 요구하면 아무리 기다려도 상황이 바뀌지 않는 상태가 되는데, 이를 교착상태
라고 한다.
교착상태의 예(MySQL)
MySQL MVCC에 따른 특성 때문에 트랜잭션에서 갱신 연산(Insert, Update, Delete)를 실행하면 잠금을 획득한다. (기본은 행에 대한 잠금)
트랜잭션 1이 테이블 B의 첫번째 행의 잠금을 얻고 트랜잭션 2도 테이블 A의 첫번째 행의 잠금을 얻었다고 하자.
Transaction 1> create table B (i1 int not null primary key) engine = innodb;
Transaction 2> create table A (i1 int not null primary key) engine = innodb;
Transaction 1> start transaction; insert into B values(1);
Transaction 2> start transaction; insert into A values(1);
트랜잭션을 commit 하지 않은채 서로의 첫번째 행에 대한 잠금을 요청하면
Transaction 1> insert into A values(1);
Transaction 2> insert into B values(1);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Deadlock 이 발생한다. 일반적인 DBMS는 교착상태를 독자적으로 검출해 보고한다.
교착 상태의 빈도를 낮추는 방법
- 트랜잭션을 자주 커밋한다.
- 정해진 순서로 테이블에 접근한다. 위에서 트랜잭션 1 이 테이블 B -> A 의 순으로 접근했고, 트랜잭션 2 는 테이블 A -> B의 순으로 접근했다. 트랜잭션들이 동일한 테이블 순으로 접근하게 한다.
- 읽기 잠금 획득 (SELECT ~ FOR UPDATE)의 사용을 피한다.
- 한 테이블의 복수 행을 복수의 연결에서 순서 없이 갱신하면 교착상태가 발생하기 쉽다, 이 경우에는 테이블 단위의 잠금을 획득해 갱신을 직렬화 하면 동시성을 떨어지지만 교착상태를 회피할 수 있다.
트랜잭션과 Lock 비교
잠금
동시성을 제어하기 위한 기능
잠금은 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할을 한다.
여기서 자원은 레코드나 테이블을 말한다.
트랜잭션
데이터의 정합성을 보장하기 위한 기능
트랜잭션은 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합되었을 때만 의미있는 개념은 아니다.
트랜잭션은 하나의 논리적인 작업 셋 중 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장하는 것이다.
예를 들면 HW 에러 또는 SW 에러와 같은 문제로 인해 작업에 실패가 있을 경우, 특별한 대책이 필요하게 되는데 이러한 문제를 해결하는 것이다.
트랜잭션 동작 방식
DB 구조
실행될 쿼리
위에서 나온 쿼리에 트랜잭션을 건 경우
BEGIN TRAN
UPDATE acounts
SET balance = balance - 10000
WHERE user = '구매자';
UPDATE accounts
SET balance = balance + 10000;
WHERE user = '판매자';
COMMIT TRAN
- 해당 쿼리가 쿼리 처리기를 통해 통과가 된다.
- 해당 쿼리를 처리하기 위해서는 구매자의 데이터가 필요하기 때문에 데이터 캐시에 구매자의 데이터를 요청한다.
- 데이터 캐시에 요청한 데이터가 없다.
- 데이터 파일에서 필요한 데이터를 데이터 캐쉬로 가져오고,
- 데이터 캐쉬는 필요한 데이터가 업데이트된다.
- 가져온 데이터를 업데이트하기전에 먼저해야할 일은, 로그를 기록하는 것
- 로그의 종류에는 REDO와 UNDO가 있음
- REDO : 변경 후(업데이트 후)의 값을 기록
- UNDO : 변경 전(업데이트 전)의 값을 기록
- 참고로 accounts.balance가 맞고 구매자는 alias임 ㅋㅋ
- REDO의 경우 트랜잭션이 시작되었다 명시를하고, 그 데이터가 어떻게 변경될 것인지 기록
- UNDO는 해당 데이터값이 원래 무엇이었는지 기록
- 로그를 기록한 후, 데이터 캐시를 업데이트한다.
- 이후 다음 데이터도 데이터 캐시에 없으므로 데이터 파일에서 요청을 해서 캐시로 가져온 후, 업데이트 전 후에 로그를 기록하고 (업데이터 전에는 UNDO, 업데이트 후에는 REDO) 데이터를 업데이트한다.
- 트랜잭션이 완료가 되면은 REDO로그에는 트랜잭션이 COMMIT되었다는 기록을 한다.
- 만약 해당 트랜잭션에 오류가 발생하여 개발자가 ROLL BACK 명령을 내리는 경우
- UNDO로그로 데이터 복원
- 왜냐하면 지금까지 데이터 캐시에서 데이터를 업데이트해서 변경된 상태로 있을 것이고, UNDO로그로 다시 원래값으로 복원한다.
- 데이터의 일관성 지킬 수 있음
- ROLLBACK 명령을 명시적으로 내리는게 아니라 예상치 못한 오류 발생하는 경우 (DB 전원이 꺼져버린다든가?)
- REDO 로그를 이용해서 데이터를 다시 원래상태로 복원한다. (일관성있게 만들어준다.)
- 내 사견을 첨부하자면, 트랜잭션이 완료된 경우에는 REDO로그를 통해서 복원해야하고, 트랜잭션이 수행되는 도중에 발생하면 UNDO를 통해 복원하는 것
트랜잭션 관리를 위한 DBMS의 전략
DBMS 구조
- 크게 2가지
- Query Processor (질의 처리기)
- Storage System (저장 시스템)
- 입출력 단위 : 고정 길이의 page 단위로 disk에 읽거나 쓴다 (read / write)
- 저장공간 : 비휘발성 저장장치인 disk에 저장, 일부분을 Main Memory에 저장
Page Buffer Manager of Buffer Manager
DMBS의 Storage System (저장 시스템)에 속하는 모듈 중 하나로, Main Memory에 유지하는 페이지를 관리하는 모듈
Buffer 관리 정책에 따라 UNDO 복구와 REDO 복구가 요구되거나, 그렇지 않게 되므로, 트랜잭션 관리에 매우 중요한 결정을 가져온다.
UNDO
필요한 이유
오퍼레이션 수행중에 수정된 page들이 Buffer 교체 알고리즘에 따라 디스크에 출력될 수 있음 (여기서 출력이란 memory에서 disk로 output연산이 되는 것을 말함)
Buffer 교체는 트랜잭션과는 무관하게 전적으로 Buffer의 상태에 따라서 결정된다.일관성 관점에 봤을 때는 임의의 방식으로 일어나게 된다.
즉, 아직 완료되지 않은 트랜잭션이 수정한 페이지들도 디스크에 출력될 수 있다. 따라서 만약 해당 트랜잭션이 어떤 이유든 정상적으로 종료될 수 없게 되면 트랜잭션이 변경한 (디스크에 출력된, 혹은 메모리 버퍼의) 페이지들은 원상 복구되어야 한다.
이러한 복구를 UNDO라고 한다.
만약 버퍼 관리자가 트랜잭션 종료 전에는 어떤 경우에도 수정된 페이지들을 디스크에 쓰지 않는다면, UNDO 연산은 메모리 버퍼에 되는 식으로 매우 간단해질 수 있다.
하지만, 이 부분은 매력적이지만 매우 큰 메모리 버퍼가 필요하다는 문제점을 가지고 있다.
버퍼 쓰기 정책
수정된 페이지를 디스크에 쓰는 시점을 기준으로 아래의 두 개의 정책으로 나눌 수 있다.
- steal : 수정된 페이지를 언제든지 디스크에 쓸 수 있는 정책
- not steal : 수정된 페이지들을 EOF(End of Transaction)까지는 버퍼에 유지하는 정책 (버퍼에 유지한다는 것은 그전까지는 버퍼에서 디스크로 출력하지 않고 가지고 있는다는 뜻)
steal 정책은 수정된 페이지가 어떠한 시점에도 디스크에 써질 수 있기 때문에 필연적으로 UNDO 로깅과 복구를 수반한다.
거의 모든 DBMS가 채택하는 버퍼관리 정책이다.
REDO
UNDO의 반대개념. 커밋한 트랜잭션의 수정은 어떠한 경우에도 유지(durability)되어야한다. 이미 커밋한 트랜잭션의 수정을 재반영하는 복구작업을 REDO라고 한다.
REDO 복구역시 버퍼관리정책에 영향을 받는다.
Buffer 관리 정책
트랜잭션이 종료되는 시점에 해당 트랜잭션이 수정한 페이지들을 디스크에도 쓸 것인가 여부로 두 가지 정책이 구분.
- force : 수정했던 모든 페이지를 트랜잭션 커밋 시점에 디스크에 반영
- not force : 수정했던 페이지를 트랜잭션 커밋시점에 디스크에 반영하지 않는 정책
여기서 주의할점
not force 정책이 수정했던 페이지(데이터)를 디스크에 반영하지 않느다는 점이지, 커밋 시점에 어떠한 것도 쓰지 않는다는 것은 아니다. 어떤 일들을 했었다고 하는 로그는 기록한다.
force 정책을 따르면 트랜잭션이 커밋되면 수정되었던 페이지들이 이미 디스크상의 데이터베이스에 반영되었으므로 REDO 복구가 필요없어진다.
반면 not force 정책을 따른다면 커밋한 트랜잭션의 내용이 디스크상의 데이터베이스상에 반영되어 있지 않을 수 있기 때문에 반드시 REDO 복구가 필요하다.
사실 force 정책을 따르더라도 데이터베이스 백업으로부터의 복구, 즉 미디어(media)복구 시에는 REDO 복구가 요구된다.
따라서 거의 모든 DBMS가 채택하는 정책은 not force이다.
정리
DBMS는 버퍼 관리 정책으로 steal (언제든지 디스크에 변경사항이 출력될 수 있다) 과 not force (커밋시점에 디스크에 변경사항 기록을 강제하지 않는다.) 정책을 채택하고 있어, 이로 인해 REDO와 UNDO 복구가 모두 필요하다.
출처 : https://d2.naver.com/helloworld/407507
Reference
- https://velog.io/@syleemk/%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C
- https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database#transaction
- https://github.com/gyoogle/tech-interview-for-developer/blob/master/Computer%20Science/Database/Transaction.md#db-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98transaction
- https://github.com/WooVictory/Ready-For-Tech-Interview/blob/master/Database/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98.md
'CS > DB' 카테고리의 다른 글
스프링 트랜잭션과 트랜잭션 전파 (0) | 2021.12.28 |
---|---|
트랜잭션 격리수준 (0) | 2021.12.28 |
관계형 데이터베이스 (0) | 2021.12.28 |
[Database] 정규화(Normalization) 쉽게 이해하기 (0) | 2021.09.12 |
[대용량DB] 데이터 테이블의 종류 및 특성 (0) | 2021.09.12 |