"관계형 데이터베이스 실전 입문 - 오쿠노 마키아"를 읽으며 매주 스터디 진행
목표
- 트랜잭션의 기본을 파악
- RDB에서 트랜잭션을 사용해 데이터 정합성을 보장하는 방법
14.1 트랜잭션
- 데이터를 올바르게 보장하기 위해 고안된 방법
- 실제 애플리케이션 개발에서는 관계형 모델(정규화)와 트랜잭션을 모두 구현해야 데이터의 정합성을 보장 가능
트랜잭션의 기능
- 트랜잭션이 필요한 상황
- DB 서버에 여러 개의 클라이언트로부터 동시에 엑세스가 발생하는 경우
- DB 서버 or 애플리케이션이 갱신 처리 도중에 중단되어 데이터 부정합이 발생하는 경우
- ex) 은행 계좌 입/출금 처리
- 잔액 100만원, A 트랜잭션은 30만원 입금 처리, B 트랜잭션은 20만원 출금 처리
- 트랜잭션 A 잔액 조회 : 100만원
- 트랜잭션 B 잔액 조회 : 100만원
- 트랜잭션 A 30만원 입금 처리 및 커밋 : A가 읽은 100만원에서 30만원 입금 처리 = 130만원
- 트랜잭션 B 20만원 출금 처리 및 커밋 : B가 읽은 100만원에서 20만원 출금 처리 = 80만원
- 최종 잔액 80만원
동시 실행 제어
- 위의 예제에서 발생한 문제는 A 트랜잭션과 B 트랜잭션이 데이터에 동시에 엑세스 하면서 발생한 문제이다. → 트랜잭션 처리가 되었다고 할 수 없다.
- 트랜잭션 처리를 통해 동시에 엑세스 하는 작업을 제어하는 것이 가능하다.
크래시 복구
- 작업을 처리하는 도중에 에러가 발생하는 경우 데이터를 올바르게 유지하는 것이 중요하다.
- 트랜잭션이 없는 경우 에러 발생 시 작업이 어디까지 처리되었는지 추적하는 것이 어렵다.
- 작업 추적을 위한 추가적인 로직을 개발해야한다.
- 트랜잭션은 비정상적으로 중단된 처리를 이전 작업으로 롤백하는 것이 가능하다.
- 트랜잭션은 DB 서버가 크래시돼도 재기동하면 필요한 복구 작업을 수행
트랜잭션의 키, 스케줄
처리의 병렬화는 필수불가결
- 애플리케이션 작업을 싱글 스레드 방식으로 처리하는 것은 데이터의 부정합이 일어나지 않지만 성능 효율이 낮아진다.
- 애플리케이션 작업을 멀티 스레드 방식으로 처리하는 것은 최근 개발에서 필수적이다.
- 실제 DB에 수백, 수천개의 트랜잭션이 동시에 수행되는 것이 일반적이다.
동시 실행 제한의 키, 스케줄
- 트랜잭션이 동시에 실행되는 경우 데이터가 정확하다고 보장하는 것은 스케줄이다.
- 트랜잭션은 일련의 작업을 정리한 것 → 데이터에 대한 읽기나 쓰기 조작을 분해 가능
- 각 트랜잭션의 순서를 지정하는 것이 스케줄
- 쓰기 처리의 경우 트랜잭션이 동시에 실행될 때 다른 트랜잭션이 읽은 데이터에 영향을 미칠 수 있다.
- 서로의 트랜잭션에 영향을 미치거나 간섭하는 지 파악하고 영향을 주지 않게 스케줄링 하는 것이 트랜잭션 병렬 처리의 주 목적
💭 데이터의 정확성
✔️ 데이터가 올바르게 유지되는 상태란 개별 트랜잭션을 한 개씩 차례로 실행할 때와 동일한 결과를 나타내는 경우
✔️ 개별 트랜잭션을 직렬로 실행할 때 나오는 결과 = 병렬로 실행해도 영향이 없는 것 = 데이터가 올바르게 유지됨
스케줄러의 성능
- 직렬화된 스케줄과 같은 결과가 나오는 스케줄은 조합에 따라 많이 존재할 수 있다.
- 가장 적합한 스케줄을 선택하는 것이 스케줄러의 임무
- 스케줄러의 성능을 고려 시 중요한 점
- 얼마나 많은 트랜잭션을 병렬화할 수 있는가? → 병렬화를 많이 할 수록 좋다.
- 최적의 스케줄을 찾는 데 필요한 비용
- 여러 개의 트랜잭션이 있을 때 동시에 실행할 수 있는 조합 중 최적의 조합을 찾는다면 조합을 찾는 비용이 많이 든다.
- 적당한 계산 비용으로 적당한 스케줄을 구하는 방식을 선택
14.2 트랜잭션의 특징
ACID란?
- ACID란 트랜잭션이 갖춰야할 필수 성질
원자성 (Atomicitiy)
- 트랜잭션에 포함된 모든 작업이 성공(Commit) 또는 실패(Abort)하는 성질
- SQL에서는 실패한 경우를 롤백(Rollback)이라고 한다.
- 애플리케이션은 트랜잭션이 처리된 이후 성공/실패 두 가지의 상태만 확인하면 되며, 에러 처리가 단순해진다.
- 원자성이 보장되지 않는 경우
- 트랜잭션 내 몇 번째 작업부터 다시 시작해야하는지 모른다.
- 원래 작업으로 돌려야 하는지에 대해 일일히 작성이 필요하다.
- 트랜잭션이 실패할 확률이 0%가 아니기 때문에 애플리케이션 단에서 실패 시 재처리 로직같은 추가적인 코드 작성이 필요 (리트라이)
일관성 (Consistency)
- 트랜잭션을 실시한 전후에는 데이터의 일관성이 손상되지 않아야 하는 성질
- 트랜잭션 실행 전 DB의 상태가 일관적 = 데이터의 부정합이 없는 것
- 트랜잭션 실행 후 DB의 상태가 일관적 = 데이터의 변경은 있지만 일관성은 유지하는 것
- 트랜잭션 실행 시 A상태의 일관성 → B 상태의 일관성으로 변경
- DB 자체가 데이터의 의미를 아는 것이 아니기 때문에 데이터의 일관성이 유지되었는지 판단하는 것은 애플리케이션이 해야할 역할
- 트랜잭션의 기능만으로 일관성을 보장하기 어렵다.
격리성 (Isolation)
- 동시에 실행하는 여러 개의 트랜잭션이 서로 영향을 주지 않는 성질
- 개별 트랜잭션의 실행 결과는 트랜잭션을 직렬로 실행했을 때와 결과가 같아야 한다.
- 격리성을 해치는 스케줄은 스케줄러가 제거, 문제 생기는 경우 롤백 수행 가능
영속성 (Durability)
- 일단 커밋이 완료된 트랜잭션이 손상되지 않는 성질
- 확정된 트랜잭션을 취소할 수 없다.
- 시스템 크래시가 발생해도 크래시 전에 커밋한 데이터로 복원 가능
다양한 비정상 상태
- ACID는 트랜잭션이 가져야 하는 성질
- 비정상 상태 (Anomaly = 이상현상)는 트랜잭션이 있어서는 안되는 상태
갱신 분실 (Lost Update)
- B 트랜잭션이 A 트랜잭션이 갱신하기 전의 데이터를 기준으로 갱신하는 경우 A 트랜잭션이 갱신한 데이터는 유실된다.
- ex) 하나의 계좌에서 입/출금이 이뤄지는 경우 (위에서 든 예시가 대표적인 갱신 분실 예)
- A 트랜잭션이 입금 처리 커밋
- B 트랜잭션은 A 트랜잭션이 입금 처리하기 전 데이터에 대해 갱신 처리 커밋
- A 트랜잭션이 갱신한 금액이 사라짐
모순적 판독 (Inconsistent Read)
- 어떤 트랜잭션의 실행 결과가 다른 트랜잭션의 실행 결과에 영향을 주면 데이터의 정합성이 보장되지 않는다.
- 한 트랜잭션이 값을 갱신하는 사이에 다른 트랜잭션이 어떤 것은 갱신 이전 값, 어떤 것은 갱신 이후 값을 읽어 데이터의 불일치가 발생하는 것
- A 트랜잭션이 1번, 2번 데이터를 읽고 1번 데이터에 대한 갱신
- A 트랜잭션 작업 중간에 B 트랜잭션이 1번(갱신 O), 2번(갱신 X) 데이터를 읽어 자신의 작업 처리
- A 트랜잭션이 2번 데이터 갱신 + 커밋 진행
- B 트랜잭션은 갱신 이전, 이후 값을 읽어 작업을 처리했으므로 데이터 부정합 발생
더티 리드 (Dirty Read)
- 아직 커밋되지 않은 데이터를 읽음으로써 발생하는 비정상 상태
- A 트랜잭션이 갱신한 이후 (커밋 전)에 B 트랜잭션이 해당 데이터를 읽을 때, A 트랜잭션이 실패해 롤백이 발생하는 경우 B 트랜잭션은 잘못된 데이터를 읽어서 사용하기 때문에 문제 발생
- A 트랜잭션이 데이터 갱신 (커밋 X)
- B 트랜잭션이 해당 데이터를 읽어서 자신의 작업 처리 (갱신 O, 커밋 X)
- A 트랜잭션에서 에러가 발생해 롤백
- B 트랜잭션은 롤백되기 전 데이터에 대해서 읽어서 작업을 처리해야 하지만, 실제로는 A에서 갱신이 일어난 잘못된 데이터를 읽어 작업 처리
논 리피터블 리드 (Non Repeatable Read)
- 한 개의 트랜잭션에 같은 데이터 항목을 여러 번 읽을 때 그 트랜잭션이 쓰기를 하지 않았어도 바뀐 값을 읽는 현상
- A 트랜잭션이 1번 데이터를 읽음 (커밋 이전)
- B 트랜잭션이 1번 데이터에 대해서 갱신 및 커밋
- A 트랜잭션이 1번 데이터를 읽을 때 B 트랜잭션이 갱신한 값으로 읽음
- 최초 A 트랜잭션이 읽은 데이터 ≠ 마지막에 A 트랜잭션이 읽은 데이터
팬텀 리드 (Phantom Read)
- 범위 검색에서 발생하는 문제
- 기존 데이터 항목 값에 이상을 일으키는 것이 아니라 과거에 없던 데이터가 나오는 것
- 전체 테이블 행이 5개인 행 기준
- A 트랜잭션이 테이블 내 데이터 전체 조회 → 5개의 행 조회 (커밋 이전)
- B 트랜잭션이 테이블에 1개의 데이터 추가 → 현재 테이블에는 6개의 행
- A 트랜잭션이 테이블 내 데이터 전체 조회 → 6개의 행 조회 (커밋)
- 최초 A 트랜잭션이 읽은 전체 데이터 개수 ≠ 마지막에 A 트랜잭션이 읽은 전체 데이터 개수
스케줄과 락
- 갱신 분실, 모순성, 더티 리드, 논 리피터블 리드, 팬텀 리드와 같은 이상현상은 트랜잭션의 동시성에서 발생할 수 있는 문제
- 이상현상이 발생하지 않도록 트랜잭션의 순서를 결정할 필요 존재 = 트랜잭션 스케줄
락킹 스케줄
- 쿼리의 실행에 따라 트랜잭션 내 데이터 일관성이 무너지지 않도록 작업 대상이 되는 행에 대해 그 작업이 수행되기 전에 락을 거는 방법
- 락을 이용해 경쟁 상태에 있는 트랜잭션 (동일한 데이터에 접근하려고 경쟁하는 트랜잭션)의 엑세스를 막아준다. → 데이터의 무결성이 유지된다.
- A 트랜잭션, B 트랜잭션 모두 데이터에 대해 갱신하려는 경우
- A 트랜잭션이 데이터에 대한 락을 걸음
- B 트랜잭션은 데이터에 엑세스 하지 못하고 대기
- A 트랜잭션이 이용하고 락을 해제
- B 트랜잭션은 데이터를 읽고 자신의 작업 처리
교착 상태 (데드락, Deadlock)
- 트랜잭션이 필요로 하는 데이터 항목을 순서대로 잠그는 아키텍처는 데드락 문제 발생 가능성 존재
- 행 (레코드)에 대한 락, 페이지 수준의 락이 구현된 경우 발생할 수 있다.
- A 트랜잭션이 1번 데이터에 대해 락을 걸고 작업 처리 (커밋 이전)
- B 트랜잭션이 2번 데이터에 대해 락을 걸고 작업 처리 (커밋 이전)
- A 트랜잭션이 2번 데이터를 사용하기 위해 락을 걸려고 시도하지만 실패
- B 트랜잭션이 1번 데이터를 사용하기 위해 락을 걸려고 시도하지만 실패
- A 트랜잭션, B 트랜잭션 모두 자신의 작업을 처리 하지 못한다.
- 데드락이 발생할 수 있는 경우 락킹을 사용하지 않으면 데이터 부정합이 일어날 수 있는 트랜잭션이다.
- 데드락 해결 방법
- 데드락 감지 시 양쪽 트랜잭션 모두 롤백, 한쪽만 롤백
- 어떤 기준으로 롤백할 트랜잭션을 결정할 것인가 ?
- 타임아웃까지 기다리는 방법
- 데드락에 대한 오류처리가 필요하다.
트랜잭션 격리 수준
- 트랜잭션의 격리 수준이 높아질 수록 직렬화된 스케줄과 동일한 결과를 보장할 확률이 높다.
- SERIALIZABLE의 경우 직렬화된 스케줄과 동일한 결과를 보장한다.
- 하지만, 락을 많이 사용해서 처리하기 때문에 병렬처리의 어려움으로 인한 성능 저하가 발생한다.
- 락을 많이 이용하기 때문에 데드락도 발생할 가능성이 많아진다.
- 격리 수준을 선정하는 가장 큰 키포인트는 애플리케이션 레벨에서 해당 격리 수준에서 발생할 수 있는 이상현상에 대응할 수 있는가이다
- SERIALIZABLE 이외 격리 수준은 명시적인 잠금을 통해 이상현상을 방지할 수 있다.
명시적인 잠금
select ... for update 구문을 의미한다.
select 하는 레코드에 쓰기 잠금(Exclusive-Lock, X-lock)을 거는 것
MVCC (Multi Version Concurrency Control)
- 락킹을 통한 직렬적인 방식은 병렬 처리가 되지 않기 때문에 성능 저하 발생
- 여러 개의 트랜잭션이 병렬적으로 처리하면서 어떤 트랜잭션이 갱신한 데이터에 대해 다른 트랜잭션은 갱신 이전 버전의 데이터를 읽어 동시성을 제어하는 기법
- MVCC의 이전 버전은 롤백 세그먼트 (UNDO 영역)에 저장된다.
- SELECT 쿼리가 발생할 때 실제 데이터 레코드에서 조회하는 것이 아닌 롤백 세그먼트에서 데이터를 조회한다.
- 갱신을 포함하지 않고 참조만 있는 트랜잭션은 MVCC를 이용해 데이터의 부정합이 발생하지 않으면서 성능을 개선할 수 있다.
크래시 복구
- 데이터의 영속성을 보장하기 위해 DB 서버가 크래시 되더라도 크래시 이전의 데이터로 복구해야한다.
- 커밋한 데이터가 크래시에 의해 손상될 수도 있다.
- 크래시 복구 절차
- 스테이블 로그를 이용해 로그 항목을 재생(REDO)하여 DB 캐시를 최신의 상태로 복원
- 크래시 한 순간에 완료되지 않은 트랜잭션에 의한 갱신을 취소 (UNDO)
- REDO, UNDO를 모두 실행해 크래시 복구 완료
DB 컴포넌트
스테이블 DB : 비휘발성 DB 스토리지
DB 캐시 : 휘발성 메모리에 있는 DB의 서브셋, 갱신 작업은 캐시에서 이뤄지고 스테이블 DB에 플러시된다.
스테이블 로그 : DB 캐시에서 수행된 작업의 내역을 기록한 것, 비휘발성 스토리지에 저장
로그 버퍼 : 스테이블 로그에 적재하기 전에 이용하는 버퍼
14.3 트랜잭션과 데이터 모델의 융합
관계형 모델과 ACID의 ⌜C⌟
- 트랜잭션을 실행하는 경우 DB는 A (일관성 유지) → B (일관성 유지)로 다른 일관성을 갖는 상태로 전환되는 것
- DB의 일관성을 보장하는 것은 애플리케이션에 달림
- 일관성 (제약조건, 규칙을 준수) = 트랜잭션 이후에 데이터가 제약조건, 규칙을 준수해서 표현됨
- 일관성 유지 판단을 위한 로직은 데이터 모델로 표현되므로 관계형 모델에 대한 이해가 필수
관계형 모델과 이상 현상
- 관계형 모델의 이상 현상 ≠ 트랜잭션에서 발생 가능한 이상 현상
- 데이터 부정합 관점에서는 동일
- 관계형 모델의 이상 현상 : 데이터 자체의 모순
- 트랜잭션의 이상 현상 : 같은 시간에 여러 작업 처리 수행 시 발생 문제
- 행의 데이터 변경, 행의 추가, 행의 감소 등
- 트랜잭션의 이상 현상이 발생하면 릴레이션 연산에 따른 결과가 올바르다고 보장 X
- 트랜잭션 이상 현상 해결 위해 SERIALIZABLE 격리 수준 설정, 참조만 하는 트랜잭션의 경우 MVCC 설정 등 릴레이션 연산이 올바른 결과를 보장하도록 설정 필요
정규화와 직교성
- 트랜잭션이 올바르게 처리되어 데이터가 갱신되더라도 실제 데이터의 중복으로 인해 데이터 부정합 발생 가능
- 트랜잭션이 이상현상 없이 처리되도록 설정 + 릴레이션의 정규화, 직교성을 구현 = 데이터 정합성 보장, 일관성 보장, 이상 상태 발생하지 않도록 줄이는 것이 가능
제약
데이터 모델만으로 충분하지 않은 이유
- 데이터 모델은 도구
- 애플리케이션 레벨에서 로직 미스로 인한 버그 발생, 데이터 모델의 잘못된 구현 등 의도하지 않은 작업이 처리될 수 있다.
- 이는 트랜잭션의 결과가 올바르지 않음을 의미
- 처리에서의 문제가 발생할 수 있기 때문에 정합성을 지키기 위해 노력 → 제약조건
제약을 활용해 데이터를 보호
- 제약 (Constraint) : 올바른 데이터가 어떤 방식으로 존재해야 하는지 표현
- 데이터 정합성을 보장하기 위해 이중, 삼중으로 방어 필요
- 정규화, 직교화를 이용한 관계형 모델의 올바른 구현
- 트랜잭션 작업의 이상현상 처리
- 제약을 이용한 애플리케이션 로직, 릴레이션 연산 등에서 발생할 수 있는 데이터의 부정합 방어
NOT NULL
- 1NF 테이블에는 NULL이 포함되면 안된다.
- NULL이 포함되지 않도록 하기 위해 NOT NULL 제약 조건 이용
고유성 제약
- 테이블에서 어떤 컬럼의 조합에 중복이 없음을 보장하는 제약조건
- 후보키가 될 수 있는 컬럼의 조합 모두에 기본키 또는 유니크 인덱스 작성
CREATE TYPE
- SQL에서 지원하는 타입보다 좁은 범위의 집합을 도메인으로 정의하거나 새로운 데이터 유형을 정의하는 경우 사용
CHECK 제약
- 컬럼의 데이터 범위를 세밀하고, 현시점의 테이블 상태에 맞춰 제약하는 조건
- ex) 한 컬럼 값에 YES, NO만 들어오도록 지정
- RDBMS마다 지원 상황 다름
외부키 제약
- 테이블 사이에 데이터 정합성을 확인하는 제약
- 외부키 제약을 통해 자식 테이블에 있는 키와 같은 값의 키가 부모 테이블에 존재하는 제약 표현 가능
- 외부키 제약 사용하지 않는 경우에는 애플리케이션 레벨에서 제약을 구현
- 외부키 제약을 이용하는 것보다 애플리케이션 레벨에서 구현하는 것이 성능이 떨어짐
- 쿼리 호출을 위한 네트워크, SQL의 해석, 운영 유지 등등..
트리거
- 외부키를 이용해 테이블 사이에 정합성을 보장하지 못하는 경우 존재
- 테이블의 키와 같은 값의 키가 다른 테이블에 존재하지 않는지 판단
- 테이블의 키와 같은 값의 키가 여러 테이블 중 하나만 존재하는지 판단
- 테이블의 키와 같은 값의 키가 N개이상 혹은 미만 존재 판단
- 트리거 : 테이블 사이의 복잡한 제약 표현 하는데 사용
- 행 트리거 : 행 마다 데이터 조작을 실행
- 문장 트리거 : SQL 문마다 실행
- 제약 표현에는 행 트리거가 적합
14.4 요약
- DB에서 가장 중요한 것은 데이터 정합성
- 데이터 정합성 유지를 위해 트랜잭션, 관계형 모델의 정규화, 직교화를 이용해 데이터 정합성 보장
- 제약, MVCC, 격리 수준, 락킹 등을 통해 데이터 정합성 추가 보장
반응형
'Computer Science > DB' 카테고리의 다른 글
웹 응용프로그램을 위한 데이터 구조 (DB 스터디 6주차) (0) | 2022.11.02 |
---|---|
인덱스 설계 전략 (DB 스터디 5주차) (0) | 2022.10.21 |
이력 데이터와 친해지기 (DB 스터디 4주차) (0) | 2022.10.21 |
SELECT를 공략하자 (DB 스터디 4주차) (0) | 2022.10.21 |
정규화 이론(두번째) - 결합 종속성 (DB 스터디 3주차) (0) | 2022.09.28 |