트랜잭션이 종료될 때까지 “한 커넥션”을 계속 사용한다.
- 그리고 일반적으로 “한 커넥션”은 “하나의 스레드”가 점유하여 처리한다.
트랜잭션과 스레드의 관계
동작
- 트랜잭션은 일관성과 원자성을 보장하기 위해 시작(BEGIN) 부터 종료(COMMIT or ROLLBACK)를 하나의 작업 단위로 묶음.
- 트랜잭션이 종료되기 전까지는 다른 작업이 해당 커넥션을 사용할 수 없음.
커넥션은 트랜잭션 동안 고정
- 트랜잭션이 시작되면, 애플리케이션은 “한 커넥션”을 고정하여 쿼리를 시작함.
- 트랜잭션이 끝날 때까지 해당 커넥션은 다른 스레드나 작업에 할당되지 않음.
하나의 스레드는 하나의 커넥션을..
- 하나의 스레드는 하나의 커넥션을 점유한다.
- 커넥션 풀을 사용한다면?
- 트랜잭션이 종료된 후, 해당 커넥션은 풀로 반환 → 다른 스레드가 다시 커넥션을 점유함.
만약 “한 가지의 작업” 에 “두 가지의 트랜잭션”이 동시 실행 된다면?
각각 트랜잭션은 “별도의 커넥션” 과 “별도의 스레드”를 사용한다.
- 첫 번째 트랜잭션이 끝난 후, 두 번째 트랙잭션이 시작된다면 → 한 개의 커넥션을 사용.
- 그러나 두 트랜잭션이 “동시 실행” 된다면(중첩 트랜잭션) → 별도의 트랜잭션과 커넥션을 사용.
- 두 트랜잭션이 동시 사용하려면 일반적으로 비동기 호출을 활용해야 함.
- DB 서버 내에서도 한 트랜잭션은 “하나의 스레드”로 실행 된다.
그럼 트랜잭션이 “프로세스”로 실행되는 경우도 있을까요?
- 특정 DB 에서 트랜잭션을 프로세스 기반으로 사용하는 경우.
- PostgresSQL 의 경우 트랜잭션은 프로세스 기반으로 실행.
- 트랜잭션 자체는 스레드 기반으로 처리하더라도, 특정 쿼리(대규모 집계 작업, 병렬 처리 작업 등)은 별도의 프로세스로 분리할 수 있음.
- 대규모 트랜잭션에서 병렬 쿼리가 활성화 되면, DB 는 여러 프로세스를 사용하여 작업을 나눠서 처리.
병렬 쿼리에서 트랜잭션이 여러 프로세스로 나뉘는 경우
- 병렬 쿼리 : 하나의 쿼리를 여러 프로세스(스레드)로 나눠 동시에 처리하는 방식.
- 대량 데이터 처리, 집계 작업을 빠르게 할 때 사용.
- 트랜잭션이 하나여도 실제 쿼리 작업은 여러 프로세스로 병렬 처리 됨.
예시
SELECT /*+ PARALLEL(employees, 4) */ department_id, SUM(salary)
FROM employees
GROUP BY department_id;
- Oracle 에서는 직접 병렬 쿼리를 실행할 수 있다.
- 4개의 프로세스를 사용해 department_id 가 같은 행들의 salary 의 합계를 구하는 것.
- 예를 들어 100만 개의 데이터가 있다면, 각 25만 개의 데이터를 읽는다.
MySQL 은?
- 애플리케이션에서 수동으로 처리할 수 있지만!
- MySQL 의 InnoDB 는 옵티마이저가 내부적으로 최적화하여 병렬 처리를 수행할 수 있다.
- 자동으로 최적화되기 때문에 사용자가 직접 병렬 실행을 제어할 순 없지만
- 데이터 크기, 쿼리 복잡도, 시스템 리소스 등을 고려하여 내부적으로 작업을 병렬화 시킨다.
- 만약 파티셔닝 을 이용한다면 데이터를 논리적으로 나누는 것이니, 쿼리 성능을 향상시킬 수 있음.
- 예) department_id 기준으로 4개의 파티션으로 나눈 후 집계(SUM) 쿼리를 실행한다면, 파티션을 기준으로 4개의 프로세스가 실행되어 집계 쿼리를 실행 시킨다.
💡하나의 트랜잭션은 스레드로 네트워크, 커넥션을 점유하여 끝날때까지 한 커넥션으로 계속 실행 됨.
트랜잭션 격리 수준 Isolation Level
READ UNCOMMITED
- 커밋되지 않은 데이터들도 다 보인다.
- Dirty Read 가 발생한다.
- 커밋되지 않은 데이터들도 다 보여서 일관성 문제가 발생한다.
READ COMMITED
- 커밋된 데이터만 보인다.
- Non-Repeatable Read 가 발생한다.
- 하나의 트랜잭션에서 계속 똑같은 결과가 보여야 하는데, 다른 트랜잭션이 커밋되어버리면 갑자기 다른 데이터가 보여짐.
- 반복해서 어떤 걸 읽었을 떄 갑자기 바뀐다. (이 트랙잭션의 결과와 상관없이 조회가 다르게 됨.)
예시 )
두 트랜잭션이 있고, 한 트랜잭션이 데이터를 업데이트하는 상황일 때
- TX_1 , TX_2 가 있고 TX_2 가 데이터를 업데이트하려고 함.
- TX_2 에서 데이터를 업데이트 함. (커밋 전)
- TX_1 에는 TX_2 커밋 이전이기 떄문에, 업데이트가 되지 않은 상황이 조회 됨.
- TX_2 → COMMIT
- TX_1 에서 똑같은 데이터를 조회했으나, TX_2 커밋 이후이므로 데이터가 다르게 조회 됨.
예시 상황
# Transaction 1
select *
from member
where name = '강휴일';
: 결과 로우 0 건
# 이 때 다른 Transaction 2 가 로우를 업데이트 한다.
update member
set name = '강휴일'
where id = 1;
commit; # T2 커밋
# T1
select *
from member
where name = '강휴일';
: 결과 로우 1 건 (T2 에서 데이터를 업데이트 후 커밋되었기 때문에, 같은 트랜잭션에서 조회가 다른 상황 발생)
REPEATABLE READ
- 같은 트랜잭션 내에서 실행되는 모든 조회 쿼리는 자기 트랜잭션보다 번호가 작은 트랜잭션에서 변경한 데이터만 보인다.
- MySQL 기본 격리 수준.
- 그러나 PHANTOM READ 발생 가능성.
- 다른 트랜잭션에서 변경한 작업에 의해 로우가 보였다 안 보였다 함.
- 갑자기 없었던 로우가 생겼다.
예시 )
TX_1 이 먼저 시작한 트랜잭션이고, TX_2 가 이후에 시작한 트랜잭션이라고 가정.
- TX_2 이 데이터를 조회하고 데이터를 추가. (커밋 전)
- TX_1 에서는 조회했으나, 언두 로그(이미 존재하는 데이터)에 있는 데이터만 보임.
- TX_2 → COMMIT
- 언두 로그는 “이미 존재하는 데이터”에 대한 상태만 보장하고, “새롭게 삽입한 데이터”에 대한 보장은 해주지 않기 때문에, TX_1 에서는 갑자기 TX_2 가 추가한 새로운 데이터가 보임.
예시 상황1 : 언두 로그 이용
# Transaction 1
select *
from member
where name = '휴일';
: 결과 로우 1 건
# 이 때 다른 Transaction 2 가 로우를 추가 한다.
update member
set name = '평일'
where id = 1;
commit; # T2 커밋
# T1
select *
from member
where name = '휴일';
: 결과 로우 1 건
(T2 에서 데이터를 업데이트 하더라도, T2 트랜잭션 번호가 더 높기 때문에 T1 에서는 T2의 업데이트 상황이 보이지 않는다.
예시 상황2 : 팬텀 리드 발생
# Transaction 1
select *
from member
where salrary > 50000
: 결과 로우 0 건
# 이 때 다른 Transaction 2 가 로우를 추가 한다.
insert into member ('강휴일', 50001);
commit; # T2 커밋
# T1
select *
from member
where salrary > 50000
: 결과 로우 1 건 (T2 에서 데이터를 추가 후 커밋되었기 때문에, 같은 트랜잭션에서 조회가 다른 상황 발생)
💡결론 : Repeatable read 에서 사용하는 Undo Log 는 “이미 존재하는 상태”의 데이터만 보장하고, 새로운 데이터가 추가되는 것에 대한 제어는 보장하지 않는다.
SERIALIZABLE
- 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에선 절대 접근할 수 없다.
- 읽기 작업도 공유 잠금(읽기 잠금)을 획득해야 하며, 동시에 다른 트랜잭션은 레코드를 변경하지 못한다.
- 동시 처리 성능이 다른 트랜잭션에 비해 매우 떨어진다.
- MySQL InnoDB 에서는 Lock 을 통해 관리하기 때문에 굳이 Serializable 을 사용하지 않아도 non-Repeatable read 가 발생하지 않는다고 하는데… 이건 더 알아봐야 할듯.
ACID
- Atomicity(원자성) : 모든 작업이 “전부 실행” 되거나 “전혀 실행되지 않아야” 한다.
- Consistency(일관성) : 트랜잭션 전 후의 데이터베이스는 항상 일관 된 상태를 유지해야 한다.
- Isolcation(고립성) : 동시에 실행되는 트랜잭선은 서로 간섭하지 않는다.
- 쿼리를 잘못 짜면 흔히 발생..
- Durability(내구성) : 트랜잭션이 커밋 된 후에는 오류가 발생하더라도 데이터는 영구적으로 보존되어야 한다.
A : 원자성
모든 작업이 “전부 실행” 되거나 “전혀 실행되지 않아야” 한다.
- 휴일이가 철수 통장으로 계좌 이체를 하면 휴일이 통장에 -1,000 / 철수 통장에는 +1,000 이 되어야 한다.
- 휴일이 통장에 -1,000 했을 때, 시스템 오류가 발생했을 경우 휴일이 통장에서는 돈이 빠져나갔지만 철수 통장에는 +1,000 이 되지 않으면 안 된다. 그러므로 ROLLBACK 되어야 한다.
- 휴일이가 통장에 돈이 출금되었다면 반드시 철수 통장에도 돈이 입금되어야한다.
C : 일관성
트랜잭션 전 후의 데이터 베이스는 항상 일관 된 상태를 유지해야 한다.
- 트랜잭션 이후 문자열 컬럼에 숫자 데이터가 들어가거나, 갑자기 없던 컬럼이 추가되면 안 된다.
- 에러 상황에 대한 이야기로, 절대 발생하면 안 되는 상황.
I : 고립성
동시에 실행되는 트랜잭션은 서로 간섭하지 않는다.
- 쿼리를 잘못 짜면 흔히 발생하는 오류이다.
- TX_1 이 실행될 때, TX_2 의 영향을 받으면 안 된다.
- TX_1 이 아직 커밋하지 않은 TX_2 의 데이터를 조회하여 실행 하던 중 TX_2 가 롤백 될 경우.
- TX_1 이 읽은 데이터는 잘못 된 정보가 되어 예상하지 않은 결과를 낼 수 있다.
D : 내구성
트랜잭션이 커밋 된 후에는 오류가 발생하더라도 데이터는 영구적으로 보존되어야 한다.
- 예를 들어, DB 서버가 다운됐을 때에도 커밋된 데이터라면 데이터는 영구적으로 보존되어야 한다는 것을 뜻함.
내구성을 보장하기 위한 방법들
- Write-Ahead Logging : 트랜잭션의 변경 사항을 적용하기 전에, 로그 파일에 먼저 기록.
- 장애가 발생해도 로그 파일을 이용해 복구 가능.
- RAID : 데이터를 복제하여 다른 디스크에 저장.
- 백업 및 복원 : 주기적으로 DB 스냅샷을 저장하여 장애 발생 시 복원.
- 장애 시점 이후에는 WAL , RAID 등에서 저장된 데이터들을 복구 함.
- Replication(복제) : DB 변경 내용을 다른 서버에 복제하여 데이터 손실 방지.
- 트랜잭션이 모든 복제본에 기록될 때까지 커밋을 완료하지 않음. (동기화 시킴)
- 장애가 발생해도 손실 없음 !
728x90
'개발공부 개발새발 > DB' 카테고리의 다른 글
DB ) 모두 성공하지 않으면 모두 실패한다. 트랜잭션입니다. (0) | 2024.11.24 |
---|---|
MySQL ) InnoDB 스토리지 엔진 (0) | 2023.08.31 |
MySQL ) MySQL 엔진 (0) | 2023.08.31 |
MySQL ) MySQL 아키텍처 ?! (0) | 2023.08.31 |
MySQL ) 사용자 및 권한과 역할 (0) | 2023.08.18 |