트랜잭션 내 명령을 하나의 묶음으로 처리하여 모두 성공하거나 아니면 모두 실패한다. 이를 전부 또는 전무의 원칙이라고 한다. 출금 명령과 입금 명령을 각각 실행하지 않고 이 둘을 묶어 송금이라는 더 이상 분리할 수 없는 원자로 취급한다.
트랜잭션으로 처리하는 데이터는 일관성을 유지한다. 성공할 경우는 물론 아무 문제가 없으며 실패하더라도 최소한 하다가 만 상태로 방치되지는 않는다.
데이터베이스는 원칙적으로 다중 사용자가 동시에 접속할 수 있어 여러 개의 트랜잭션이 동시에 실행된다. 이때 트랜잭션끼리 격리되어 서로 방해하지 않는다. 또한 이체 전이나 후의 상황을 볼 수 있어도 이체 진행중인 순간은 외부에서 보이지 않는다.
트랜잭션을 성공적으로 수행하면 수정된 데이터를 시스템에 영구적으로 적용한다. 중간 결과가 아니라 완성된 결과만 저장한다.
자동커밋 모드 : 명을 내리자 마자 임시영역을 거치지 않고 바로 확정하며 트랜잭션을 구성하지 않는다. 수정 속도는 빠르지만 한 번 내린 명령은 취소할 순 없다.(SQL SERVER, MARIADB는 이 모드가 디폴트이다.)
수동커밋 모드 : 명령 실행시 트랜잭션이 즉시 시작되며 모든 결과를 임시 영역에 기록한다. 커밋 또는 롤백 명령을 내려야 확정 또는 취소를 결정하며 트랙잭션을 종료한다.(ORACLE. DB2는 이 모드가 디폴트이다.)
INSERT INTO tcity VALUES ('평택',453,51,'n','경기');
SELECT * FROM TCITY
BEGIN TRANSACTION [이름] [WITH MARK]
명령
{COMMIT TRANSACTION | ROLLBACK TRANSACTION}
계좌이체 명령을 트랜잭션으로 묶어보자
BEGIN TRANSACTION
UPDATE TMEMBER SET MONEY = MONEY + 100 WHERE MEMBER = '춘향';
UPDATE TMEMBER SET MONEY = MONEY - 100 WHERE MEMBER = '이도령';
ROLLBACK;
BEGIN TRANSACTION으로 명령을 시작한 후 춘향이의 예치금을 100원 증가시키고 이도령의 예치금을 100원 감소시켰다. 두 작업의 결과는 로그에 임시적으로 기록되며 이 상태에서 ROLLBACK 명령을 내리면 두 명령 모두 취소한다. 결국 예치금에 변화는 없다.
ROLLBACK명령을 COMMIT로 바꾸고 다시 실행하면 명령 결과가 테이블에 잘 반영되어 있다.
실제로 계좌이체 명령은 다음과 같이 쿼리문을 작성해야 한다.
BEGIN TRAN
UPDATE tMember SET money = money + 10000 WHERE member = '춘향';
DECLARE @remain INT
SELECT @remain = money FROM tMember WHERE member = '이도령';
IF @remain < 10000
BEGIN
ROLLBACK
END
ELSE
BEGIN
UPDATE tMember SET money = money - 10000 WHERE member = '이도령';
COMMIT
END
만약 사용자가 여렷 이라면 수동모드에서 문제가 발생할 수 있다.
어느 한 사용자가 다음과 같은 명령을 실행했다고 가정하자
UPDATE TCITY SET AREA = 500 WHERE NAME = '서울';
SET AUTOCOMMIT ON;
SET AUTOCOMMIT OFF;
SHOW AUTOCOMMIT;
수동커밋, 자동커밋 어떤 쪽이 더 우월하다기보다는 장단점이 있어 취향과 작업의 특성에 따라 모드를 변경해야 한다.
UPDATE TCITY SET POPU = 1000 WHERE NAME = '서울';
SAVEPOINT P1000;
UPDATE TCITY SET POPU = 1100 WHERE NAME = '서울';
SAVEPOINT P1100;
UPDATE TCITY SET POPU = 1200 WHERE NAME = '서울';
SAVEPOINT P1200;
ROLLBACK TO SAVEPOINT P1100
동시성과 일관성은 항상 반비례 관계이다. 정확성을 희생하면 동시성을 높일 수 있고, 정확성을 확보하면 동시성을 제약할 수 밖에 없다.
DBMS는 적당한 수준에서 이 둘의 균형을 맞추는 여러 가지 장치와 옵션을 제공한다. 그 장치가 바로 명령을 묶어서 처리하는 트랜잭션과 사용중에 다른 사람의 접근을 막는 락이다.
1. 손실 업데이트
15석의 좌석이 남은 상태에서 2명의 예약원이 근무중이다. 각 예약원이 10개, 7개의 자석을 예약하는데 먼저 예약원1이 15- 10 = 5로 갱신하고 B가다시 15 - 7 = 8로 갱신했다. 잔여 좌석은 15석 뿐이지만 2석이 초과 예약되었을 뿐만아니라 아직도 8석이 남은 것으로 잘못 기록된다.
2. 커밋되지 않은 읽기
이번에는 예약원과 배차원이 동시에 작업하는 경우를 보자
배차원이 100석을 늘리는 트랜잭션을 진행중이며 아직 완료하지 않았는데 예약원이 중간 결과를 보고 예약하여 문제가 발생했다.
3. 팬텀 읽기
열차 시간이 2시 4시로 편성되어 있다. 예약원이 4시 열차가 있음을 확인하고 고객과 예약을 상담중인데 그 사이에 배차원이 4시 열차를 편성표에서 빼버렸다. 4시 열차는 DB상에서 사라졌으며 예약원이 확인한 4시 열차는 유령 열차이다. 이처럼 다른 트랜잭션에 의해 삭제된 레코드를 팬덤 이라고 한다.
이 문제는 삭제뿐만 아니라 삽입시에도 나타난다. 예약원은 2시, 4시 열차가 있음을 고객에게 알려주고 상담을 진행중인데 3시 열차가 추가된다면 혼란을 초래할 수 있다. 팬덤을 해결하려면 데이터를 삽입, 삭제하는 동안에는 아예 읽지 못하도록 해야 한다.
4. 반복하지 않은 읽기
여기서 반복하지 않은 읽기란 한 트랜잭션이 같은 값을 두번 읽었을 때 값이 달라지는 경우이다. 예약원이 15석이 남아있음을 확인하고 예약을 진행중인데 다른 예약원이 10석을 먼저 예약해 버렸다면 남은 좌석은 5석밖에 되지 않는다. 고객이 예약을 결심하고 8석 예약을 요청했을 때 상담원이 다시 잔여 좌석을 확인해보면 5석만 남아있게 된다.
똑같은 데이터를 읽었는데도 한 트랜잭션내에서 다른 결과가 리턴되었는데 이는 트랜잭션 진행중에 다른 트랜잭션이 데이터를 변경하도록 내벼러 두었기 때문이다.
대부분의 DBMS는 레코드 단위의 락을 사용하되 쿼리문의 종류에 따라 범위를 자동으로 판별한다.
READ UNCOMMITED
커밋되지 않은 읽기를 허용한다. SELECT문을 실행할 때 공유락을 걸지 않아 한 트랙잭션에서 데이터를 바꾸는 중에도 데이터를 읽을 수 있다. 이 때 읽은 데이터는 진행중인 다른 트랙잭션에 의해 변경 될 가능성이 있어 격리수준이 가장 낮다. 동시성은 높지만 일관성은 쉽게 깨질 수 있다.
READ COMMITED
커밋된 읽기만을 허용하며 SELECT 문을 실행할 때 공유락을 건다. 다른 트랙잭션이 데이터를 바꾸고 있는 중에는 읽을 수 없어 커밋되지 않은 읽기 현상은 발생하지 않는다. 읽기 동작을 마친 후 즉시 공유락을 풀기 때문에 다시 읽었을 때 값이 달라지는 반복하지 않은 읽기의 가능성이 있다.
REPETEABLE READ
읽기를 마치더라도 공유락을 풀지 않으며 트랜잭션이 완전히 종료될 때까지 락을 유지한다. 그래서 같은 값을 두 번 읽을 때 항상 같은 결과를 리턴한다. 공유락이 걸린 상태에서 데이터를 변경하는 것은 금지하지만 삽입하는 것은 가능해 팬텀 읽기가 발생할 수 있다.
SERIALIZABLE
가장 높은 격리수준이다. 데이터를 읽는 동안 다른 트랜잭션이 이 데이터를 읽지도 쓰지도 못할 뿐만 아니라 새로운 레코드를 추가하는 것도 허용하지 않는다. 완벽에 가까운 일관성을 유지하지만 동시성은 떨어진다.
ALTER PROFILE DEFAULT LIMIT IDLE_TIME 1;
--사용자1
UPDATE TMEMBER SET AGE = 25 WHERE MEMBER = '향단';
UPDATE TITEM SET NUM = 10 WHERE ITEM = '두부';
COMMIT;
--사용자2
UPDATE TITEM SET NUM = 5WHERE ITEM = '두부';
UPDATE TMEMBER SET AGE = 18 WHERE MEMBER = '향단';
COMMIT;
두 명의 사용자가 동시에 이 명령을 실행했다고 가정하자
사용자1은 TMEMBER테이블에 베타락을 걸고 사용자2는 TITEM에 배타락을 건다. 다시 사용자 1이 TITEM에 접근하려 했을 때 사용자 2가 걸어둔 락 때문에 대기상태에 들어간다. 또한 사용자 2가 TMEMBER에 접근할 때 사용자 1이 걸어둔 락 때문에 대기상태에 들어가고 둘 다 무한정 대기상태에 들어간다.
트랜잭션을 효율적으로 관리하여 데드락을 최소화하기