개발자 끄적끄적
트랜잭션(transaction) 본문
<트랜잭션(transaction)이란?>
- 일반 응용 프로그램의 구성
- 명령어들의 집합(함수나 클래스 단위로 구성)
- 데이터베이스 응용 프로그램의 구성
- 업무처리에 관련된 세부 프로그램 단위
- 세부 프로그램들은 데이터베이스에 대한 연산(검색/삽입/삭제/수정)으로 구성
- 예를 들어 은행에서 계좌이체, 대출, 예금, 출금 등의 업무
- 트랜잭션
- 논리적인 작업 단위를 구성하는 연산들의 집합
- 실행 중 멈추거나 중단되지 않는 최소 작업 단위
- 데이터베이스 응용 프로그램은 트랜잭션의 집합
<트랜잭션의 필요성>
- 대부분의 DBMS들은 이러한 상황을 방지하기 위한 기능을 갖춘다
- 데이터베이스 개발자는 작업 단위들을 트랜잭션으로 적절히 정의해야 한다
- 즉, 트랜잭션을 정의하는 것은 전적으로 개발자의 의무
<트랜잭션이 지켜야할 조건>
- ACID 특성(ACID property)
- 원자성(Atomicity)
- 트랜잭션은 중간에 멈출 수 없다
- 일관성(Consistency)
- 트랜잭션 실행 전후 데이터베이스 내용이 일관되어야 한다
- 고립성(Isolation)
- 트랜잭션이 실행하는 과정에서 갱신한 데이터는 트랜잭션이 완료될 때 까지 다른 트랜잭션이 참조할 수 없다
- 지속성(Durability)
- 트랜잭션이 성공적으로 완료되면 그 트랜잭션이 갱신한 데이터베이스의 내용은 영구적으로 저장되어야 한다
<원자성>
- 트랜잭션은 성공하거나 실패하거나 둘 중의 하나
- ex) A, B 모두 잔액이 400원이고 100원을 이체한다고 가정
- 트랜잭션이 성공적으로 실행되는 경우
- A 계좌의 잔액 : 300원
- B 계좌의 잔액 : 500원
- 트랜잭션이 실패하는 경우
- A 계좌의 잔액 : 400원
- B 계좌의 잔액 : 400원
- 원래의 상태로 되돌려 놓아햐 한다
<일관성>
- 트랜잭션의 완료 후에도 데이터베이스가 일관된 상태로 유지
- ex) 계좌이체 과정에서의 데이터베이스 일관성
- 계좌이체 전 후 두 계좌의 잔액의 합은 일정
- 일관성 여부는 사용자가 판단하고 정의해야 한다
- 일관성이 유지되도록 트랜잭션을 정의하는 것은 데이터베이스 개발자의 책임
- 계좌이체를 인출, 입금 트랜잭션으로 각각 분리할 경우
- 인출 트랜잭션 종료 후 두 계좌 잔액의 합이 달라진다 -> 일관성이 깨진다
<고립성>
- 트랜잭션이 실행하는 도중에 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못하게 하는 특성
- 고립성을 해결하려면?
- 각 트랜잭션을 순차적으로 실행
- 다중 프로그래밍(multiprogramming) 환경에서 트랜잭션들을 순차적으로 실행하는 것은 성능 면에서 많은 문제가 발생
- 따라서, 트랜잭션을 동시에 실행하면서 상호간에 간섭이 일어나지 않도록 하는 기법이 필요하다
- 고립성이 만족되는지 확인하려면?
- 동시에 실행하는 트랜잭션들의 실행 결과가 순차적으로 실행된 결과와 동일한지를 확인
<지속성>
- 트랜잭션 완료 후 그 결과가 영구적으로 보존되어야 한다
- 트랜잭션이 완료되면?
- 주기억장치가 아닌 디스크와 같은 보조기억장치에 저장
- 또는 시스템 장애가 회복되고 난 후에 어떠한 형태로든지 그 데이터를 복구할 수 있어야 한다
<트랜잭션의 상태>
- 동작(active)
- 트랜잭션이 시작되고 연산들이 정상적으로 실행 중인 상태
- 부분완료(partially committed)
- 트랜잭션에 정의된 모든 연산의 실행이 끝난 상태
- 완료(committed)
- 트랜잭션이 성공적으로 종료된 상태
- 실패(failed)
- 트랜잭션이 완료되지 못하고 더 이상 실행되지 못하는 상태
- 중단(aborted)
- 트랜잭션이 실패 한 후 실행되기 이전으로 복귀된 상태
<동시성 제어(concurrency control)>
- 사용자 수에 따른 DBMS의 구분
- 단일 사용자 DBMS
- 다중사용자 DBMS
- 은행, 항공기 예약 등
- 동시성 제어
- 다중 사용자 DBMS에서 필요한 기법
- 하나의 트랜잭션이 완료되지 않은 상태에서 다른 트랜잭션 실행 가능
- 트랜잭션 간의 간섭이 발생하여 일관성이 깨지지 않도록 제어하는 기법
<트랜잭션에서의 연산>
- read(x)
- 이름이 x인 데이터베이스 항목을 트랜잭션의 지역변수 x로 읽어 들인다
- ex) SQL의 select 연산
- write(x)
- 지역변수 x에 저장된 값을 데이터베이스 항목 x에 저장한다
- ex) SQL의 update 연산
- x는 테이블, 레코드, 필드 등 데이터베이스를 구성하는 임의의 구성 요소가 될 수 있다
- 단, write(x) 연산을 수행했을 때 그 결과가 디스크에 즉시 저장될 수도 있고 그렇지 않을 수도 있다
- 대부분 주기억장치에 buffer을 유지
<동시성 제어가 필요한 이유>
- 트랜잭션 명령들 간의 끼어들기가 가능하다
- 스케줄(schedule)
- 끼어들이 방식에 의해 실행되는 순서
- 스케줄은 전적으로 운영체제의 권한
- 사용자는 어떠한 스케줄로 트랜잭션들이 실행되는지 미리 예측하기가 거의 불가능
- 끼어들기 방식은 서로간의 간섭에 의해서 잘못된 데이터를 생성할 수 있다
<끼어들기로 인한 문제>
- 갱신 분실(lost update)
- 연쇄 복귀(cascading rollback)
- 불일치 분석(insconsistent analysis)
<갱신 분실(lost update)
- T1에 의해 수생된 갱신(update)이 T2에 의해 사라짐
<연쇄 복귀(cascading rollback)>
- T3이 복귀(rollback)하면 아무 문제 없는 T4도 복귀해야 한다
- T4가 이미 완료된 이후라면 복귀 불가능(지속성 위배)
- 연쇄복귀가 발생한 이유
- 완료되지 않은 트랙잭션의 쓰기 연산에 의해 갱신된 데이터를 다른 트랜잭션이 읽었기 때문
<불일치 분석(inconsistent analysis)>
- 끼어들기로 의해 트랜잭션의 일관성이 유지되지 못하는 상황
<끼어들기로 인한 문제점 해결>
- 트랜잭션들을 순차적으로 실행(가장 단순한 방법)
- 끼어들기를 허용하지 않는다
- 트랜잭션 T(i)와 T(j)가 있을 경우, T(i)가 모두 수행된 후에 T(j)를 수행하던지, 반대로 T(j)가 완전히 수행된 다음에 T(i)를 수행
- 끼어들기(병행수행)를 최대한 허용하면서 직렬 스케줄과 동일한 결과를 갖도록 실행 순서를 제어
- 직렬 가능한 스케줄(serializable schedule)
- 동시성 제어의 목표
<직렬 스케줄(serial schedule)>
- 각 트랜잭션의 연산들이 끼어들기 방식으로 실행되지 않고 순차적으로 실행되는 스케줄
<직렬 가능한 스케줄(serializable schedule)>
- 직렬 가능한 스케줄이란?
- 직렬 스케줄과 실행 결과가 동일한 스케줄
- 직렬 가능한 스케줄인지 판단하는 방법
- 스케줄이 나타난 연산들의 순서를 전체적인 실행 결과에 영향을 미치지 않도록 교환
- 이 때 주어진 스케줄이 직렬 스케줄로 변환되면 직렬 가능한 스케줄이다
- 트랜잭션 Ti의 연산 C1, 트랜잭션 Tj의 연산 C2
- C1과 C2의 실행 순서를 바꿔 직렬 스케줄로 만들 수 있으면 직렬 가능 스케줄
- 실행 순서를 바꿀 수 있는 경우
- C1과 C2가 서로 다른 데이터 항목에 대한 read, write연산 일 경우
- 같은 데이터 항목에 대한 read 연산일 경우
- C1과 C2의 실행 순서를 바꿀 수 있는 경우
- C1과 C2가 서로 다른 데이터 항복에 대한 연산일 경우
- 교환 가능
- C1과 C2가 같은 데이터 항목에 대한 연산일 경우
- C1과 C2가 모두 read 연산일 경우는 교환 가능
- C1과 C2에 하나라도 write 연산일 경우 교환이 불가능
<직렬 가능한 스케줄이 되도록 하는 방법>
- 잠금(locking)
- 트랜잭션의 실행 순서를 강제로 제어
- 타음스탬프(timestamp)
- 최대한 병행 수행을 보장
- 직렬 가능한 스케줄에 위배될 가능성이 있으면 실행 취소
- 대부분의 DBMS에서는 잠금 기법 사용
<잠금(locking)>
- 잠금의 정의
- 하나의 트랜잭션이 수행하는 동안 특정 데이터 항목에 대해 다른 트랜잭션이 동시에 접근하지 못하도록 방지
- 잠금이 걸린 데이터는 잠금을 실행한 트랙잭션만 독점적으로 접근
- 잠금에 사용되는 연산
- lock, unlock
<잠금의 종류>
- 공유잠금(shared lock : S-lock)
- 트랜잭션 T가 데이터 항목 x에 대해 S-lock을 걸면 T는 read(x)연산은 가능하지만 write(x)연산은 불가
- 하나의 데이터 항목에 대해 여러 개의 공유잠금 가능
- 베타잠금(exclusive lock : X-lock)
- 트랜잭션 T가 데이터항목 x에 대해 X-lock을 걸면 T는 read(x), write(x) 연산 모두 가능
- 하나의 데이터 항목에 대해서는 하나의 베타잠금만 가능
- 하나의 데이터에 대해 동시에 여러 개의 배타잠금은 불가능
<동시 잠금 가능 여부>
- 잠금 설정 규칙
- read(x) 실행을 위해서는 S-lock(x), X-lock(x) 중 하나 실행
- write(x) 실행을 위해서는 X-lock(x) 실행
- 연산 종료 후에는 unlock(x) 실행
- S-lock(x), X-lock(x) 실행 후에만 unlock(x) 실행 가능
<잠금의 한계>
- 단순한 잠금 연산만으로 직렬 가능한 스케줄을 보장하는 것은 아니다
- 교착상태(deadlock) 발생 가능
<2단계 잠금 규약>
- 2-Phase Locking protocol : 2PL
- 잠금을 두 단계로 나누어 실행
- 확장 단계(growing phase)
- 트랜잭션이 lock 연산은 수행할 수 있으나 unlock 연산은 수행할 수 없는 단계
- 축소 단계(shrinking phase)
- 트랜잭션이 unlock 연산은 수행할 수 있으나 lock 연산은 수행할 수 없는 단계
- 트랜잭션은 확장단계로 시작하여 축소단계로 끝난다
<2PL과 직렬 가능한 스케줄>
- 2PL을 준수하면 항상 직렬 가능한 스케줄이 된다
- 모든 직렬 가능한 스케줄들이 2PL을 준수 하는 것은 아니다
- 2PL은 직렬 가능한 스케줄의 '충분조건'
<2PL의 한계>
- 교착상태 방지 불가능
- 교착상태 방지를 위한 다른 방법 사용
- 교착 상태 회피(deadlock avoidance)
- 교착 상태 탐지(deadlock detection)
- 연쇄복귀문제가 발생할 수 있다
- 해결방안 : 엄격한(strict) 2PL 적용
- 모든 X-lock에 대한 unlock 연산은 트랜잭션 완료 후 실행
- 완료되지 않은 트랙잭션에 의해 갱신된 데이터를 다른 트랜잭션이 읽거나 쓸 가능성이 원천적으로 봉쇄
<잠금 단위(lock9ng granularity)>
- 잠금의 대상이 되는 데이터 객체의 크기
- 잠금 단위의 예
- 필드, 레코드, 디스크 블록, 테이블, 데이터베이스
- 보통 lock은 '레코드 단위'
- 잠금단위 크면
- 동시성 수준 낮아진다(동시에 실행하는 경우의 수가 줄어든다)
- 트랜잭션 제어가 간단해진다
- 잠금단위 작으면
- 동시성 수준 높아진다(동시에 실행하는 경우의 수가 늘어난다)
- 트랜잭션 제어가 복잡해진다
- 잠금단위를 여러 단계로 정해놓고 필요에 따라 혼용하여 사용
<장애(failure)와 복구(recovery)>
- 데이터베이스에서의 장애 종류
- 트랜잭션 장애
- 트랜잭션 내의 논리적 오류나 잘못된 입력 데이터, 또는 시스템 내의 자원 부족으로 인한 트랜잭션 중단
- 시스템 장애
- 정전이나 하드웨어 결함으로 시스템 작동이 중단
- 미디어 장애
- 디스크와 같은 저장장치의 일부(스크래치) 또는 전체(자연재해)가 손상
- 복구
- 데이터베이스를 장애 발생 이전의 일관된 상태로 복원
- 복구의 기본 원리는 '데이터 중복(redundancy)'
- ex) backup, mirroring
<미디어 장애에 대한 대처>
- 백업(backup) 파일
- DVD나 자기 테이프와 같은 저장장치를 이용
- Mirroring
- 하나의 데이터베이스를 서로 다른 디스크에 복제
- 하나가 문제가 발생하면 복제 디스크로 계속 운용 가능
- 원본 디스크(HOT)와 복제 디스크(STAND BY)에 동일한 데이터베이스 복제(약간의 인터벌은 있을 수 있다)
<트랜잭션 장애나 시스템 장애의 대처>
- 트랜잭션이 완료되지 못하고 장애 발생 시, 재가동 되었을 때 갱신된 내용이 복구되어야 한다
- 복구가 가능하려면?
- 데이터베이스에 어떠한 순서로 갱신이 이루어졌는가를 나타내는 정보가 기록되어 있어야 한다
- '로그(log)' 라고 한다
- 로그는 비휘발성 저장장치(디스크)에 보관
- 로그(log)
- 트랜잭션이나 시스템 장애에 대비한 데이터베이스 갱신 이력(histroy) 저장
*로그(log) : 원양어선의 항해 위치
<로그의 구성 요소>
- 로그 : 데이터베이스의 모든 갱신에 대한 기록 저장, read에 대해서는 필요가 없고 'write'연산에 대해서만 기록
- 장애가 발생하면 이 기록을 보고 이전상태로 복귀가 가능
- 로고의 구성 요소
- 트랜잭션의 시작 <T start> //'T'는 트랜잭션의 아이디(id)
- 트랜잭션의 완료 <T commit>
- 트랜잭션의 중단 <T abort> //사실은 쓰지 않는다
- 데이터 항목에 대한 갱신 <T, x, v1, v2> //x : 데이터 항목 이름(필드), v1 : 원래 값, v2 : 변경될 값
<로그의 예>
-- 트랜잭션 T --
(시작) -> <T start>
read(x)
x=x-50
write(x) -> <T, x, 100, 50>
read(y)
y=y+50
write(y) -> <T, y, 200, 250>
(완료) -> <T commit>
-- 트랜잭션 T의 로그 --
<T start>
<T, x, 100, 50>
<T, y, 200, 250>
<T commit>
<로그 우선 기록 규약>
- write-ahead logging protocol //log 테이블에 먼저 기록하고 os에 요청
- 로그 레코드를 기록할 때 트랜잭션이 갱신한 데이터 항목을 기록하기 전에 로그 레코드를 먼저 기록해야 한다
- 데이터베이스에 이미 기록된 변경내용을 취소하려면 로그 레코드에 그 기록이 남아있어야 하기 때문
- 이미 완료된 트랜잭션에 대해서 데이터베이스의 갱신 내용을 디스크에 반영할 때도 유용하게 사용된다
- 완료된 트랜잭션의 변경이 주기억장치의 버퍼에만 기록되어 있다면?
- 재가동 후 로그를 이용하여 실제 데이터베이스에 갱신 기록을 반영할 수 있다
<로그를 이용한 복구 기법>
- 복구에 사용되는 기본 연산
- UNDO : 갱신된 값을 이전 상태로 되돌린다 //트랜잭션이 실패되었을 때
- REDO : 갱신을 재실행 한다 //트랜잭션이 성공했을 때
- 로그를 이용한 복구 기법
- 지연 갱신(deferred database modification)을 기반으로 한 복구
- 즉시 갱신(immediate database modification)을 기반으로 한 복구
<지연 갱신을 기반으로 한 복구 기법>
- 지연 갱신
- 트랜잭션의 수행이 성공적으로 끝난 후(완료 후) 갱신 내용을 디스크에 반영
- 완료 전에는 주기억 장치의 버퍼(buffer)에만 기록을 저장, OS에게 요청하지 않는다
- 트랜잭션 실행 과정
- 트랜잭션 T가 시작되면 로그에 <T start>를 기록
- T가 데이터 항목 x에 쓰기 연산을 수행하면 로그에 기록하고 메모리 버퍼에 변경된 x 저장
- 최종적으로 T가 모든 연산에 대한 실행을 마치면 T는 부분 완료 상태가 되고 로그에 <T commit>를 기록
- <T commit>이 로그에 기록되면 T는 완료 상태가 되어 종료
- 이후 적정한 시기에 버퍼에 저장된 x를 디스크에 저장
<TIP : 트랜잭션 완료의 의미>
- 트랜잭션이 완료가 되었다는 것은 디스크에 저장된 로그에 <T commit>이 기록되었다는 것을 의미
- 이후에 시스템 장애에 대해서도 이 기록만으로 트랜잭션이 완료되었다는 것을 알 수 있다
- 장애 후에 로그에 기록된 레코드를 이용하여 갱신 내용들을 복구 가능
- 즉, 이 레코드가 로그에 기록이 되지 않으면 트랜잭션 T가 완료된 것인지 아니면 실행 도중에 중단된 것인지 알 수 없다
<지연 갱신을 기반으로 한 복구 기법>
- UNDO 연산 불필요, REDO 연산 필요
- 데이터 갱신에 의한 로그 레코드 형식 : <T, x, v2> (v2 : 갱신 이후의 값)
- 복구 과정
- 로그 파일의 처음부터 기록을 순차적으로 검색
- 로그에 저장된 트랜잭션 T의 로그 레코드에서 <T commit>이 저장되었으면 갱신 기록에 대해 REDO 수행
- <T commit>이 없는 나머지 트랜잭션에 대한 로그 레코드는 무시
- 지연 갱신을 기반으로 한 복구 알고리즘은 UNDO 연산이 필요 없으므로 'NO-UNDO/REDO' 알고리즘 이라고 한다
<즉시 갱신을 기반으로 하는 복구 기법>
- 즉시 갱신
- 버퍼에서 갱신된 데이터들을 트랜잭션 실행 도중(완료되기 전)에 디스크에 저장 가능
- 데이터 갱신에 의한 로그 레코드 형식 : <T, x, v1, v2>
- 복구 과정
- 로그 파일이 기록된 마지막부터 반대방향으로 순차적 검색
- 로그에 저장된 각 트랜잭션 T의 로그 레코드에 대해서 <T start>가 있으나 <T commit>이 없으면 UNDO 연산 수행
- 로그 파일에 처음 도달했으면 반대방향으로 순처적으로 검색
- 로그에 저장된 각 트랜잭션의 로그에 대해 <T commit>이 저장되었으면 REDO 연산 수행
- 즉시 갱신을 기반으로 한 복구 알고리즘을 'UNDO/REDO 알고리즘' 이라고 한다
<검사점을 이용한 복구>
- 로그의 크기가 커질 수록 복구 부담 증가
- 특히, 불필요한 REDO 연산이 다량 발생
- 검사점(check point)
- 주기적으로 버퍼의 갱신 기록을 디스크에 기록
- 현재까지는 로그에 기록된 내용과 디스크에 저장된 데이터베이스의 내용을 일치시키는 과정
- 검사점 이전 로그에 대해서는 REDO, UNDO 불필요
- 검사점 작업(즉시 갱신만을 가정)
- 모든 트랜잭션의 실행 중단
- 갱신된 모든 버퍼의 내용을 디스크로 출력
- 로그에 <checkpoint> 레코드 저장
- 중단된 트랜잭션들을 다시 실행
<오라클에서의 트랜잭션>
- 트랜잭션의 완료와 복구
- DDL, DCL의 경우
- 자동 완료(auto commit)
- DDL, DCL이 성공적으로 실행된 경우 복구 불가능
- DML : commit, rollback 명령 사용
- commit : 트랜잭션 종료
- rollback : 가장 최근 commit 시점까지 복구
<자동 완료와 자동 복귀>
- 자동 완료
- commit을 실행하지 않더라도 정상적으로 종료되면 트랜잭션도 자동으로 완료
- DDL, DCL들은 항상 자동 완료
- 자동 복귀
- 비정상적으로 종료되면 트랜잭션은 자동으로 복귀
<set autocommit on>
- DML에서도 'set autocommit on'명령을 실행하면 자동 완료된다
<오라클에서 자동 완료되는 경우>
- SQL*Plus가 정상적으로 종료하는 경우
- DDL, DCL 명령을 실행하는 경우
- 'set autocommit on' 명령을 실행하면 개별 DML 명령들도 자동 완료
<저장점(save point)>
- 트랜잭션을 여러 개로 분할하여 '일부만'을 복귀시키고자 할 때 사용
- check point와는 다르다
- 저장점 지정
- savepoint <라벨>
- 지정된 저장점으로 복귀
- rollback <라벨>
'데이터 베이스' 카테고리의 다른 글
함수의 종속성과 정규화 (0) | 2023.05.16 |
---|---|
데이터베이스 설계 (0) | 2023.05.01 |
물리적 저장구조와 인덱스 (0) | 2023.04.23 |