DB 락에 대해서 정리하고 싶은 생각이 문득 들어서, 정리를 하려다보니 앞서 DB 트랜잭션에 대해 알 필요가 있다는 생각이 들었다.
회사에서 여러가지 문제에도 부딪혔엇고, 문제 해결에 개념 적용도 했었지만 (곧 추가 코멘트 예정) 여전히 제대로 알고 있다는 생각이 들지 않았기에, 이참에 정리하자는 생각이 들었다.
트랜잭션(Transaction)이란?
데이터베이스의 상태를 변경시키는 작업의 단위이다.
•
사용자 입장에서는 작업의 논리적 단위
•
시스템의 입장에서는 데이터들을 접근 또는 변경하는 프로그램의 단위
트랜잭션의 단위는 나누어지지 않는 최소한의 단위라고 정의한다.
트랜잭션 중 오류가 났다면 트랜잭션 내 작업을 모두 Rollback하여 아무 작업도 이루어지지 않은 Nothing 상태로 전략을 취한다. (All Or Nothing)
# 예시
# 출금 -> 입금 -> 기록
# 만약 나의 계좌에 출금한 상태로 입금하지 않은 상태로 오류가 난다면? -> Nothing
# 만약 출금 및 입금이 완료되었는데 기록이 되지 않는다면? -> Nothing
BEGIN TRANSACTION;
-- 계좌에서 출금
UPDATE bank_accounts
SET balance = balance - 100000
WHERE account_id = 'A123';
-- 다른 계좌로 입금
UPDATE bank_accounts
SET balance = balance + 100000
WHERE account_id = 'B456';
-- 거래 내역 기록
INSERT INTO transactions (
from_account,
to_account,
amount,
transaction_date
) VALUES (
'A123',
'B456',
100000,
CURRENT_TIMESTAMP
);
COMMIT;
SQL
복사
트랜잭션의 특징(ACID)
위와 같은 작업의 단위로 정의되었기 때문에, 크게 4가지 특성을 가진다.
•
Atomicity(원자성)
◦
트랜잭션의 결과가 모두 DB에 반영되거나, 전혀 반영되지 않아야 한다.
•
Consistency(일관성)
◦
트랜잭션 수행 전과 수행 후의 시스템 고정 요소 상태는 같아야 한다.
▪
송금 시 금액의 데이터 타입이 변경될 수 없음(ex. integer → string)
•
Isolation(격리성)
◦
둘 이상의 트랜잭션이 동시에 실행되고 있을 때, 한 트랜잭션이 다른 트랜잭션에 접근하거나 영향을 끼칠 수 없다.
•
Durability(영속성)
◦
트랜잭션이 성공적으로 완료되었다면, 결과는 영구적으로 저장된다.
트랜잭션의 연산
1.
COMMIT
•
트랜잭션이 성공적으로 수행되었음을 선언
•
결과를 최종 DB에 저장 반영한다.
2.
ROLLBACK
•
트랜잭션이 실패했음을 선언
•
DB를 트랜잭션 수행 전과 일괄된 상태로 되돌린다.
트랜잭션의 상태
1.
Active
•
트랜잭션 중인 상태
2.
Partially Committed
•
COMMIT 명령이 도착한 상태
•
COMMIT 이전 SQL문들이 수행되고, COMMIT 만 남은 상태를 말한다.
3.
Failed
•
더이상 트랜잭션이 정상적으로 진행될 수 없는 상태
4.
Committed
•
트랜잭션 완료 상태
5.
Aborted
•
트랜잭션 취소 상태
•
트랜잭션 수행을 실패하고, ROLLBACK 연산을 실행한 상태
트랜잭션 격리성
기본적으로 실행 중인 트랜잭션의 중간결과를 다른 트랜잭션이 접근할 수는 없다.
하지만 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정할 수 있다.
강한 격리성을 둘 수도 있지만, 반대로 약하게 격리성을 설정할 수 있다.
트랜잭션 격리성으로 인한 나타날 수 있는 문제점
•
Dirty Read
특정 트랜잭션에서 COMMIT 되지 않은 데이터를 읽을 수 있다
•
Not Repeatable Read
한 트랜잭션 내에 같은 데이터를 두번 이상 읽을 경우 다른 결과 (Undo Changes 로그에서 가져와서 읽기 때문)
•
Phantom Read
한 트랜잭션 내에 같은 쿼리를 두번 수행했을 때, 첫 번째 쿼리에서 존재하지 않던 데이터(Phantom) 레코드가 두 번째 쿼리에서 나타남
트랜잭션 격리 수준
아래로 갈 수록 고립 정도가 높아지며 성능이 떨어진다!
•
READ UNCOMITTED
◦
Dirty Read 발생 가능성 있음
◦
정합성에 문제가 많기 때문에 사용하는 것을 권장하지 않는다.
▪
그럼 도대체 언제 사용하길래 만들어진 격리 수준일까?
•
실시간 모니터링/분석도구
•
성능이 중요하고 데이터 정확성이 덜 중요한 경우
•
READ COMITTED
◦
Oracle의 Default Lock
◦
특정 트랜잭션이 커밋되어 확정된 데이터만, 다른 트랜잭션이 해당 데이터를 읽도록 허용
▪
Dirty Read의 발생가능성을 막을 수 있다.
▪
Non-Repeatable Read 발생가능
▪
Phantom Read가 발생가능
•
REPEATABLE READ
◦
MySQL의 Default Lock
◦
특정 트랜잭션에서 발생한 변경에 대해 Undo 로그에 넣어두고, 다른 트랜잭션에서는 해당 변경사항이 아닌 Undo 로그에 있는 데이터를 읽게 한다.
Non Repeatable Read 오류 해소 할 수 있음
•
SERIALIZABLE
◦
가장 단순하지만, 가장 엄격한 격리 수준
◦
완벽한 읽기 일관성 모드 제공
▪
Phantom Read가 발생하지 않는다. 하지만 거의 사용되지 않는다고..
◦
동시 처리 성능이 가장 낮다
정리
트랜잭션은 DB 작업단위이기 때문에 테이블/스키마 단위로 격리할 수는 없다.
Isolation Level | Dirty Read | Non Repeatable Read | Phantom Read |
Read Uncomitted | O | O | O |
Read Comitted | O | O | |
Repeatable Read | O | ||
Serializable |
Ref:
트랜잭션에 대해서는 알 것 같으면서도 복잡한 문제에 부딪혔을 때에는 전혀 연관성을 떠올리지 못 했다.
코드레벨에서만 트랜잭션을 적용시키다보니, 동시성에 있어 고려하지 못한 개념들이 참 많다는 생각이 들었다. (하지만 DB레벨이라 함부로 건들 수는 없긴하다.)
하지만 Lock은 여러 레벨에서 설정이 가능하기 때문에, 다음 글은 DB Lock에 관하여!