운영체제

트랜잭션

논리적 작업의 단위, 일관성이 보장되어야 한다.

ACID

Atomicity (원자성)

트랜잭션을 구성하는 모든 연산은 모두 실행이 되거나 아무것도 실행되지 않아야 한다.

Consistency (일관성)

트랜잭션이 실행된 후에도 일관성 있는 데이터베이스로 유지되어야 한다.

Isolation (격리성)

트랜잭션 수행 시 다른 트랜잭션의 연산이 끼어들 수 없다.

Durability (지속성)

성공적으로 수행된 트랜잭션은 이후 시스템 장애, 업데이트 등 변화에 관계없이 영원히 데이터베이스에 반영되어야 한다.

트랜잭션 모드

Autocommit mode (default)

statement가 시작할 때 transaction이 내부적으로 시작하고 매 statement가 끝날때마다 commit이 자동으로 실행된다.

SET AUTOCOMMIT = OFF
connection.setAutoCommit(false) // JDBC

Explicit mode

START TRANSACTION; -> Transaction 시작
COMMIT; -> Transaction의 commit에서 변경 내용을 데이터베이스에 저장하는 statement
ROLLBACK -> Transaction의 연산들로 변경된 내용을 취소하는 statement
START TRANSACTION;
UPDATE accounts SET balance = balance-5000 WHERE accID = "성호" 
UPDATE accounts SET balance = balance+10000 WHERE accID = "은경"
COMMIT; 

Isolation Level

트랜잭션의 분리 단계를 지정할 수 있다. 트랜잭션의 Isolation이 지켜지기 위해서는 a 트랜잭션에서 b 트랜잭션의 과정을 알아야 할 필요가 있고, 접근할 수 있는 단계를 설정할 수 있다.

READ UNCOMMITED

COMMIT이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 값을 읽을 수 있다. 가장 낮은 단계의 isolation level이다.

정합성에 문제가 있을 수 있기 때문에 추천하지는 않는다.

Transaction 1의 Commit이 되기 전이지만 update된 값을 읽을 수 있다. 이를 Dirty Read 현상이라고 한다.

READ COMMITED

Mysql 등 대부분의 RDB에서 기본적으로 사용되는 isolation level이다. 데이터를 읽을 때 Undo 영역에 백업된 레코드를 읽는다.

READ UNCOMMITED와 같은 Dirty read 현상은 일어나지 않고 commit된 데이터만 읽을 수 있게 된다.

하지만 여기에서도 문제는 존재한다. Transaction 2에서 데이터를 2번 읽는다고 생각해보자.

Transaction 2는 하나의 Transaction에서 같은 쿼리를 날렸을 때, 각각 다른 값을 가져오게 된다.

REPEATABLE READ

트랜잭션마다 id를 부여해 트랜잭션 ID보다 작은 트랜잭션 ID에서 변경한 것만 읽는다. 이 역시 Undo 영역에 백업된 데이터를 읽게 된다.

Transaction 2는 해당 트랜잭션이 완료될때까지 Transaction ID를 기준으로 데이터를 읽기 때문에 한 트랜잭션 안에서 데이터의 정합성이 보장된다.

그러나 Undo 영역에 백업되는 데이터가 많아져 자칫 쿼리의 처리성능이 떨어질 수 있고, PHAMTOM READ와 같은 부작용이 일어날 수 있다.

SERIALIZABLE

트랜잭션이 완료될 때까지 SELECT문이 사용되는 모든 데이터에 공유 잠금을 건다.

먼저 작업이 선행된 트랜잭션의 데이터를 후행 트랜잭션이 갱신, 삭제하지 못할 뿐만 아니라 새로운 레코드를 삽입하는 것도 막기 때문에 완벽한 읽기 일관성을 보장한다.

Transaction level에 따른 부작용

PHANTOM READ

  1. Alice와 Bob이 각각 트랜잭션을 갖고 DB에 접근한다.

  2. Bob의 트랜잭션이 먼저 시작한다.

    Bob은 SELECT문을 날려 모든 row를 조회하고 그 중 post_id가 1인 레코드만을 반환받는다. Bob은 총 3개의 row를 반환받는다.

  3. Alice의 트랜잭션이 시작된다. id가 4고 post_id가 1인 레코드를 추가한다.

  4. Alice의 트랜잭션이 종료된다.

  5. Bob의 트랜잭션은 아직 끝나지 않은 상태에서 다시 동일한 쿼리를 날리게 되면 다른 결과를 반환받게 된다.

Non-Repeatable read

  1. Alice와 Bob이 각각 트랜잭션을 갖고 DB에 접근한다.
  2. Alice의 트랜잭션이 먼저 시작한다.
  3. Bob의 트랜잭션이 시작되고 id가 1인 레코드를 반환받는다.
  4. Alice가 id가 1인 레코드의 값을 변경한 뒤 commit한다. Alice의 트랜잭션은 종료된다.
  5. Bob이 동일한 쿼리를 날렸을 때 다른 결과를 반환받게 된다.

phamtom read와 non-repeatable read는 비슷해보이지만 차이점이 있다.

  • non-repeatable read는 값이 수정되기 때문에 단일 레코드에 대한 결과가 달라질 수 있다.
  • phantom read는 일정 범위의 레코드를 읽는 쿼리에 대한 결과가 달라질 수 있다. 첫번째 쿼리에서는 없던 레코드가 두번째 쿼리에서는 나타나거나 없어질 수 있는 현상이다.

Non-Repeatable read (UPDATE)

START TRANSACTION;  # Transaction 1
SELECT * FROM Member WHERE name = 'psw' # 1 row
	
	START TRANSACTION; # Transaction 2
	SELECT * FROM Member WHERE name = 'psw' # 1 row
	UPDATE Member SET name = 'psy' WHERE name = 'psw' # update
	COMMIT;

UPDATE Member SET name = 'g-dragon' WHERE name = 'psw' # o row affected
COMMIT;

2번 트랜잭션에서 psw를 psy로 변경한 뒤 COMMIT을 날리게 되면 name = 'psw' 의 내용을 undo에 남겨놓게 된다. 그래야 아직 완료되지 않은 트랜잭션에서 undo를 보고 psw를 조회할 수 있기 때문이다.

2번 트랜잭션이 완료된 이후에 1번 트랜잭션에서 name = 'psw' 를 다시 조회하게 되는데, undo 영역의 데이터를 읽어서 변경을 하려고 하면 문제가 발생한다. UPDATE는 변경을 수행할 ROW에 대해 쓰기 잠금을 걸어야 하는데, undo 영역의 데이터는 그게 불가능하기 때문이다.

결국 UPDATE문에서 name = 'psw'를 찾을 수 없게 되고 변경이 일어나지 않게 된다.

Dirty Read

커밋되지 않은 데이터가 수정될 때 다른 트랜잭션에서 접근해 커밋 전과 후의 결과가 달라지는 현상



https://bit.ly/3FVdhDa
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

profile
Devops, AWS에 관심있어요.

0개의 댓글