트랜잭션이란?
데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위입니다.
여러 개의 변경 작업 쿼리가 조합될 때 주로 사용하며, 한 개의 쿼리가 있어도 논리적인 작업 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장해야 할 때 사용합니다.
트랜잭션은 언제 사용할까?
여러 개의 쿼리가 일련의 과정을 통해 데이터베이스 상태 값을 변경해야 할 때, 중간에 실패해서 일부만 업데이트가 되고 일부는 작업이 실패한다면 문제가 생깁니다. 아래는 가장 많이 사용되는 예시 입니다.
A가 B에서 만원을 송금을 한다고 할 때, 작업단위가 만원이 A계좌에서 출금되는 쿼리, B의 계좌로 만원이 입급되는 쿼리로 이루어져 있다고 하자. 만약 만원이 A계좌에서 출금되는 쿼리는 성공했는데, B의 계좌로 만원을 입금하는 쿼리는 오류가 나서 실패한다면 만원은 증발해버리게 된다. 따라서 B의 계좌로 입금하는 쿼리가 오류가 발생하면 A계좌에서 출금되는 작업도 Rollback 되어야 한다.
트랜잭션의 특성(ACID)
-
Atomicity (원자성)
트랜잭션의 모든 연산이 정상적으로 모두 수행 완료되거나, 아니면 어떤 연산도 수행되지 않는 상태인 것을 보장해야 합니다.
"All or Nothing"
트랜잭션이 정상적으로 수행되지 않으면 Rollback을 수행해서 트랜잭션에서 수행한 모든 작업을 초기화합니다.
-
Consistency (일관성)
트랜잭션이 완료되면 언제나 일관성 있는 DB 상태로 유지되어야 합니다. 예를 들면 계좌 이체가 성공적으로 실행되었다면 A계좌 잔액과 B계좌 잔액 합이 트랜젝션 실행 전의 합과 동일해야 합니다
-
Isolation (고립성)
하나의 트랜잭션이 실행하는 도중 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트렌잭션이 참조하지 못하게 하는 특성입니다. 하나의 트랜잭션이 A계좌에서 작업을 하고 있다면, 다른 트랜잭션이 A계좌에 참조하거나 관여할 수 없고 작업이 끝날 때까지 대기해야 합니다.
-
Durability (영속성)
트랜잭션이 성공적으로 완료되면, 해당 트랜잭션에 의해 변경된 데이터는 어떤 일이 일어나도 보존되어야합니다.
트랜잭션이 성공적으로 수행되었을 때, Commit 을 수행해서 데이터를 영구적으로 저장합니다.
트랜젝션의 상태
-
Active
트랜잭션이 실행 중인 상태입니다.
-
Failed
트랜잭션 실패한 상태로, 트랜젝션이 더 이상 정상적으로 진행할 수 없는 상태입니다.
-
Partial Committed
트랜잭션 쿼리가 모두 수행되고 Commit 직전의 상태입니다.
-
Committed
트랜잭션이 정상적으로 완료 된 상태입니다.
-
Aborted
트랜잭션이 취소되고 트랜잭션 이전 상태로 Rollback된 상태입니다.
트랜잭션 사용 시 주의사항
- 트랜잭션은 꼭 필요한 부분에만 적용하는 것이 좋습니다. 데이터베이스 커넥션은 개수가 제한적인데 각 작업 단위에서 커넥션을 소유하는 시간이 길어지면 사용 가능한 커넥션이 줄어들게 되기 때문입니다.
- 그래도 데이터의 변경이 일어나는 UPDATE나 DELETE문의 경우, 트랜잭션을 사용하는 것이 좋습니다.
교착 상태
복수의 트랜잭션을 사용하다보면 교착상태가 일어날 수 있습니다. 교착상태는 두 개 이상의 트랜잭션이 특정 자원의 잠금을 획득한 채 다른 트랜잭션이 소유하고 있는 잠금을 요구하면 아무리 기다려도 상황이 바뀌지 않는 상태가 되는데 이를 교착 상태라고 합니다.
데드락
교착 상태의 예를 들면, 트랜잭션1에서 table1 에 update 쿼리를 사용하여 잠금을 획득하고 트랜잭션2에서 table2에 update쿼리를 사용하여 잠금을 획득했을 때, 트랜젝션1에서 table2에 또 update쿼리를 사용하고, 트랜잭션2에서 table1 에 update쿼리를 사용하면 서로 교착상태가 되어 트랜젝션2에서 데드락이 발생합니다.
- 트랜잭션1
- 트랜잭션2
트랜잭션의 격리 수준(Isolation level)
동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경한 데이터를 확인할 수 있도록 허용할 지를 결정하는 수준입니다. 즉, 동시에 일어나는 트랜잭션들의 서로에 대한 영향 수준을 결정하는 것을 의미합니다.
Isolation Level 의 종류
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
READ UNCOMMITTED
각 트랜잭션에서 변경하는 데이터를 COMMIT이나 ROLLBACK여부에 상관없이 다른 트랜잭션에서 참조할 수 있습니다.
정합성에 문제가 많기 때문에 사용하지 않는 것을 권장합니다. (POSTGRESQL 에서는 기본적으로 지원하지 않습니다.)
DIRTY READ 가 발생합니다.
DIRTY READ 현상 - 다른 트랜잭션에서 COMMIT 되지 않은 UPDATE가 현재의 트랜잭션에 영향을 미치는 현상
READ COMMITTED
RDBMS 에서 대부분 기본적으로 사용되고 있는 격리 수준입니다.(POSTGRESQL도 기본 READ COMMITTED입니다)
다른 트랜잭션에서 COMMIT되지 않은 것은 현재 트랜잭션에 영향을 미치지 않기 때문에 DIRTY READ는 발생하지 않습니다.
하지만 다른 트랜잭션에서 데이터 업데이트가 일어난 후, COMMIT이 된 데이터는 현재의 트랜잭션에 영향을 미칠 수 있기 때문에 이런 경우 정합성이 깨질 수 있는 우려는 있습니다.
예를 들어 트랜잭션A에서 SELECT를 한 번 하고, 트랜잭션B에서 INSERT가 되고, COMMIT이 된다면, 트랜잭션A에서 그 다음에 일어나는 SELECT 쿼리는 이전의 SELECT쿼리와 결과가 달라집니다. (NONREPEATABLE READ)
데이터가 업데이트되는 쿼리에서는 데이터를 변경하는 도중 다른 트랜잭션이 같은 로우에 접근하면 그 트랜잭션이 끝난 후에 변경사항에 대해서 작업을 진행합니다. 이 때, 외부 트랜잭션으로 인해 변경된 로우가 현재 명령의 WHERE 조건에 맞는 지 다시 평가합니다.
복잡한 검색조건이 있는 상황에는 맞지 않고, 단순한 처리의 경우 적합합니다.
REPEATABLE READ
현재 트랜잭션이 시작되기 전에 커밋된 데이터만 참조합니다. (단, 자신의 트랜잭션 내에서 일어난 수정사항은 참조할 수 있습니다.)
데이터가 업데이트되는 쿼리에서는 데이터를 변경하는 도중 다른 트랜잭션이 같은 로우에 접근하면 그 트랜잭션이 끝난 후에 변경하려던 로우가 변경되었는지 확인해서 변경사항이 없으면 진행하고, 변경됐으면 에러가 나면서 트랜잭션이 롤백됩니다.
이 레벨을 사용하면 실패 시 재시도하는 것을 어플리케이션에서 처리해야합니다.
SERIALIZABLE
가장 단순하면서 엄격한 레벨입니다.
트랜잭션을 동시 처리 하지 않고, 하나씩 순차적으로 실행하는 것처럼 동작합니다.
트랜잭션 실패 시 어플리케이션에서 재시도를 처리해주어야 합니다.
데이터 정합성을 지키는 데는 좋지만, 성능저하가 심해진다. 잘 사용하지 않습니다.
참조
https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation
https://brownbears.tistory.com/272
https://www.postgresql.org/docs/current/transaction-iso.html