트랜잭션이란
여러 쿼리를 하나의 작업으로 묶어두는것을 트랜잭션이라고 한다. 돈거래로 예를 들어보겠다.
판매자 A와 구매자 B가 있다고 했을때 다음과 같은 작업이 일어난다.
-구매자 계좌에서 5000원 출금
-판매자 계좌에 5000원 입금
각각 쿼리로 나타낸다면 다음과 같다.
-UPDATE 구매자 계좌 5000원 빼기
-UPDATE 판매자 계좌 5000원 더하기
하지만 이 과정에서 하나의 쿼리만 실행된 후 서버가 다운되거나 오류가 발생한다면 큰 문제가 발생할 것이다.
이러한 상황의 발생을 막기 위해 나타난 것이 바로 트랜잭션이다.
위의 쿼리들을 하나의 작업으로 묶어 모두 실행되거나(커밋) 실행되지 않게 해주는 것이다(롤백). 이로 인해 시스템 상의 오류가 있거나 사용자가 오류를 범하더라도 데이터베이스의 데이터를 안전하게 보장할 수 있도록 해준다.
트랜잭션의 성질
이러한 트랜잭션이 안전하게 수행된다는것을 보장하기 위해 4가지의 성질이 있는데 각 앞글자를 따서 ACID라고 한다.
Atomicity(원자성)
트랜잭션은 모두 db에 반영되거나 반영되지 않아야 한다.
Consistency(일관성)
일관된 데이터 베이스의 상태를 유지해야 한다는 뜻으로, db에 있는 여러 제약 조건에 보장해 줘야 한다. 예를 들어 마이너스 통장이 허락되지 않는 제약 조건이 있을 경우 이 조건이 위배될 때 트랜잭션은 종료된다.
Isolation(독립성)
트랜잭션은 서로 각각의 독립성을 보장해야 한다는 의미로, 구매자의 계좌에서 돈이 빠져나가고, 판매자의 계좌에 아직 돈이 들어가지 않은 db상황을 다른 트랜잭션이 개입하면 안된다.
Durability(지속성)
트랜잭션이 성공적으로 완료되었다면, 그 결과는 영원히 반영되어야 한다는 의미로 구매자의 계좌는 5000원이 빠져나간채로 지속되어야 하고, 판매자의 계좌는 5000원이 입금된 상태로 유지되어야 한다는 뜻이다.
트랜잭션의 격리 수준
ACID성질은 트랜잭션이 이론적으로 보장해야 하는 성질로 실제로는 성능을 위해 이러한 성질이 완화가 되기도 한다. 예를 들어 독립성을 완벽하게 보장하려고 하면, 동일한 데이터에 100개의 연결이 접근시 각각의 연결을 순차적으로 해결해야 한다. 동시성이 매우 떨어지게 되어 이를 해결하기 위한 방법으로 트랜잭션 격리 레벨 설정이 있다.
트랜잭션 격리 레벨은 하나의 데이터에 여러개의 접근이 있을때 그 접근을 어떻게 제어할지에 대한 설정이다. 표준적으로 다음과 같은 4가지의 설정이 있다. 아래로 내려갈수록 격리 수준이 높아지지만 성능은 낮아지므로 상황에 맞게 잘 선택하는것이 중요하다.
READ_UNCOMMITTED
커밋 전의 내용을 다른 트랜잭션에서 읽는것을 허용
커밋되기 전에 읽으므로 롤백 됐을 경우 무효가된 데이터를 읽기 때문에 dirty read라는 문제가 발생하기도 한다.
READ_COMMITTED
커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회 가능하다.
이때 커밋이 완료된 값만 읽기 때문에 트랜잭션1이 수행되는 도중에 값을 읽는다면 트랜잭션2는 수행되기 전 값을 읽는다. 하지만 트랜잭션2에서 값을 두번 읽는 쿼리를 발생시킨다고 했을 때 하나의 쿼리는 트랜잭션 1이 커밋되기 전, 하나의 쿼리는 트랜잭션1이 커밋된 후인 경우 같은 쿼리를 두번 수행하였지만 결과는 다른 문제가 발생할 수 있다. 이를 Non-repeatable read라고 한다.
REPEATABLE-READ
READ-COMMITTED에서 한 트랜잭션이 조회한 데이터는 이 트랜잭션 내에서 반복적으로 조회하더라도 같은 값을 읽게 한다는 차이점이 있다.
하지만 여기서도 문제가 발생한다. 바로 phatom-read라는 non-repeatable read의 한 종류로 select문을 쓸때 특정 행이 읽혔다가 안읽혔다가 하는 문제이다. 다음 그림과 같이 볼 수 있다.
SERIALIZABLE
마지막으로 가장 높은 수준의 격리 단계로 한 트랜잭션에서 사용하는 데이터에 다른 트랜잭션에서 접근을 불가능하게 하는것이다. 가장 높은 수준의 격리 단계지만 가장 성능이 떨어진다. 해당 트랜잭션이 커밋될때까지 그 안에 있는 데이터는 모두 잠궈진다.
각 격리 수준별 발생할 수 있는 문제점은 다음과 같다.
트랜잭션 전파 타입
트랜잭션 전파타입은 트랜잭션이 시작하거나 참여하는 방법에 관한 설정이다.
다음과 같이 트랜잭션이 처리되는 과정 안에서 또 다른 트랜잭션이 처리되는 경우가 있다. 상위 트랜잭션이 있느냐 없느냐에 따라 타입별로 트랜잭션의 경계를 설정할 수 있다.
public class ServiceA {
private ServiceB serviceB;
...
@Transactional
public void a() {
serviceB.b();
}
}
public class ServiceB {
@Transactional
public void b() {...}
}
스프링에서 제공하는 전파 타입은 다음과 같이 총 7개가 있다.
상위 트랜잭션이 있을 때 | 상위 트랜잭션이 없을 때 | |
REQUIRED(기본값) | 상위 트랜잭션에 합류 | 새로운 트랜잭션 생성 |
MANDATORY | 상위 트랜잭션에 합류 | 예외발생 |
REQUIRES_NEW | 상위 트랜잭션은 상위 트랜잭션 사용, 하위 트랜잭션은 하위 트랜잭션 생성후 사용 | 새로운 트랜잭션 생성 |
SUPPORTS | 해당 트랜잭션 사용 | 트랜잭션 없이 진행 |
NOT_SUPPORTED | 상위 트랜잭션 보류 | 트랜잭션 없이 진행 |
NEVER | 예외발생 | 트랜잭션 없이 진행 |
NESTED | 중첩 트랜잭션 생성 | 새로운 트랜잭션 생성 |
결론적으로 스프링 트랜잭션은 앞서 설명한 여러 격리레벨과 전파타입을 제공하는데 중요한 데이터의 안전에 관한 설정이므로 케이스에 따라 문제가 발생하지 않도록 각 속성을 잘 이해하고 선택해야 한다.
'DB' 카테고리의 다른 글
MySQL 설치 및 DB 구축과정 미리 실습하기: DBMS 개요와 MySQL 소개 (0) | 2023.08.01 |
---|---|
정규화 (0) | 2023.01.09 |
데이터베이스 인덱스 (0) | 2023.01.07 |
데이터 베이스의 시작 (0) | 2023.01.06 |
Requires_new (0) | 2022.12.18 |