트랜잭션
트랜잭션, DB가 처리하는 명령어의 최소 단위로 데이터를 조작하고 명령어를 내리는 조작 단위이다.
트랜잭션을 한국어로 번역하면 “거래” 이다. 즉 하나의 거래를 안전하게 처리하도록 보장해주는 작업이다.
실생활의 예시로 이해를 해보자. 계좌이체를 하는 상황을 생각해보고, A가 B에게 1만원을 보내야 한다고 가정해보자.
만약 트랜잭션이 없을 경우,
(1) A가 B에게 1만원 보냄.
--중간 처리과정에서 오류 발생--
(2) B는 1만원을 받지 못함
A 잔고: -10,000원 / B 잔고: 유지
트랜잭션이 있다면, 트랜잭션 기능으로 commit과 rollback의 작업 가능하도록 한다.
위의 상황에서 (1)번은 성공했지만 중간 처리과정의 오류로 (2)번에서는 실패했을 때 거래 전의 상태로 돌아갈 수 있도록 하는 작업이 Rollback이며, (1)번도 성공하고 (2)번도 성공하는 경우 Commit이라고 한다.
트랜잭션이 커밋을 호출하기 전까지는 데이터를 임시로 저장하고 있다가 성공 여부에 따라서 Commit이나 Rolllback을 하는 것이다.
DB 서버는 내부에 세션을 만들고 모든 요청을 세션을 통해서 실행한다. 세션은 트랜잭션을 시작하고 커밋이나 롤백을 통해서 트랜잭션을 종료한다. 커밋에도 자동 커밋과 수동 커밋이 있는데, 자동 커밋을 하면 본래 트랜잭션을 사용하는 의미를 상실할 수 있다. 트랜잭션 기능을 제대로 수행하려면 수동 커밋을 사용하는 것을 권장하는 편이다.
트랜잭션 상태
- Active: 실행 중
- Failed: 오류 발생으로 중단
- Aborted: 비정상적으로 종료되어 롤백
- Partially Committed: 마지막까지 실행되고 커밋 직전의 상태
- Committed: 성공적으로 종료되어 커밋 연산 실행 한 후의 상태
트랜잭션의 특징 (ACID)
트랜잭션은 원자성, 일관성, 격리성, 지속성을 보장해야한다. 4가지의 특징의 앞글자를 따서 ACID로 부른다.
- 원자성(Atomicity): 트랜잭션 내 작업들은 하나의 작업인 것처럼 모두 성공하거나 실패해야한다. All or Nothing.
- 일관성(Consistency): 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야한다.
- 독립성/격리성(Isolation): 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 독. 동시에 같은 데이터를 수정하지 못하도록 해야함
- 지속성(Durability): 트랜잭션을 성공적으로 끝내면 결과가 항상 기록되어야 한다.
트랜잭션 격리 수준
트랜잭션의 특징인 격리성에는 격리 수준 4가지가 존재한다. 트랜잭션 작업이 동시에 이루어질 때 변경 중인 데이터에 대한 접근 허용 여부 등을 결정한다.
- READ UNCOMMITED: 다른 트랜잭션에서 커밋되지 않았더라도 변경된 데이터를 조회할 수 있다.
- READ COMMITTED: 트랜잭션이 시작되기 전 커밋된 내용이 반영된 데이터에 대해서만 조회할 수 있다. 트랜잭션 실행 도중에 다른 트랜잭션에서 커밋이 되었다면 변경된 데이터를 조회할 수 있다. 일반적인 웹 비즈니스 로직에서 많이 사용
- REPEATABLE READ: 트랜잭션 내에서 조회하는 데이터에 대해 공유락(리소스의 WRITE 제한)을 걸어서 잠금된 데이터의 변경 불가능이 보장된다. 트랜잭션들은 번호를 갖고 자신보다 낮은 트랜잭션 번호에서 커밋된 내용만 사용한다. RDBMS에서 대부분 이를 기본으로 사용한다.
- SERIALIZABLE: 트랜잭션 내에서 SELECT된 자원들은 공유 잠금이 된다. 공유 잠금된 데이터는 수정 및 입력 불가능이 보장된다. 4가지 중에서 가장 단순하면서 가장 엄격한 관리 수준이다.
민감하고 엄격한 데이터라면 Transaction의 격리 수준을 높이고 그렇지 않을 경우에는 격리 수준을 완화시키는 것이 좋다.
프로젝트에서 사용하고 있는 PostgreSQL에서 트랜잭션 격리수준의 기본값은 READ COMMITTED이다.
트랜잭션 격리 수준은 트랜잭션을 시작할 때 설정해줄 수 있다.
await queryRunner.startTransaction('READ COMMITTED');
프로젝트에 적용 (with Nest.js, TypeORM)
진행 중인 프로젝트에서 백엔드 파트를 담당하고 있고 Nest.js, TypeORM을 RDBMS는 PostgreSQL을 다루고 있다.
Service 계층에서 트랜잭션을 적용하고 있으며 4가지 특징들을 보장하고 있는지 확인해보려 한다.
클라이언트로 응답해야하는 데이터
1) totalCalories 2) totalNutrient 3) targetCalories 4) recommendNutrient
여기서 1)과 2)는 cumulative-record 테이블에서, 3)과 4)는 health-info 테이블에서 데이터를 가져와야 한다
async getDateRecord(date: Date, userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// [Cumulative Table] - 1) totalCalories, 2) totalNutrient
let totalResult = await this.cumulativeRepository.getDateRecord(
date,
userId,
queryRunner.manager
);
totalResult = plainToInstance(CumulativeRecordDateDto, totalResult);
// [HealthInfo Table] - 3) targetCalories, 4) recommendNutrient
const HealthInfoResult = this.healthInfoRepository.findHealthInfoByUserId(
date,
userId,
queryRunner.manager
);
await queryRunner.commitTransaction();
const result = {
totalResult,
HealthInfoResult,
};
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
- 4가지의 데이터를 모두 클라이언트로 한 번에 보내줘야 하기 때문에 하나의 트랜잭션에서 동작하도록 해야한다 -> 원자성 보장
- 테이블의 컬럼, 값, 타입 들은 모두 일관되게 정의되어있고 적용된다 -> 일관성 보장
- 동시에 여러 요청이 들어와서 각 트랜잭션이 동시에 실행이 된다고 해도, 조회되는 데이터들은 각 user 마다의 개별 정보이기 때문에 트랜잭션들이 서로 영향을 미치지 않는다 → 독립성 보장
- 트랜잭션이 성공적으로 끝나면 commitTransaction 함수로 커밋을 하고 release 함수로 연결을 종료해서 DB에도 반영이 된다 -> 지속성 보장
에러 발생
DB에서 데이터를 삭제하는 DELETE 쿼리를 구현했는데, 아래처럼 영향을 받은 row가 하나 있다고는 출력이 되었지만 테이블에서는 데이터가 삭제되지 않는 문제가 발생했다.
쿼리에서 문제가 있나? 싶어서 한참을 삽질 하다보니 GPT가 트랜잭션이 올바르게 이루어지지 않아서 발생한 문제인 것 같다고 알려주었다. 그제서야 repository 파일이 아닌 service 파일에서 코드들을 확인해보니 트랜잭션 커밋 코드를 빼먹었다는 것을 알 수 있었다.
교훈: 커밋 필수 !!! 커밋되지 않으면 트랜잭션이 정상적으로 동작하지 않아서 DB에도 반영이 되지 않는다 !!
참고자료
https://sjh9708.tistory.com/40
'IT Study > DB' 카테고리의 다른 글
[Programmers SQL] WITH RECURSIVE문 (1) | 2024.10.22 |
---|---|
[DB] 인덱스 개념, 종류 및 적용(feat. PostgreSQL) (0) | 2024.01.28 |
[TypeORM] QueryBuilder | SELECT 절에서 as 사용하기 (with. getMany vs getRawMany) (0) | 2024.01.24 |
[SQL] 데이터조작어(DML) (Feat. Elice 12주차) (0) | 2023.11.11 |
[SQL] 제약조건 추가 및 삭제 쿼리 (Feat. Elice 12주차) (0) | 2023.11.07 |