본문 바로가기

Database

[DB]트랜잭션, 동시성 제어, 회복

트랜잭션(Transaction)

트랜잭션

  • 트랜잭션: DBMS에서 데이터를 다루는 논리적인 작업의 단위
  • DB에서 데이터를 다룰 때 장애가 일어난 경우 데이터를 복구하는 작업의 단위가 됩니다.
  • DB에서 여러 작업이 동시에 같은 데이터를 다룰 때가 이 작업을 서로 분리하는 단위가 됩니다.
  • 트랜잭션은 전체가 수행되거나 또는 전혀 수행되지 않아야 합니다.(원자성)

우리가 데이터베이스에 삽입, 수정, 삭제 등의 작업을 할 때, 여러 개의 작업들을 하나의 트랜잭션으로 묶습니다. 즉, 트랜잭션은 DBMS에서 데이터를 다루는 논리적인 작업의 단위가 됩니다. 예를 들어 A계좌(홍길동)에서 B계좌(김연아)로 돈을 이체하는 경우에 이 업무는 A에서 돈을 빼고 B에 돈을 더하는 2가지의 Update 문으로 나뉘게 됩니다. 그리고 이것들은 개별적으로 수행되는 것이 아니라 하나의 트랜잭션으로 묶이게 되며 하나의 트랜잭션이 실행될 때 이 2개의 SQL문이 연속적으로 실행됩니다. 그러므로 2개의 Update 문이 하나의 트랜잭션으로 묶여있다고 가정할 때 1개의 SQL만 실행되는 상황은 발생하지 않습니다. 이를 All OR Nothing이라 합니다. 이어서 위의 계좌 이체 트랜잭션이 일어나는 세부 과정에 대해 알아보도록 하겠습니다.

 

 

 

트랜잭션 수행 과정

  1. A 계좌의 값을 하드디스크(데이터베이스)에서 주기억장치 버퍼로 읽어옵니다.
  2. B 계좌의 값을 하드디스크(데이터베이스)에서 주기억장치 버퍼로 읽어옵니다.
  3. A 계좌에서 10000원을 인출한 값을 저장합니다.
  4. B 계좌에서 10000원을 입금한 값을 저장합니다.
  5. A 계좌의 값을 주기억장치 버퍼에서 하드디스크(데이터베이스)에 기록합니다.
  6. B 계좌의 값을 주기억장치 버퍼에서 하드디스크(데이터베이스)에 기록합니다.

위와 같은 트랜잭션의 수행 과정은 2가지 방법으로 나뉘어집니다. 우선 2가지 방법을 알기 전에 COMMIT이라는 개념을 알아야합니다. 여기서 COMMIT이란 트랜잭션의 수행이 완료됨을 트랜잭션 관리자에게 알려 주는 연산입니다. 2가지 방법은 COMMIT이 어느 위치에 들어가는지에 따라 달라지는데, 4번까지 수행한 후 COMMIT을 할 수 있고 6번까지 종료한 후 COMMIT을 할 수 있습니다. 여기서 DBMS는 사용자에게 빠른 응답을 위해 방법 1을 택한다고 합니다.

방법 1의 실제 동작: ① → ② → ③ → ④ → COMMIT → ⑤ → ⑥ →완료

 

 

 

 

트랜잭션의 ACID 성질

  • 원자성(Atomicity): 트랜잭션에 포함된 작업은 전부 수행되거나 전부 수행되지 않아야 합니다.
  • 일관성(Consistency): 트랜잭션을 수행하기 전과 후에 데이터베이스는 항상 일관된 상태를 유지해야 합니다.
  • 고립성(Isolation): 수행 중인 트랜잭션에 다른 트랜잭션이 끼어들어 변경중인 데이터 값을 훼손하지 않아야 합니다.
  • 지속성(Durability): 수행을 성공적으로 완료한 트랜잭션은 변경한 데이터를 영구적으로 저장해야 합니다.

데이터베이스는 일반적은 프로그램과 다르게 4가지의 성질을 지니는데 이를 ACID라고 합니다.

원자성이란 All OR Nothing의 성질로 트랜잭션이 원자처럼 더 이상 쪼개지지 않는 하나의 프로그램 단위로 동작해야 한다는 의미입니다. 트랜잭션이 중간에 끊기면 이후에 해당 트랜잭션의 어디서부터 이어 수행되어야 하는지 모르기 때문에 원자성이라는 성질을 지니게 되었습니다.

 

일관성이란 트랜잭션은 트랜잭션의 수행 전과 후에 일관된 상태를 유지해야 하는 성질을 말합니다. 예를 들어 어떤 테이블의 기본키와 같은 속성은 유지되어야 한다는 것. 또는, A에서 B로 돈을 이체할 때 A와 B계좌의 돈의 총합이 같아야 한다는 것 등이 있습니다. 

 

고립성이란 데이터베이스는 클라이언트들이 같은 데이터를 공유하는 것이 목적이므로 여러 트랜잭션이 동시에 수행되어야 하는데, 이 때 트랜잭션은 상호 간의 존재를 모르고 독립적으로 수행되어야 하는 성질을 이야기합니다. 고립성을 격리성이라고 얘기하기도 하는데 이를 유지하기 위해서는 여러 트랜잭션이 동시에 접근하는 데이터에 대한 제어가 필요합니다.

 

마지막으로 트랜잭션은 지속성이라는 성질을 지녀야 합니다. 지속성은 트랜잭션의 성공 결과 값은 장애 발생 후에도 변함없이 보관되어야 한다는 것으로 트랜잭션이 정상적으로 완료(COMMIT)된 경우에는 버퍼의 내용을 하드디스크(데이터베이스)에 확실히 기록하여야 하며, 부분 완료(Partial Commit)된 경우에는 작업을 취소하여야 합니다. 즉, 정상적으로 완료 혹은 부분 완료된 데이터는 DBMS가 책임지고 데이터베이스에 기록하는 성질이 지속성이며, 영속성이라고 표현하기도 합니다.

 

 

 

트랜잭션과 DBMS

  • DBMS는 원자성을 유지하기 위해 회복(복구)관리자 프로그램을 작동시킵니다.
  • DBMS는 일관성을 유지하기 위해 동시성 제어 알고리즘과 무결성 제약조건을 활용합니다.
  • DBMS는 고립성을 유지하기 위해 동시성 제어 알고리즘을 작동시킵니다.
  • DBMS는 지속성을 유지하기 위해 회복 관리자 프로그램을 이용합니다.

어떤 트랜잭션이 실행되다 장애로 인해 부문 완료되는 상황은 원자성지속성에 위배됩니다. 그래서 DBMS는 이를 유지하기 위해 회복 관리자 프로그램을 이용하는데, 일부만 진행된 트랜잭션을 취소시켜 원자성을 유지할 뿐 아니라 값을 트랜잭션 이전의 상태로 복원시켜 지속성을 유지시켜 줍니다. 또한, 일관성고립성을 유지하기 위해서 값에 동시에 접근하지 않도록 하므로 동시성 제어(Locking)를 활용하여 이를 해결합니다. 파일 시스템과 같은 경우처럼 값이 덮어 씌워지는 경우 일관성이 무너질 수 있고 그러한 경우 고립성에 위배되는 경우이므로 Locking을 하여 이를 만족시킵니다. 여기에 더해 DBMS는 잘못된 값에 대한 입력이 오면 일관성이 무너질 수 있으므로 이를 유지시키기 위해 무결성 제약조건도 활용합니다.

 

 

 

 

기타 개념들

  • ROLLBACK: 트랜잭션이 행한 모든 연산을 취소시키거나 트랜잭션을 재시작합니다.
  • DBMS는 일관성을 유지하기 위해 무결성 제약조건을 활용합니다.
  • DBMS는 고립성을 유지하기 위해 동시성 제어 알고리즘을 작동시킵니다.
  • DBMS는 지속성을 유지하기 위해 회복 관리자 프로그램을 이용합니다.

ROLLBACK 연산은 트랜잭션의 실행 중에 장애가 발생한 경우에 수행됩니다. 장애가 발생한 경우는 데이터베이스에 일부만 반영되어 일관되지 못한 상태를 가질 수 있으므로 모두 취소하거나 트랜잭션을 재시작해야 합니다.

트랜잭션의 활동은 5가지 상태를 갖게됩니다. → 활동(Active), 부분 완료(Partially Committed), 완료(Committed), 실패(Failed), 철회(Aborted)

여기서 활동은 트랜잭션의 Begin_transaction으로 부터 실행을 시작하였거나 실행 중인 상태를 의미합니다. 부분 완료는 마지막 명령문을 실행시킨 직후의 상태를 의미하며 실패 또는 완료의 상태로 전이하게 됩니다. 실패는 트랜잭션의 실행 중에 장애나 오류가 발생하여 정상적인 실행을 더 이상 할 수 없는 상태이며 ROLLBACK 연산을 수행한 상태인 철회 상태로 전이하게 됩니다. 위에서 ROLLBACK에 의해 트랜잭션이 재시작 되거나 강제종료 되는 경우가 있다고 하였는데, 철회의 원인이 트랜잭션 자체의 논리적인 오류가 아닌 경우에는 재시작되며 트랜잭션 철회의 트랜잭션 내부의 논리적인 오류에 의해 오류를 수행해야 하는 상황이거나 얻고자 하는 데이터가 데이터베이스에 존재하지 않는 경우에 강제 종료 됩니다.

 


동시성 제어

동시성 제어

  • 다중 사용자 환경에서 둘 이상의 트랜잭션이 동시에 수행될 때, 일관성을 해치지 않도록 트랜잭션의 데이터 접근을 제어합니다.
  • 다중 사용자 환경을 지원하는 DBMS의 경우, 반드시 지원해야 합니다.

위에서 배운 고립성은 상호 간의 트랜잭션을 독립적으로 만들어 주었습니다. 그런데 2개 이상의 트랜잭션이 하나의 값에 접근하는 경우에는 어떻게 될까요?

A,B 트랜잭션이 모두 읽기 작업을 하는 경우는 문제가 발생하지 않습니다.

A 트랜잭션은 쓰기 작업, B 트랜잭션은 읽기 작업을 할 때 오손 읽기, 반복 불가능 읽기, 유령 데이터 읽기 문제가 발생할 수 있습니다.

A,B 트랜잭션이 모두 쓰기 작업 시 무제어 병행을 수행하는 경우에 갱신 손실, 모순성, 연쇄 복귀 등의 문제가 발생할 수 있습니다.

 

 

 

 

갱신 손실(Lost Update)

  • 하나의 트랜잭션이 갱신한 내용을 다른 트랜잭션이 덮어씀으로써 갱신이 무효화가 되는 것을 의미합니다.
  • 두 개의 트랜잭션이 한 개의 데이터를 동시에 갱신(Update)할 때 발생합니다.
  • 데이터베이스에서 절대 발생하면 안되는 현상입니다.

갱신 손실은 파일 시스템에서 발생하는 문제와 유사합니다. 하나의 값에 계속해서 덮어쓰기(Overwrite)하여 데이터의 갱신이 무효화 되는 현상입니다. 초기값 X = 1000이 있고, 트랜잭션 T1은 X += 300, 트랜잭션 T2는 X -= 500 이라 할 때, 동시성 제어를 해주지 않으면 1000이라는 값이 T1에 의해 1300이 된 상태에서 T2가 아직 1300으로 Write 되기 전인 X = 1000을 Read하여 1300 - 500이 아니라 1000 - 500을 수행하여 500을 갖게 되었고 T1과 T2가 순차적으로 값을 저장하여 덮어씌워지는 값의 손실을 갱신 손실이라 합니다.

 

 

 

 

모순성(Inconsistency)

  • 다른 트랜잭션들이 해당 항목 값을 갱신하는 동안 한 트랜잭션이 두 개의 항목 값 중 어떤 것은 갱신되기 전의 값을 읽고 다른 것은 갱신된 후의 값을 읽게 되어 데이터의 불일치가 발생하는 상황

초기값 X = 1500, Y = 1000이 있고, 트랜잭션 T1은 X와 Y를 300 증가, 트랜잭션 T2는 X와 Y를 3배씩 증가시킨다고 해보겠습니다. T1이 수행될 때 X와 Y는 각각 1500과 1000이 읽혀야 합니다. T1이 수행되면서 X만을 1800으로 증가시키고 Write된 다음 Y를 수행하는 것이 아닌 트랜잭션 T2가 수행된다고 합시다. 그러면 T2에 의해 X와 Y는 각각 5400과 3000이 되고 다시 T1의 Y가 Read를 해야 하는 상황에서 1000이 아니라 3000이 읽히게 됩니다. 이렇게 되면 어떤 값은 갱신 전의 값을 다른 값은 갱신 후의 값을 읽어 데이터가 불일치하는 것을 모순성이라 합니다.

 

 

 

 

연쇄 복귀(Cascading Rollback)

  • 두 트랜잭션이 동일한 데이터 내용을 접근할 때 발생합니다.
  • 한 트랜잭션이 데이터를 갱신한 다음 실패하여 ROLLBACK 연산을 수행하는 과정에서 갱신과 ROLLBACK 연산을 실행하고 있는 사이에 해당 데이터를 읽어서 사용할 때 발생할 수 있는 문제입니다.

초기값 X = 1500, Y = 1000과 X를 300 증가시키고 Y를 200 감소시키는 트랜잭션 T1과 X 항목을 3배 증가시키는 트랜잭션 T2가 있다고 합시다. T1이 X를 300 증가시키고 Write하여 1800이 된 상황에서 T2 트랜잭션이 실행되어 X를 3배하여 Write한 후 종료되었다고 가정합시다. 그리고 T1이 이제 ROLLBACK 연산을 하여 X를 1500으로 다시 돌려놓으려고 하는데 T2는 이미 해당 트랜잭션을 완료하여 종료되었으므로 ROLLBACK할 수 없게 되버리는 문제가 발생합니다.

 

 

 

 

트랜잭션 스케줄(Transaction Schedule)

  • 직렬 스케줄(Serial Schedule)
  • 비직렬 스케줄(Nonserial Schedule)
  • 직렬 가능 스케줄(Serializable Schedule)

트랜잭션들은 삽입, 수정, 삭제 등과 같은 연산들로 이루어져 있는데 여기서 트랜잭션 스케줄이란 그 연산들의 실행 순서를 의미합니다.

직렬 스케줄의 경우 트랜잭션 연산을 순차적으로 실행하는 것을 의미합니다. 즉, 하나의 트랜잭션이 실행되면 해당 트랜잭션이 완료되어야 다른 트랜잭션이 실행될 수 있습니다.

비직렬 스케줄은 트랜잭션의 직렬 수행 순서와 상관없이 병행 수행하는 스케줄을 의미합니다. 그러므로 한 트랜잭션이 진행중인 상황에서 다른 트랜잭션이 실행될 수 있습니다. 

직렬가능 스케줄은 직렬 스케줄과 동등한 비직렬 스케줄을 의미합니다. 쉽게 설명하자면 서로 영향을 주지 않는 직렬 스케줄을 비직렬적으로 수행하겠다는 것입니다. 

 

 

 

 

직렬 가능 스케줄

  • 두 개의 트랜잭션이 Read 연산만 수행할 것이라면 상호 간섭이 발생하지 안흐며 연산의 순서도 중요하지 않습니다.
  • 두 개의 트랜잭션이 같은 데이터 항목에 접근하지 않는다면 상호 간섭이 발생되지 않으며 연산의 순서도 중요하지 않습니다.
  • T1이 X에 Write연산을 하고 T2가 X에 Read 또는 Write 연산을 한다면 실행 순서는 중요하지 않습니다.

 

락(Lock)

  • 로킹(Locking) 기법: 트랜잭션들이 동일한 데이터 항목에 대해 임의적인 병행 접근을 하지 못하도록 제어하는 것
  • 트랜잭션 T가 데이터 항목 X에 대해 Read(X) OR Write(X) 연산을 수행하려면 반드시 lock(X) 연산을 해주어야 합니다.
  • 트랜잭션 T가 실행한 lock(X)에 대해서는 해당 트랜잭션이 종료되기 전에 반드시 unlock(X) 연산을 해주어야 합니다.
  • 트랜잭션 T는 다른 트랜잭션에 의해 이미 lock이 걸려 있는 X에 대해 다시 lock(X)를 수행시키지 못합니다.
  • 트랜잭션 T가 X에 lock을 걸지 않았다면, unlock(X)를 수행시키지 못합니다.

즉, 여러 개의 트랜잭션들이 하나의 데이터로 동시에 접근하려고 할 때 이를 제어해주는 도구가 바로 Lock입니다. Lock은 트랜잭션이 읽기를 할 때 사용하는 공유락(LS, Shared Lock)읽고 쓰기를 할 때 사용하는 베타락(LX, Exclusive Lock)으로 나뉩니다. 트랜잭션 T가 데이터 항목 X에 대하여 Shared-Lock을 설정할 경우, 트랜잭션 T는 해당 데이터 항목에 대해서 읽을 수 있지만 기록할 수 없습니다. 그리고 Read는 서로 영향을 주지 않으므로 다른 트랜잭션도 Shared-Lock이 설정된 X에 대해서 Shared-Lock을 동시에 설정할 수 있습니다. 트랜잭션 T가 데이터 항목 X에 대하여 Exclusive-Lock을 설정할 경우, 트랜잭션 T는 해당 데이터 항목에 대해서 읽을 수도 있고 기록할 수도 있습니다. Write는 영향을 주는 작업이므로 다른 트랜잭션은 Exclusive-Lock을 설정한 데이터 항목 X에 대해서 어떠한 Lock도 설정할 수 없습니다.

 

 

 

공유락과 베타락을 사용하는 규칙

  • 데이터에 락이 걸려있지 않으면 트랜잭션은 데이터에 락을 걸 수 있습니다.
  • 트랜잭션이 데이터 X를 읽기만 할 경우 LS(X)를 요청하고, 읽기, 쓰기의경우 LX(X)를 요청합니다.
  • 다른 트랜잭션이 데이터에 LS(X)를 걸어둔 경우, LS(X)의 요청을 허용하고 LX(X)는 허용하지 않습니다.
  • 다른 트랜잭션이 데이터에 LX(X)를 걸어둔 경우, LS(X)와 LX(X) 모두 허용하지 않습니다.
  • 트랜잭션이 락을 허용받지 못하면 대기 상태가 됩니다.

 

2단계 락킹

  • 로킹 단위: 로킹 기법에서 사용하는 Lock 연산의 대상
  • 2단계 로킹 규약: 락을 걸고 해제하는 시점에 제한을 두지 않으면 두 개의 트랜잭션이 동시에 실행될 때 데이터의 일관성이 깨질 수 있어서 로킹 단계를 2개로 구분하여 이를 방지하는 방법입니다.
  • 확장 단계(Growing Phase): 트랜잭션은 새로운 lock 연산만 가능하고, unlock 연산을 할 수 없는 단계입니다.
  • 축소 단계(Shrinking Phase): 트랜잭션은 unlock 연산만 실행할 수 있고, lock 연산은 실행할 수 없는 단계입니다.

로킹 단위가 속성 → 튜플 → 릴레이션 → 데이터베이스로 커질수록 구현이 용이하고 Lock의 수가 적어지며 제어가 간단하지만 병행성이 떨어지는 반면 로킹단위가 작을수록 구현이 복잡하고 Lock의 수가 많고 제어 기법이 복잡하여도 병행성을 높일 수 있습니다. 2단계 로킹 규약(2PLP:Two-Phase Locking Protocol)트랜잭션들이 Lock 하는 시간과 unlock을 하는 시간을 구분하여 수행하도록 하는 것입니다. 2단계 로킹 규약을 사용하지 않으면 아래의 그림과 같이 일관성이 위배되는 문제가 발생할 수 있습니다. 그래서 임의의 시간에 Lock 또는 Unlock을 하는 것이 아니라 확장 단계와 축소 단계를 두어 이를 해결해줍니다. 2PLP는 직렬 가능성을 보장할 수 있는 규약으로 가장 많이 사용되지만 교착 상태에 빠질 수 있으므로 이를 해결해주어야 합니다.

 

 

 

 

데드락(Deadlock)

  • 데드락(Deadlock): 두 개 이상의 트랜잭션이 각각 자신의 데이터에 대하여 락을 획득하고 상대방 데이터에 대해 락을 요청하면 무한 대기 상태에 빠지는 현상을 이야기합니다. 
  • 데드락은 교착상태라고도 합니다.

데이터베이스의 데드락과 운영체제의 데드락은 상당히 비슷합니다. 쉽게 설명하면 공통된 자원을 이용하기 위해 여러 개의 트랜잭션이 서로 lock을 걸어주다가 무한 대기 상태에 빠지는 것을 데드락이라 합니다. 예를 들어 먼저 T1에서 A에 대해 Lock을 걸고 T2에서 B에 대해 락을 걸었다고 하겠습니다. 그리고 나서 T1에서 B에 대해 락을 걸고 T2가 A에 대해 락을 건다면 T1과 T2는 서로 A, B에 대한 락을 유지하며 무한 루프에 빠지게 됩니다. 일반적으로 데드락이 발생하면 DBMS가 T1 혹은 T2 중 하나를 강제로 증지시켜 한 트랜잭션은 정상적으로 실행되며 중지된 트랜잭션에서 변경한 데이터는 원래 상태로 되돌려 놓습니다.

 


트랜잭션 고립 수준

오손 읽기(Dirty Read)

  • 읽기 작업을 하는 트랜잭션 1쓰기 작업을 하는 트랜잭션 2가 작업한 중간 데이터를 읽기 때문에 발생하는 문제를 이야기 합니다.
  • 작업중인 트랜잭션 2가 작업을 ROLLBACK한 경우 트랜잭션 1은 무효가 된 데이터를 읽게 되고 잘못된 결과를 도출합니다.

아래의 그림과 같이 T1이 T2가 ROLLBACK 되기 전의 데이터를 읽었고, T2가 ROLLBACK이 되면 T1이 의미가 없는 값을 갖게 되므로 문제가 발생합니다. 그리고 이러한 무효가 된 데이터를 읽게되어 발생하는 문제를 오손읽기(Drity Read)라고 합니다.

 

 

 

 

반복 불가능 읽기(Non-repeatable Read)

  • T1 이 데이터를 읽고 T2가 데이터를 쓰고(Update), T1이 다시 한번 데이터를 읽을 때 생기는 문제입니다.
  • T1이 읽기 작업을 다시 한번 반복할 경우 이전의 결과와 다른 결과가 나오는 현상입니다. 

아래 그림과 같이 T1이 먼저 값을 읽었고 T2가 값을 갱신하였다고 합시다. 그리고 T1이 다시 값을 읽으려고 하면 바로 전에 읽은 값이 달라지는데 이를 반복 불가능 읽기라고 합니다.

 

 

 

 

유령데이터 읽기(Phantom Read)

  • T1이 데이터를 읽고 T2가 데이터를 쓰고(Insert), T1이 다시 한번 데이터를 읽을 때 생기는 문제
  • T1이 읽기 작업을 다시 한번 반복할 경우 이전에 없던 데이터(유령 데이터)가 나타나는 현상

아래의 그림과 같이 T1이 먼저 값을 읽었고, T2가 값을 삽입하였다고 하겠습니다. 그리고 T1이 다시 값을 읽으려고 하면 바로 전에는 없었던 값이 읽히게 되는데 이를 유령데이터 읽기라고 합니다.

 

 

 

 

트랜잭션 고립 수준 명령어(Transaction Isolation Level Instruction)

  • DBMS는 트랜잭션을 동시에 실행시키면서 Lock보다 좀 더 완화된 방법으로 문제를 해결하기 위해 제공하는 명령어 입니다.

트랜잭션이 작업을 수행하다 보면 위의 오손 읽기, 반복 불가능 읽기, 유령데이터 읽기와 같은 문제를 직면할 수 있습니다.

그래서 Lock보다는 완화된 방법으로 트랜잭션을 동시에 실행시키면서, 발생하는 문제를 해결하기 위해 DBMS가 제공하는 명령어가 바로 트랜잭션 고립 수준 명령어(Transaction Isolation Level Instruction) 입니다. 여기에는 고립 수준을 나타내는 READ UNCOMMITED, READ COMMITED, REPEATABLE READ, SERIALIZABLE과 같은 명령어들이 있습니다.

 

READ UNCOMMITTED

  • 고립 수준이 Level 0으로 가장 낮은 명령어로, 자신의 데이터에 아무런 공유락을 걸지 않습니다.

READ UNCOMMITTED는 자신의 데이터에 아무런 공유락을 걸지 않지만, 베타락은 데이터의 갱신 손실 문제 때문에 걸어 주어야 합니다. 또한 다른 트랜잭션에 공유락과 베타락이 걸린 데이터를 대기하지 않고 읽습니다. SELECT 문을 수행하는 경우 해당 데이터에 Shared Lock이 걸리지 않는 Level입니다. 따라서, 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은 데이터(오손 데이터) B를 읽을 수 있습니다.

 

 

READ COMMITTED

  • 고립 수준 Level 1인 명령어, 오손 페이지의 참조를 피하기 위해 자신의 데이터를 읽는 동안 공유락을 걸지만 트랜잭션이 끝나기 전에라도 해지가 가능합니다.

다른 트랜잭션 데이터는 락 호환성 규칙에 따라 진행됩니다. SQL Server가 Default로 사용하는 Isolation Level으로 SELECT 문이 수행되는 동안에 공유락이 걸리게 됩니다. 그러므로 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안에 다른 사용자는 해당 데이터 접근할 수 없습니다.

 

 

REPEATABLE READ

  • 고립 수준 Level 2인 명령어, 자신의 데이터에 설정된 공유락과 베타락을 트랜잭션이 종료될 때까지 유지하여 다른 트랜잭션이 자신의 데이터를 갱신(Update)할 수 없도록 합니다.

다른 트랜잭션 데이터는 락 호환성 규칙에 따라 진행됩니다. 다른 고립화 수준에 비해 데이터의 동시성(Concurrency)이 낮아 특별하지 않은 상황이라면 사용하지 않는 것이 좋습니다. 즉, 트랜잭션이 완료될 때 까지 SELECT 문이 사용하는 모든 데이터에 공유락(Shared Lock)이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능합니다. 

예를 들어 SELECT number FROM A WHERE number BETWEEN 1 AND 10 을 수행하였고, 이 범위에 해당하는 number가 2 or 3이 있는 경우 다른 사용자가 number가 2 OR 3인 행에 대한 Update가 불가능하지만 나머지 범위에 대해서 행을 INSERT하는 것이 가능합니다.

 

 

SERIALIZABLE

  • 고립 수준이 Level 3으로 가장 높은 명령어로, 실행 중인 트랜잭션은 다른 트랜잭션으로부터 완벽하게 분리됩니다.

데이터 집합에 범위를 지어 잠금을 설정할 수 있기 떄문에 다른 사용자가 데이터를 변경(Update) 또는 삽입(Insert)하려고 할 때 트랜잭션을 완벽하게 분리할 수 있습니다. 이 명령어는 가장 제한이 심하고 동시성도 낮습니다. 즉, SELECT 문이 사용하는 모든 데이터에 Shared Lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정(Update) 및 입력(Insert)이 불가능합니다. 앞의 Repeatable Read의 경우에는 1에서 10 사이의 number에 대한 삽입이 가능하였지만 SERIALIZABLE은 SELECT의 대상이 되는 모든 테이블에 Shared Lock을 설정하는 것과 같아 나머지 범위에 대한 Insert가 불가능합니다.

 


회복

장애의 유형

  • 트랜잭션 장애: 트랜잭션 실행 시 논리적인 오류로 발생할 수 있는 에러 상황
  • 시스템 장애: H/W 시스템 자체에서 발생할 수 있는 에러 상황
  • 미디어 장애: 디스크 자체 손상으로 발생할 수 있는 에러 상황

트랜잭션 실행 시 발생할 수 있는 에러 상황의 원인으로는 트랜잭션 내에 잘못된 데이터 입력, 데이터의 부재, 오버플로우, 자원의 한계 초과 요청, 어떤 수를 0으로 나누게 되는 연산 등이 있습니다.

시스템 장애는 하드웨어 자체에서 발생할 수 있는 에러로 하드웨어의 잘못된 작동으로 메인 메모리에 저장되어 있는 정보가 손실되거나 교착 상태(DeadLock)의 발생으로 더 이상 트랜잭션 작업이 수행될 수 없는 상황에서 발생합니다. 

미디어 장애는 디스크 손상으로 발생할 수 있는 에러로 디스크 헤드 손상이나 고장으로 인하여 저장장치 내의 정보가 일부 또는 전부 손상될 수 있는 상태입니다.

 

 

 

 

회복(Recovery)

  • 데이터 베이스를 장애가 발생했던 이전의 상태로 복구시켜 일관된 데이터베이스의 상태로 만드는 것

데이터베이스를 갱신하는 과정에서 장애가 발생한 경우 회복절차를 수행하여 장애 발생 이전의 데이터베이스로 만드는 것을 회복이라고 합니다. 

회복을 위해 데이터 복사본을 만드는 방법에는 1. 덤프, 2. 로그 2가지가 있습니다. 

덤프(Dump)는 가장 기본적인 기법으로 일정 주기로 원본 데이터베이스의 모든 내용을 다른 저장장치에 복사하는 것입니다.

로그(Log)는 변경 이전의 데이터베이스를 기준으로 변경 연산이 발생할 때 마다 로그 파일을 작성하여 기록하고, 회복할 때 로그에 적힌 내용을 사용하여 복원하는 방법입니다.

 

 

 

 

로그 파일(Log File)

  • 트랜잭션이 반영한 모든 데이터의 변경사항을 데이터베이스에 기록하기 전에 미리 기록해두는 별도의 데이터베이스
  • 하드디스크에 영구적으로 저장됩니다. 
  • 구조: <트랜잭션 번호, 로그의 타입, 데이터 항목 이름, 수정 전 값, 수정 후 값>
  • 타입: START, INSERT, UPDATE, DELETE, ABORT, COMMIT 등 트랜잭션의 연산 타입

로그란 트랜잭션이 반영한 모든 데이터의 변경사항을 데이터베이스에 직접 기록하기 전에 미리 기록해두는 별도의 데이터베이스로 특정한 구조를 가지고 기록이 되며 장애 발생 시 복원을 위해 사용됩니다. 실제로 아래와 같이 기록됩니다.

 

 

 

 

로그 파일을 이용한 회복

  • 데이터의 변경이 발생할 때 마다 생성되는 로그 파일을 이용하는 것
  • 데이터의 변경 기록을 저장해 둔 로그 파일을 이용하면 시스템 장애도 복구가 가능합니다.

DBMS는 트랜잭션이 종료되었는지 혹은 중단되었는지 여부를 판단하여 종료된 경우에는 종료를 확정하기 위하여 REDO(재실행)을 하고 중단된 경우에는 없던 일로 되돌리기 위해 UNDO(취소)를 진행합니다. 

REDO는 장애가 발생한 후 시스템을 재기동 했을 때 로그 파일에 트랜잭션의 시작(START) ~ 종료(COMMIT)의 기록을 보면서 트랜잭션이 변경한 내용을 다시 기록하는 과정입니다.

UNDO는 장애가 발생한 이후, 시스템을 재기동 했을 때 로그 파일에 트랜잭션의 시작(START)만 있고 종료(COMMIT)가 없는 경우 완료하지 못했지만 버퍼의 변경 내용이 데이터베이스에 기록되어 있을 가능성이 있기 때문에 로그를 보면서 트랜잭션이 변경한 내용을 원상 복구 시키는 과정입니다.

REDO의 경우 T1에 의해 변경된 모든 데이터 항목들을 로그 파일에 있는 새로운 값으로 대체시켜야 하고 이를 T1의 첫 번째 로그 레코드부터 진행합니다. 하지만, UNDO의 경우에는 T1에 의해 변경된 모든 데이터 항목들을 로그 파일에 있는 예전 값으로 대체하여야 하고 장애가 발생한 마지막 로그 레코드부터 거꾸로 진행합니다.

 

REDO
UNDO

트랜잭션이 시작되면 변경 연산에 대해선 로그 파일에만 기록되고 실제 데이터베이스에는 반영되지 않습니다.

이후, 부분 완료까지 성공하게 되면 그 동안에 변경 연산이 기록되어진 로그 파일을 참조하여 실제 데이터베이스에 값을 변경시킵니다. 트랜잭션이 실패한 경우에는 연산이 로그 파일에만 저장되어 있기 때문에 추가 작업이 필요없지만, 중간에 부분 완료를 거친 후, 트랜잭션 실패가 발생한다면, 데이터베이스에 일부 기록된 내용이 있을 수 있으므로 이러한 경우에는 UNDO를 해주어야 합니다. 아래 그림은 트랜잭션이 성공한 경우와 실패한 경우입니다.

 

트랜잭션이 성공한 경우
트랜잭션이 실패한 경우

 

 

 

로그 파일의 회복 방법

  • 즉시 갱신(Immediate Update): 갱신 데이터 → 로그, 버퍼 → 데이터베이스 작업이 부분 완료 전에 동시에 진행될 수 있으며 부분 완료가 되면 갱신 데이터는 로그에 기록이 끝난 상태
  • 지연 갱신(Deferred Update): 갱신 데이터 → 로그가 끝난 후, 부분 완료를 하고 버퍼 → 데이터베이스 작업이 진행되는 방법

즉시 갱신은 갱신된 데이터를 로그에 기록하는 작업과 버퍼의 데이터를 데이터베이스 옮기는 작업이 동시에 진행될 수 있으며 부분 완료가 된 경우는 로그에 기록이 끝난 상태인 반면에 지연 갱신은 갱신된 데이터를 로그에 작성하는 작업이 끝난 후에야 버퍼의 데이터를 데이터베이스로 옮기는 작업이 진행될 수 있습니다.

 

 

 

체크포인트(CheckPoint, 검사점)

  • 로그는 그대로 기록을 유지하면서, 회복 관리자가 정하는 일정한 시간 간격으로 검사 시점을 생성하는 것
  • 회복 시 많은 양의 로그를 검색하고 갱싱하는 시간을 줄이기 위함입니다.
  • 체크포인트가 있으면 로그를 이용한 회복 기법은 좀 더 간단해집니다.

DBMS에서 회복을 할 때 처음부터 마지막 로그 기록까지 한번에 처리하려면 시간적으로 오래 걸릴 수 밖에 없습니다. 그러므로 특정 시간마다 검사를 하여 문제가 없음을 확인하면 다음번 에러 발생 시 회복을 시작하는 시점을 앞으로 당길 수 있습니다. 그래서 체크 포인트 시점에는 다음과 같은 작업을 수행하게 됩니다. 먼저 주기억장치의 로그 레코드를 모두 하드 디스크의 로그 파일에 저장합니다. 그리고 트랜잭션 수행 중 변경된 버퍼 내의 내용을 하드디스크의 데이터베이스에 저장합니다. 그리고 나서 체크포인트를 로그 파일에 다음과 같이 표시합니다. <Checkpoint T_List>: T_List는 현재 수행중인 트랜잭션의 리스트입니다.

 

  • CheckPoint 이전에 COMMIT 기록이 있는 경우: 아무 작업이 필요 없습니다.
  • CheckPoint 이후에 COMMIT 기록이 있는 경우: REDO(T)를 진행합니다.
  • CheckPoint 이후에 COMMIT 기록이 없는 경우: 즉시 갱신 방법 사용시 UNDO(T)를 진행합니다.

체크포인트 이전에 COMMIT이 있는 경우 로그에 체크포인트가 나타나는 시점은 이미 변경 내용이 데이터베이스에 모두 기록된 후 이기 때문에 아무런 작업도 필요하지 않습니다. 

체크포인트 이후 COMMIT 기록이 있는 경우는 체크포인트 이후에 변경 내용이 데이터베이스에 반영되지 않았으므로 REDO를 진행합니다. 

체크포인트 이후에 COMMIT 기록이 없고 즉시 갱신 방법을 사용한 경우는 데이터를 로그에 작성하는 과정과 버퍼의 내용을 데이터베이스에 기록하는 작업이 동시에 이루어지므로 버퍼의 내용이 데이터베이스에 반영됐을 수 있기 때문에 원상보구 시키기 위해 UNDO를 해야 합니다. 하지만 체크포인트 이후에 COMMIT 기록이 없고 지연 갱신 방법을 사용한 경우는 갱신 데이터를 로그에 작성한 후 COMMIT을 시작해야 버퍼의 내용 데이터베이스에 기록하는 작업이 이루어지므로 아무 작업도 해줄 필요가 없습니다.

 


오라클로 배우는 데이터베이스 개론과 실습

1장. [DB]데이터베이스 시스템

2장. [DB]관계 데이터 모델

3장. [DB]SQL 기초

4장. [DB]SQL 고급

5장. [DB]데이터베이스 프로그래밍

6장. [DB]데이터 모델링

7장. [DB]정규화(Normalization)

8장. [DB]트랜잭션, 동시성 제어, 회복

참고

https://mangkyu.tistory.com/30

 

[Database] 8. 트랜잭션, 동시성 제어, 회복

[ 본 사진은 쉽게 배우는 오라클로 배우는 데이터베이스 개론과 실습 ppt에서 캡처했습니다. ]이번 장에서는 트랜잭션(Transaction), 동시성 제어(Locking or Currency Control), 회복(Recovery)에 대해 알아보겠

mangkyu.tistory.com

 

 

 

'Database' 카테고리의 다른 글

[DB]Query 최적화 및 튜닝 기술  (1) 2024.09.28
[DB]WITHIN GROUP 구문  (0) 2024.09.27
[DB]정규화(Normalization)  (0) 2024.09.19
[DB]집합 관련 연산자(UNION, INTERSECT, MINUS)  (0) 2024.09.19
[DB]데이터 모델링  (1) 2024.09.13