트랜잭션이란?

HyunKyu Lee·2023년 12월 18일
0

데이터베이스

목록 보기
1/2

1. 트랜잭션


1. 트랜잭션의 특징

  • 트랜잭션은 관련된 SQL문을 하나로 묶어 처리하는 작업 단위이며 다음 4가지 특징을 가진다.
  1. 원자성

트랜잭션 내 명령을 하나의 묶음으로 처리하여 모두 성공하거나 아니면 모두 실패한다. 이를 전부 또는 전무의 원칙이라고 한다. 출금 명령과 입금 명령을 각각 실행하지 않고 이 둘을 묶어 송금이라는 더 이상 분리할 수 없는 원자로 취급한다.

  1. 일관성

트랜잭션으로 처리하는 데이터는 일관성을 유지한다. 성공할 경우는 물론 아무 문제가 없으며 실패하더라도 최소한 하다가 만 상태로 방치되지는 않는다.

  1. 격리성

데이터베이스는 원칙적으로 다중 사용자가 동시에 접속할 수 있어 여러 개의 트랜잭션이 동시에 실행된다. 이때 트랜잭션끼리 격리되어 서로 방해하지 않는다. 또한 이체 전이나 후의 상황을 볼 수 있어도 이체 진행중인 순간은 외부에서 보이지 않는다.

  1. 영속성

트랜잭션을 성공적으로 수행하면 수정된 데이터를 시스템에 영구적으로 적용한다. 중간 결과가 아니라 완성된 결과만 저장한다.

2.임시 작업 영역

  • 원자성을 구현하는 알고리즘은 복잡하고 성능에 지대한 영향을 주기 때문에 제품별로 고유한 방식을 사용한다. 제각각 다른 기술을 쓰지만 트랜잭션 처리 과정을 어딘가에 저장해두는 방식에서 크게 벗어나지 않는다.
  • 오라클은 이 저장영역을 언두 세그먼트 또는 롤백 세그먼트라고 부른다.
  • 이체 상황을 예로 들어보면 각 계좌의 출금 전후, 입금 전후의 상태를 모두 이 영역에 저장해두고 롤백과 커밋 명령에 따라 원래의 데이터를 유지하던지 이체 처리 후의 데이터로 바꾸던지 한다.

2. 트랜잭션 모드


1. 자동, 수동모드

  • DBMS가 트랜잭션을 처리하는 모드는 크게 자동과 수동 2가지가 있다. 모드에 따라 트랜잭션의 시작 여부와 유지 기간, 명령을 확정하는 시점이 다르다.

자동커밋 모드 : 명을 내리자 마자 임시영역을 거치지 않고 바로 확정하며 트랜잭션을 구성하지 않는다. 수정 속도는 빠르지만 한 번 내린 명령은 취소할 순 없다.(SQL SERVER, MARIADB는 이 모드가 디폴트이다.)

수동커밋 모드 : 명령 실행시 트랜잭션이 즉시 시작되며 모든 결과를 임시 영역에 기록한다. 커밋 또는 롤백 명령을 내려야 확정 또는 취소를 결정하며 트랙잭션을 종료한다.(ORACLE. DB2는 이 모드가 디폴트이다.)

INSERT INTO tcity VALUES ('평택',453,51,'n','경기');
SELECT * FROM TCITY
  • 다음과 같이 TCITY에 평택을 추가해 보았다. 당연히 SELECT 하여 모드 레코드를 검색해보면 가장 아래에 평택이 추가되어 있을 것이다. 그러나 평택은 실제 테이블에 적용된 것이 아니라 언두 세그먼트에 삽입 기록만 저장되어 있는 상태이다.
  • 이 상태에서 ROLLBACK명령을 실행하면 테이블에서 평택은 사라지고 COMMIT을 실행하면 평택이 완전히 테이블에 추가된다. 이 두 명령을 실행할 시 언두 세그먼트의 기록은 삭제된다.

2. 명시적 트랜잭션

  • 자동 커밋 모드(SQL SERVER)라고 해서 트랜잭션을 쓸 수 없는 것은 아니다. 두 명령을 하나의 논리적은 묶음으로 처리하려면 직접 트랜잭션을 구성하면 된다. 이를 명시적 트랜잭션이라고 하고 다음 형식으로 트랜잭션을 처리한다.
BEGIN TRANSACTION [이름] [WITH MARK]
명령
{COMMIT TRANSACTION | ROLLBACK TRANSACTION}
  • TRANSACTION은 TRAN으로 짧게 줄여도 되며 COMMIT과 ROLLBACK 다음에 TRANSACTION 키워드는 생략 가능하다.
  • BEGIN TRANSACTION 명령은 트랜잭션을 시작한다. 트랜잭션에 이름을 주거나 WITH MARK 명령으로 표식을 남기면 이 지접까지만 취소할 수 있다. 이후의 명령은 로그에 기록만 해 둔다.
  • COMMIT은 명령을 확정하고 ROLLBACK는 로그의 기록을 무시한다.

계좌이체 명령을 트랜잭션으로 묶어보자

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
  • 만약 이도령의 남은 계좌 금액이 이채해야할 돈(10000)보다 클때만 update를 하고 아닐시에는 트랜잭션을 rollback한다.

3. 수동 모드와 락

  • 자동 커밋 모드는 결과를 즉시 반영하여 편리한 반면 취소할 수 없어 위험하다. 이에 비해 수동 커밋 모드는 조금 느리지만 안전하다.
  • 수동모드는 동시성이 떨어지는 약점이 있다. 임시 영역에 저장한 작업 기록을 소유자만 볼 수 있으며 다른 사용자는 보지 못한다. 변경중인 행은 확정 또는 취소할 때까지 다른 사람이 변경하지 못하도록 락을 걸어 두기 때문이다.

만약 사용자가 여렷 이라면 수동모드에서 문제가 발생할 수 있다.

어느 한 사용자가 다음과 같은 명령을 실행했다고 가정하자

UPDATE TCITY SET AREA = 500 WHERE NAME = '서울';
  • 커밋을 하기 전 단계이기 때문에 다른 사용자는 서울행을 읽기만 할 수 있고(사용자 1이 트랜잭션 진행중이라 락을 걸어놓기 때문), 사용자 1과 사용자 2가 TCITY를 조회했을 때 서울의 AREA값이 서로 다르게 조회된다. 락이 걸리지 않은 다른행의 변경은 가능하다.
  • 다중 사용자 환경에서는 수동모드가 오히려 더 위험할 수도 있다. 한쪽에서 트랜잭션이 진행중일 때 불일치가 늘상 발생할 수 있으며 양쪽에서 같은 행에 대해 동시에 트랜잭션을 진행하면 데드락에 걸릴 위험도 있다.
  • 어떤 작업을 하든 결과를 점검한 후 COMMIT을 빼먹지 않도록 주의해야 한다 . 가급적 짧은 시간 안에 트랜잭션을 끝내 락의 지속 시간을 최소화하는 것이 바람직하다.

4. 트랜잭션 모드 변경

  • 오라클에서 커밋 모드를 변경하는 명령어는 다음과 같다

SET AUTOCOMMIT ON;

SET AUTOCOMMIT OFF;

  • 다음과 같은 명령으로 현재의 모드 확인도 가능하다.

SHOW AUTOCOMMIT;

수동커밋, 자동커밋 어떤 쪽이 더 우월하다기보다는 장단점이 있어 취향과 작업의 특성에 따라 모드를 변경해야 한다.

5. 저장점

  • 트랜잭션이 일단 시작되면 이후의 명령은 모두 하나의 묶음이 되어 전채를 확정하거나 취소하는 방법밖에 없다. 중간에 어느 지점으로 돌아가려면 각 명령 단계에서 저장점을 설정한다. 저장점은 트랜잭션 과정의 한 지점에 이름을 붙여 놓은 것이다.
  • 트랜잭션내에서 여러 개의 명령을 순서대로 내릴 때 SAVEPOINT 명령으로 특정 지점에 이름을 붙여두면 ROLLBACK할 때 이름을 지정하여 원하는 지점으로 정확히 복구 가능하다.
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

3. 락


1. 동시성과 일관성

  • 동시성 : 복수의 사용자가 같은 데이터를 동시에 엑세스 할 수 있는 것
  • 일관성 : 항상 정확한 정보를 갖는 것

동시성과 일관성은 항상 반비례 관계이다. 정확성을 희생하면 동시성을 높일 수 있고, 정확성을 확보하면 동시성을 제약할 수 밖에 없다.

DBMS는 적당한 수준에서 이 둘의 균형을 맞추는 여러 가지 장치와 옵션을 제공한다. 그 장치가 바로 명령을 묶어서 처리하는 트랜잭션과 사용중에 다른 사람의 접근을 막는 락이다.

2. 동시성의 부작용

  • 동시성 유지를 위해 어떤 문제가 발생하는지 알아보자. 다음은 열차 좌석 예약 시스템의 일관성이 깨지는 경우이다. 전국의 기차역에서 동시다발적으로 사용하기 때문에 일관성이 깨질 가능성이 높다.

1. 손실 업데이트

15석의 좌석이 남은 상태에서 2명의 예약원이 근무중이다. 각 예약원이 10개, 7개의 자석을 예약하는데 먼저 예약원1이 15- 10 = 5로 갱신하고 B가다시 15 - 7 = 8로 갱신했다. 잔여 좌석은 15석 뿐이지만 2석이 초과 예약되었을 뿐만아니라 아직도 8석이 남은 것으로 잘못 기록된다.

2. 커밋되지 않은 읽기

이번에는 예약원과 배차원이 동시에 작업하는 경우를 보자

  1. 잔여좌석 조사. 5석남음(배차원)
  2. 100석을 늘려 105을 만듬(배차원)
  3. 예약원이 좌석조사 → 105석이 남음
  4. 예약원이 20석 예약 85석 남음
  5. 여유 객차가 없어 100석 취소(배차원)

배차원이 100석을 늘리는 트랜잭션을 진행중이며 아직 완료하지 않았는데 예약원이 중간 결과를 보고 예약하여 문제가 발생했다.


3. 팬텀 읽기

열차 시간이 2시 4시로 편성되어 있다. 예약원이 4시 열차가 있음을 확인하고 고객과 예약을 상담중인데 그 사이에 배차원이 4시 열차를 편성표에서 빼버렸다. 4시 열차는 DB상에서 사라졌으며 예약원이 확인한 4시 열차는 유령 열차이다. 이처럼 다른 트랜잭션에 의해 삭제된 레코드를 팬덤 이라고 한다.
이 문제는 삭제뿐만 아니라 삽입시에도 나타난다. 예약원은 2시, 4시 열차가 있음을 고객에게 알려주고 상담을 진행중인데 3시 열차가 추가된다면 혼란을 초래할 수 있다. 팬덤을 해결하려면 데이터를 삽입, 삭제하는 동안에는 아예 읽지 못하도록 해야 한다.


4. 반복하지 않은 읽기
여기서 반복하지 않은 읽기란 한 트랜잭션이 같은 값을 두번 읽었을 때 값이 달라지는 경우이다. 예약원이 15석이 남아있음을 확인하고 예약을 진행중인데 다른 예약원이 10석을 먼저 예약해 버렸다면 남은 좌석은 5석밖에 되지 않는다. 고객이 예약을 결심하고 8석 예약을 요청했을 때 상담원이 다시 잔여 좌석을 확인해보면 5석만 남아있게 된다.
똑같은 데이터를 읽었는데도 한 트랜잭션내에서 다른 결과가 리턴되었는데 이는 트랜잭션 진행중에 다른 트랜잭션이 데이터를 변경하도록 내벼러 두었기 때문이다.

3. 락의 범위

  • 일관성을 얻으려면 동시성을 어느 정도 희생할 수밖에 없으며 동시성을 제한하는 장치가 바로 락이다. A가 트랜잭션을 진행중이면 B는 A가 트랜잭션을 종료하기 전까지 대기한다.
  • 락의 범위가 넓을수록 일관성은 높아지고 동시성은 떨어진다. 만약 DB전체에 락을 걸면 일관성은 확실해지지만 상관 없는 트랜잭션까지 같이 대기해야 하니 동시성은 극도로 나빠진다.
  • 반대로 락의 범위가 좁을수록 동시성은 좋아진다. 예약 작업시 2시 열차를 예약할 때 2시 레코드만 락을 걸면 3시나 4시 열차에 대한 예매는 방해없이 동시에 진행할 수 있다.

대부분의 DBMS는 레코드 단위의 락을 사용하되 쿼리문의 종류에 따라 범위를 자동으로 판별한다.

4. 락의 종류

  • 공유락 : 공유락은 다른 트랜잭션이 쓰는 것을 금지하지만 읽는 것은 허용한다. 내가 읽는 동안 남이 쓰지는 못하지만 읽는 것은 가능하다.
  • 배타락 : 배타락은 다른 트랙잭션은 쓰지 못할 뿐 아니라 읽지도 못하도록 하여 데이터를 독점적으로 사용한다.

5. 격리 수준

  • 격리수준은 쿼리를 실행할 때 어떤 종류의 락을 얼마동안 걸 것인가를 지정하는 옵션이다. 동시성이 떨어지더라도 일관성이 중요하다면 높은 격리수준을 설정하고 반대로 일관성을 희생하더라도 동시성을 높이려면 격리수준을 낮춘다.

READ UNCOMMITED

커밋되지 않은 읽기를 허용한다. SELECT문을 실행할 때 공유락을 걸지 않아 한 트랙잭션에서 데이터를 바꾸는 중에도 데이터를 읽을 수 있다. 이 때 읽은 데이터는 진행중인 다른 트랙잭션에 의해 변경 될 가능성이 있어 격리수준이 가장 낮다. 동시성은 높지만 일관성은 쉽게 깨질 수 있다.

READ COMMITED

커밋된 읽기만을 허용하며 SELECT 문을 실행할 때 공유락을 건다. 다른 트랙잭션이 데이터를 바꾸고 있는 중에는 읽을 수 없어 커밋되지 않은 읽기 현상은 발생하지 않는다. 읽기 동작을 마친 후 즉시 공유락을 풀기 때문에 다시 읽었을 때 값이 달라지는 반복하지 않은 읽기의 가능성이 있다.

REPETEABLE READ

읽기를 마치더라도 공유락을 풀지 않으며 트랜잭션이 완전히 종료될 때까지 락을 유지한다. 그래서 같은 값을 두 번 읽을 때 항상 같은 결과를 리턴한다. 공유락이 걸린 상태에서 데이터를 변경하는 것은 금지하지만 삽입하는 것은 가능해 팬텀 읽기가 발생할 수 있다.

SERIALIZABLE

가장 높은 격리수준이다. 데이터를 읽는 동안 다른 트랜잭션이 이 데이터를 읽지도 쓰지도 못할 뿐만 아니라 새로운 레코드를 추가하는 것도 허용하지 않는다. 완벽에 가까운 일관성을 유지하지만 동시성은 떨어진다.

  • 오라클은 이 중 READ COMMITED와 SERIALIZABLE 두 가지만 지원한다.

6. 라이브락과 데드락

  • 다른 트랜잭션의 락에 의해 잠시 대기하고 있는 상태를 라이브락이라고 한다. 타임아웃은 라이브락이 대기하는 최대 시간이며 이 시간이 지나도록 락이 풀리지 않으면 에러 코드를 리턴한다.
  • 오라클, SQLSERVER은 모두 타임아웃을 무한으로 설정해 놓았으며, 장시간 대기할 가능성이 있다면 다음 명령으로 타임아웃을 지정한다.

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이 걸어둔 락 때문에 대기상태에 들어가고 둘 다 무한정 대기상태에 들어간다.

트랜잭션을 효율적으로 관리하여 데드락을 최소화하기

  1. 가급적이면 트랜잭션이 같은 순서로 객체를 액세스한다.
  2. 트랜잭션 내에서 사용자와의 상호 작용을 피한다. 사용자에게 입력을 요구하거나 질문을 하는 코드를 트랜잭션에 포함시키면 안된다.
  3. 트랜잭션은 가급적 짧게 작성한다. 트랜잭션내에는 꼭 필요한 쿼리문만 포함하여 락이 걸리는 시간을 최소화한다.
  4. 가급적 낮은 격리수준과 좁은 법위의 락을 사용해야 동시성에 유리하다. 격리수준이 높으면 범위가 넓은 락이 오랫동안 유지되어 데드락 발생 빈도가 증가한다.

참고

profile
backend developer

0개의 댓글