데이터베이스

Transaction

승무_ 2023. 7. 6. 15:37

Transaction을 간단히 설명해 주세요 ⭐⭐

트랜잭션이란 데이터베이스의 상태를 변경시키기 위해 수행하는 작업의 논리적인 단위입니다. 트랜잭션은 데이터베이스의 신뢰성과 일관성을 지키기 위해 ACID라는 4가지 원칙을 만족해야 합니다.

 

[꼬꼬무1] commit과 rollback에 대해 설명해보세요

commit이란 지금까지의 수정사항들이 db에 영구적으로 반영되는 것을 의미하며
rollback은 지금까지의 변경사항을 취소하고 이전 커밋 상태로 되돌리는 것을 의미합니다.


[꼬꼬무2] 원자성, 일관성, 고립성, 지속성이 뭔가요?

원자성(Atomicity)
한 트랜잭션 내의 연산 중 하나라도 실패하면 rollback하고 전부 실행이 되는 경우에만 commit하는 것을 의미합니다.

일관성(Consistency)
트랜잭션이 일어난 후에도 데이터베이스의 제약이나 규칙을 만족해야 한다는 것을 의미입니다.

고립성(Isolation)
각각의 트랜잭션은 서로 간섭없이 독립적으로 수행되어야 한다.

 

Isolation이 안 지켜진 상황

더보기

J가 H에게 20만원을 이체할 때, 동시에 H도 본인 계좌에 30만원을 입금하는 상황

 

1. 20만원을 차감하기 위해 먼저 계좌 잔액을 읽고(read) 차감(write)

2. H에게 20만원을 보내주기 위해 먼저 잔액을 읽음(read)

3. 그런데 하필이면 이 때, H가 자기 계좌에 30만원 입금을 수행

4. 30만원을 입금 하기 위해 잔액을 읽고(read) 30만원 추가(write)

5. 위 트랜잭션이 끝나고 원래 진행되던 트랜잭션 이어서 수행

6. 그러나 이미 잔액을 200만원으로 읽어왔기 때문에 30만원이 추가되었다는 사실을 모르고 200+20 수행

7. 30만원이 사라지는 이상한 동작이 수행됨

8. 트랜잭션이 동시에 실행된것이 원인

지속성(Durability)
트랜잭션이 정상적으로 종료된 다음에는 영구적으로 데이터베이스에 작업의 결과가 저장되어야 한다.


Deadlock이란 무엇인지 설명해 주세요 ⭐

복수의 트랜잭션을 사용하다보면 나타나는 현상으로, 두 개 이상의 트랜잭션이 특정 자원(테이블 또는 행)의 잠금(Lock)을 획득한 채 다른 트랜잭션이 소유하고 있는 잠금을 요구하면 아무리 기다려도 상황이 바뀌지 않는 상태가 되는데, 이를 교착상태라고 한다.


[꼬꼬무1] deadlock을 해결하려면 어떻게 해야 하나요?

회피 기법

더보기

Wait-Die 방식

  • 트랜잭션 A가 트랜잭션 B에 의해 잠금된 데이터를 요청할 때 트랜잭션 A이 먼저 들어온 트랜잭션이라면 대기(Wait)한다.
  • 트랜잭션 A가 나중에 들어온 트랜잭션이라면, 포기(Die)하고 나중에 다시 요청한다.

Wound-Wait 방식

  • 트랜잭션 A가 트랜잭션 B보다 먼저 들어온 트랜잭션이라면, 데이터를 빼앗(Wound)는다.
  • 반면, 트랜잭션A가 트랜잭션 B보다 나중에 들어온 트랜잭션이라면 대기(Wait)한다.

 

테이블에 접근할 때 미리 지정한 순서에 따라 접근하게 하여 circular wait를 방지한다.

 

  • 트랜잭션을 사용해 본 경험이 있나요? 어떤 경우에 사용할 수 있나요?
더보기

결제 기능을 구현할 때 트랜잭션을 사용해본 경험이 있습니다.

트랜잭션이 필요한 경우는 결제와 같이 원자성이 꼭 필요한 경우입니다. 

  • 읽기에는 트랜잭션을 걸지 않아도 될까요?
더보기

일반적으로 단순 조회에는 트랜잭션을 걸지 않아도 되지만 복잡한 연산을 포함한 읽기를 할때는 중간에 다른 트랜잭션에 의해 값이 변경될 수도 있으므로 트랜잭션이 필요합니다.

트랜잭션 격리 수준

ACID 원칙에 따르면 트랙잭션간에 고립성을 완정히 보장해야 되므로 이를 완벽히 보장하기 위해서는 모든 트랜잭션을 차례대로 처리해야 합니다. 그러나 이런 방법은 자원을 효율적으로 활용하지 못하므로 동시에 여러 트랜잭션이 처리될 때 특정 트랜잭션이 다른 트랜잭션에의해 변경되는 데이터를 볼 수 있도록 허용할지 말지 단계별로 나눈것을 트랜잭션 격리 수준이라고 합니다.

READ UNCOMMITTED

가장 낮은 격리 수준으로, 다른 트랜잭션에서 수정 중인 데이터도 읽을 수 있습니다.

이로 인해, 다른 트랜잭션이 롤백되면서 수정되지 않을 데이터를 읽는 Dirty Read 문제가 발생할 수 있습니다.

READ COMMITTED

커밋된 데이터만 읽을 수 있습니다.

따라서, Dirty read 문제는 발생하지 않지만 Non-repeatable read 문제가 발생할 수 있습니다.

 

NON-REPEATABLE READ
하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행했을 때 항상 같은 결과를 보장해야 한다는 REPEATABLE READ 정합성에 어긋나는 것

 

 

REPEATABLE READ

tx 시작 시간 기준으로 그전에 commit된 데이터를 읽는다

 

REPEATABLE READ는 MySQL의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준입니다. 이 격리 수준에서는 READ COMMITTED 격리 수준에서 발생하는 "NON-REPEATABLE READ" 부정합이 발생하지 않습니다.

InnoDB 스토리지 엔진은 트랜잭션이 ROLLBACK될 가능성에 대비해 변경되기 전 레코드를 언두(Undo) 영역에 백업해두고 실제 레코드 값을 변경합니다. 

REPEATABLE READ는 언두 영역에 백업된 이전 데이터를 통해 동일한 트랜잭션 내에서는 동일한 결과를 보여줄 수 있도록 보장합니다.

 

사용자 A의 트랜잭션 번호는 12이고, 사용자 B의 트랜잭션 번호는 10입니다. 이때 사용자 A는 사원의 이름을 'Toto'로 변경하고 커밋을 수행합니다. 그런데 사용자 B는 emp_no = 50000인 사원을 A 트랜잭션이 변경을 실행하기 전과 실행한 후 각각 조회를 했지만, 데이터는 항상 동일한 'JuBal'라는 값을 SELECT합니다. 사용자 B가 BEGIN 명령으로 트랜잭션을 시작하면서 10번이라는 트랜잭션 번호를 부여받았는데, 그때부터 사용자 B의 10번 트랜잭션 안에서 실행되는 모든 SELECT 쿼리는 자신의 트랜잭션인 10번 보다 작은 트랜잭션 번호에서 변경한 것만 보게 됩니다.

 

PHANTOM READ

위 그림에서 사용자 B는 트랜잭션 시작(BEGIN) 후 SELECT 쿼리를 수행하고 있습니다. 따라서 이전 REPEATABLE READ 격리 수준에서의 설명처럼 동일한 트랜잭션 내에서는 결과가 동일해야 합니다.

하지만 위 그림에서 사용자 B가 실행하는 두 번의 SELECT .. FOR UPDATE 쿼리 결과는 서로 다릅니다. 이렇게 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상을 PHANTOM READ라고 합니다.

SELECT .. FOR UPDATE 쿼리는 SELECT하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없습니다.

따라서 위와 같은 쿼리는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 됩니다.

 

* SELECT .. FOR UPDATE(locking read중 배타락)는 가장 최근의 commit된 데이터를 읽는다.

 

SERIALIZABLE

가장 높은 격리 수준으로, 모든 데이터에 대해 락을 걸어 다른 트랜잭션이 접근하지 못하도록 합니다.

따라서, 앞서말한 문제는 발생하지 않지만 성능 저하가 발생할 수 있습니다.

 

* tx의 모든 평범한 select문은 select ... for share(locking read중 공유락)처럼 동작한다.

 

  • 만약 MySQL을 사용하고 있다면, (InnoDB 기준) Undo 영역과 Redo 영역에 대해 설명해 주세요.
더보기

Redo Log
DB 장애시 복구에 사용되는 로그

Undo Log
트랜잭션이 ROLLBACK될 가능성에 대비해 변경되기 전 레코드를 언두(Undo) 영역에 백업

  • 트랜잭션을 사용할 때 주의할 점
더보기

트랜잭션은 꼭 필요한 곳에서만 사용해야 한다. 일반적으로 데이터베이스의 커넥션 갯수는 제한적인데 한명이 오랫동안 커넥션을 소유하고 있으면 여유 갯수가 줄어들고, 커넥션 부족 현상이 발생하기 때문이다.