본문 바로가기

CS 과목(CS科目)/데이터 베이스(データベース)

14.Transaction

Transaction

우선 예제를 한 번 보자!!

위 2개의 SQL문이 모두 정상적으로 실행이 완료되야 지만 이체라는 것이 성립된다. 

그런데 만약 2개의 SQL문 중 어느 1개만 정상적으로 실행된 경우에는 어떻게 되버릴까?? 아래에서 살펴 보자!!!

 

1번재 SQL문 실패

이렇게 되면 J는 100만원 그대로가 되고, H는 200만원에서 220만원이 된다. 갑자기 없던 20만원이 H에게 생겨버렸다.

1번째 SQL문 성공

이렇게 되면, J는 100만원에서 80만원으로 바뀌고, H는 200만원이 그대로 있다. 원래 있던 20만원이 증발해버렸다.

이와 같이 이체라는 작업은, 이 2개의 SQL문이 모두 실행이 돼야 하는 작업이다. 

 

그럼 SQL문을 사용하여 Transaction을 구현해보자

현재 account 테이블의 상태

START TRANSACTION 

RDBMS에게 이제 TRANSACTION을 시작한다 라고 알리는 명령,

COMMIT

1. 지금까지 작업한 내용을 DB에 영구적(permanently)으로 저장하라(영구적 이라는 표현이 무엇을 의미하는지는 아래에서 자세히 다루겠다.)

즉, COMMIT을 하지 않으면, 지금까지 작업한 내용이 DB에 저장이 되지 않기에, SELECT문으로 테이블을 조회를 해보면 아무런 변화가 일어나 있지 않는다.

2. TRANSACTION을 종료하라!

-> COMMIT는 위 2가지의 의미를 가지고 있다. 

또 다른 transaction을 만들어 보자!!

1번째 transaction의 실행 결과

이제는 J가 H에게 30만원 이체하는 것을 Transaction으로 구현을 해 보자. 

2번째 transaction 구현

SELECT * FROM acoount

-> 위의 UPDATE문이 실행이 되면서 원래 80만원이 있었던 것이 50만원이 되었다.

ROLL BACK

->ROLL BACK 명령에 의해서, 다시 J는 80만원을 갖게 될 것이다.

위 그림에서 ROLL BACK은 지금까지의 작업들을 모두 취소하고 transaction 이전 상태로 되돌린다 라고 돼 있다.

이 의미를 본질적으로 이해를 해보자(아래의 설명들을 이해해야지만 이 부분을 이해할 수 있음)

1.지금까지의 작업들이란??

-> Transation이 시작되고 ROLL BACK 명령 이전의 SQL문들의 실행 결과!!!

2.Transation 이전 상태란??

-> Transaction이 시작되기 이전, 가장 마지막commit된 DB의 상태!!!

ROLL BACK의 결과

SELECT @@AUTOCOMMIT

-> 현재 AUTOCOMMIT이 활성화(사용)되어 있는지를 확인하는 문법.(참고로, MySQL은 Default로 AutoCommit이 enabled처리 돼 있다.)

여태껏 MySQL에서 INSERT문이나 UPDATE문 등을 실행 시, COMMIT이라는 명령을 내리지 않았는데도, 영구적으로 DB

에 저장이 되었다.

이것은 MySQL에서 default로 AUTOCOMMIT이 설정이 돼 있기 때문이다.

만약 AUTOCOMMIT 기능이 없었더라면 우리는 하나의 SQL문이 끝날 때마다 COMMIT 명령어를 타이핑해줘야 한다. 

아래에서 AUTOCOMMIT을 비활성화시켰을 때의 경우를 살펴보자

SET autocommit = 0

-> 이걸로 MySQL에서 Default로 enabled돼 있던, autocommit이 off 상태가 된다.

DELETE FROM ACCOUNT WHERE balance <= 100000

-> 이 SQL문이 실행이 완료가 돼도, 실제로 DB에는 변화가 없을 것이다.

(위 그림에서는 DELETE가 된 것처럼 보이나,MySQL을 껐다가 다시 키면 DELETE문의 실행이 DB에 반영돼 있지 않은 것

을 확인할 수가 있다. 이게 MySQL의 특성인지는 모르겠으나, autocommit을 off로 해놔도 select문으로 조회를 하였을 때,

SQL문의 실행이 DB에 반영돼서 출력이 된다. ㅠㅠ)

만약, autocommit을 enabled로 한 상태에서 DELETE문을 실행을 했으면 어떻게 됐을까???

-> DELETE문의 실행이 DB에 영구적으로 반영이 됐기 때문에, ROLL BACK을 하여도 DELETE문 실행 이전의 DB상태로

복구가 되지 않는다.

.왜냐하면 ROLL BACK은 가장 마지막 COMMIT이 된 DB의 상태로 돌아 가는 것인데, autocommit이 enabled된 상태에서

DELETE문을 실행을 하였기에, DELETE문의 실행의 결과가 COMMIT 명령에 의해 가장 마지막으로 DB에 반영되었기 때

문!! 

 

그럼 이런 의문이 들수가 있다.

" MySQL에서는 autocommit이 default로 enable상태이니깐, 위 2개의 UPDATE문을 실행을 하면, 그 결과도 이미 COMMIT상태가 되어 있는 거 아냐??" 

NOPE!!!!

->적어도 MySQL에서는 START TRANSACTION 명령이 실행이 되는 순간, COMMIT or ROLLBACK 명령어를 만나기 전

에는 autocommit이 off가 된다. 

그렇게 COMMIT or ROLLBACK 명령어를 만나서 Transactino이 종료가 되면, 

START TRANSACTION 이전의 autocommit의 상태로 돌아 온다.

1. START TRANSACTION 이전의 auttocommit이 활성화 된 경우

-> transactino이 종료되면, 다시 autocommit이 활성화 상태로 돌아 온다..

2. START TRANSACTION 이전의 autocommit이 비활성화 된 경우

-> transaction이 종료되면, 다시 autocommit이 비활성 상태로 돌아 온다. 

transaction 정리

그러나 요즘 시대에 개발자가 DB 서버에 붙어서 SQL문으로 직접적으로 Transaction 코드를 작성하지는 않는다. 

JAVA, Python 등과 같은 프로그래밍 언어로 Transaction을 작성을 한다.

아래에는 JAVA로 Transaction을 작성한 예이다. 

JAVA로 Transaction을 구현

Connection connection = ...;

-> 위 코드는 logic tier에서 동작하는 것이기에 data tier에 있는 DB 서버와 연결을 해줄 필요가 있다.

연결이 된 후에는 아래에 작성된 Transaction의 작업들의 로직이 실행이 된다.

connection.commit()

-> Transaction이 중간에 1번의 실패없이 정상적으로 동작이 되었다면, commit이 돼 영구적으로 DB에 저장이 된다.

connection.rollback()

-> 하지만 만약에 Transaction 실행 중, 단 1번이라도 실패가 나면 나머지 Transaction 작업을 하지 않고, try-catch에 의해

catch블럭으로 실행 흐름이 옮겨져서, rollback()에 의해 트랜잭션이 종료가 되며, 이전 상태로 DB가 돌아 온다. 

connectino.setAutoCommit(true)

-> connection은 보통 끊지 않고, 계속 연결된 상태를 유지하여 다른 SQL문을 실행을 할 것이다. 

근데 이때 autocommit을 다시 활성화를 시켜줘야지 원활히 자동으로 commit이 된다. 

 

ACID : 트랜잭션이 가춰야 할 특성

ACID

Atomicity(원자성)

 

Consistency(일관성)

위 그림에서도 나와 있듯이, 우리는 account 테이블을 정의를 할 때, balance가 0이상이 돼야 한다는 constraints를 걸어 주

었다.

근데, J가 100만원을 이체해주고 나면, J의 balance는 -20만원이라는 음수가 되버린다. 

이는 balance를 0이상으로 해주어야 한다는 account 테이블의 일관성을 깨뜨리게 된다.

고로, 1번째 SQL문에서 ROLLBACK에 의해 트랜잭션의 실행이 종료돼야 한다.

DBMS는 트랜잭션이 실행이 끝나면 DB에 정의된 RULE을 위반했는지에 대해 검사를 하여 

만약 있다면 에러를 알려준다.

JAVA의 경우, 이 에러를 catch문을 통하여 잡아서, RollBack을 해줘야 할 것이다. 

 

Isolation(격리)

Transation1은 위 예제를 푸는 작업들이고, Transaction2는 Transation1에 포함된 일부의 작업이다.

이게 왜 문제가 되는지 실행의 흐름에 따라 생각해보자.

1. 먼저, J가 20만원을 H에게 보내야 하므로, J의 balance를 DB로부터 가져와야 한다.-> read( balance )

2. 그후, 읽어 온 J의 balance에서 20만원을 차감한 값을 J의 balance값으로 갱신을 한다. -> write( balance )

3. H도 본인 계좌에 30만원을 입금을 해야 하기에, DB로부터 H의 balance값을 가져와야 한다. -> read(balance)-> 200만원

Transaction2의 시작

4. 이때 H가 본인 계좌에 30만원을 입금하는 Transation이 실행이 된다.

5. H의 balance를 읽어 들인다 -> read( balance ) -> 200만원

6. balance에 30만원을 더한다.-> write( balance) -> 230 만원

Transaction2의 끝(다시 Transactino1의 실행이 재개된다.)

7. J가 보낸 20만원을 H에게 입금을 시킨다.-> write( balance ) -> ( 200 + 20 )만원

-> H의 잔액은 250만원이 돼야 한다. 그러나, 7에서 balance에 20만원을 더할 때, 3에서 읽어 온 값에서 20만원을 더하였으므로 220만원이 입금되는 초유의 사태가 발생을 하게 된다. Transation끼리 서로 영향을 받게 되면 이러한 심각한 오류를 범하게 된다. 

고로, 트랜잭션끼리는 최소한 서로에게 영향을 미치지 않도록 해야 한다. 

isolation level이 높을수록, 여러 트랜잭션이 동시에 실행될 가능성이 낮아지지만, 동시성이 떨어지므로 퍼포먼스가 좋지 않다. 반면에 isolation level이 낮을 수록, 여러 트랜잭션이 동시에 실행될 가능성이 높아져서 퍼포먼스는 좋겠지만 여러 트랙잭션에 영향을 받아 오류가 발생할 가능성이 있다.

Durability(영존성)