'Programer Story/DB Stroy'에 해당되는 글 22건

  1. 2007.01.08 데드락 (Dead Rock) -2 by gala
 

작성자 : 손호성 (NTFAQ.CO.KR)

http://www.ntfaq.co.kr or http://www.mcse.co.kr

 

트랜잭션과 락(Transaction and lock)

 

저번 세미나에서도 이것저것 너무 많은 것들을 다룬다고 질책 받았었는데,

이번에도 또 그래야만 할 것 같습니다. 그렇다고 데드락을 설명하면서 트랜잭션과 락을

이해하지 못하고서야 데드락을 정확히 이해하고 있다고 말할 수 없지 않잖아요?

하지만 너무 길어지면, 재미가 없으므로, 아주 간단히 핵심만 쫓아서 트랜잭션과 락을

이해하도록 합시다.

 

우선 몇 가지 용어들을 이해하도록 하죠. 잔소리 같지만, 데이터베이스나 프로그래밍

언어에서 매뉴얼에 있는 용어들을 제대로 이해하는 것만 해도 반은 그렇고 삼분의 일은

먹고 들어간다고 보시면 됩니다. 그리고 그 다음단계는 그러한 용어들이 실제 제품에서

어떻게 구현되는 지를 살펴보면 되는 겁니다. 이런 형태로 공부하게 되면,

제품의 기능에 대한 혹은 아키텍처에 대한 이해도 빠르게 될 뿐더러 공부하는 게

재밌어질 겁니다.

 

처음 다룰 용어는 동시성과 일관성(Concurrency and Consistency)이라는 용어입니다.

동시라는 말이 "둘 이상의 일이 일어나는 시점이나 시간이 같은 때"라는 의미를 가지고 있고,

일관이라는 단어는 "하나의 방법 혹은 태도로 처음부터 끝까지 한결같이 하는 것"이라고

설명되어있습니다. 뭐. 독자들 중에서 이런 의미를 이해하지 못하시는 분들은 없을 거예요.

그러나, 이러한 용어들이 실제 데이터베이스에서는 어떠한 의미로 사용되는 지를

이해해야만 합니다. 데이터베이스의 물리적인 환경이나 프로세스 안에서

동시성과 일관성은 데이터 무결성(Data Integrity)을 지켜주는 방법의 하나가 됩니다.

동시성은 하나의 데이터베이스에 동시에 여러 명의 사용자들이 접속해서 동일한

데이터에 대해서 연산(수정,삭제,검색,추가)하려 할 때 발생할 수 있는 잠재적인

위험을 예방하기 위해서 사용됩니다. 실제로는 락(Lock:잠금)으로 구현되지요.

 

이에 반해 일관성은 서버가 어떤 작업을 수행 중에 하드웨어 및 소프트웨어적인 충격,

즉 서버의 전원이 갑작스레 나간다거나, 윈도 운영체제가 갑작스레 블루 스크린을

띄우고는 리부팅해버리는 순간에 수행되던 작업들의 수행을 보장해 준다는 의미입니다.

이는 실제로는 트랜잭션이라는 개념으로 수행되게 됩니다.

 

우선 그림 1과 2 두 가지를 보고서 이야기해 봅시다. 이 그림 두 가지는 각각

데이터베이스의 동시성(Concurrency) 및 일관성(Consistency) 개념에 대해서

설명하는 그림들입니다.

 

<그림 1> 동시성

 

<그림 2> 일관성

 

동시성 부분을 살펴보도록 하죠. DBMS에는 현재 User A, B, C 그리고 D가 접속해서

하나의 동일한 레코드 A를 사용하고 있습니다. 물론 그러한 경우는 매우 드물겠지만,

동일한 자원인 레코드 A를 동일한 시간에 사용하려고 한다고 가정하죠.

User A는 읽기 작업, User B는 A 레코드에 "AA"라는 데이터를 쓰는 작업이고

User C는 "AAA"라고 데이터를 쓰려고 하고 있으며, User D는 A 데이터를

읽으려 하고 있습니다. User A와 User D는 데이터를 읽는 작업(데이터베이스의 경우에는

SELECT가 되겠지요)을 하려하고 User B와 User C는 데이터를 쓰는 작업(데이터베이스에서는

 Insert 작업)이 됩니다. 그렇다면 User A와 User D는 동일한 데이터를 읽고 있을까요?

일반적으로 동일한 시간에 동일한 레코드를 읽는다면, User A와 User D는 동일한 값들을

가져와야만 할것입니다.

 

그렇지만, 이 경우는 그럴 수가 없지요. 왜냐면 User A는 다른 사용자들이 아무런 작업들을

하지 않은 상태의 값을 읽고 User D는 User B 혹은 User C가 데이터를 쓰는 작업을 하고 난

후에 바뀌어진 값을 읽을 수가 있죠. 물론,  User D도 User B와 User C의 작업들이

이루어지기 전에 수행한 값들을 읽을 수도 있습니다.

 

이렇게 혼란스러운 상황을 정리해 주는 것이 바로 락(Lock,잠금) 입니다.

락은 무질서한 데이터 액세스 상황들을 교통정리해 준다고 보시면 됩니다. 한번에 한명의

사용자만이 자원에 액세스할 수 있도록 결정해 주는 것이지요. User B가 레코드에

데이터를 적고 있으면 User C는 User B가 작업을 종료한 후에야 데이터에 접근할 수

있도록 기다리게 하는 것입니다. 이렇게 하면 작업은 굉장히 단순해 지지요.

 

User A가 데이터를 읽고 있으면 User B, C, D는 작업을 기다리게 됩니다.(물론 실제로는

여러 가지 형태의 락 종류들이 지원되고 있기 때문에 락이 설정 되는 시간은 서로 간에

틀려질 수 있습니다. 읽기 작업과 같은 경우에는 공유 락이 기 때문에 데이터를 읽어오면

바로 락이 해제되지만, 쓰기 작업과 같은 경우에 일반적으로 배타적 락이 설정되기 때문에

트랜잭션이 커밋되기 전까지는 락이 유지되게 됩니다.) 그리고 User B가 User A가 작업이

종료되면 재빨리 들어가서 자신의 쓰기 작업을 완료하고 다음 기다리는 사람에게 제어를

넘기게 되죠. 물론 작업 시작 전에는 락을 걸었다가 작업이 끝나면 락을 풀어줘야만

하겠지요. 락은 이런 식으로 사용됩니다.

 

다음은 일관성 부분의 그림2를 살펴보도록 할까요. 일반적으로 우리는 하나의 작업 혹은

하나의 업무를 기준으로 작업하게 됩니다. 예를 들어서 은행에서 일어나는 작업을 한번

살펴볼까요. 흔히 은행에서 돈을 이체하는 경우가 많이 있죠. 물론 요즘은

인터넷 뱅킹이나 ATM을 많이 이용하지만, 저희는 돈을 직접 가서 이체하는 것으로

가정하죠. 실제 이체 작업은 은행에 찾아가서, 대기표를 받고 돈과 통장을 주고,

담당 직원이 현금을 확인하고 돈을 다른 계좌에 입금하고 두 계좌 정보를 변경하고(한

계좌에서는 돈을 빼고, 다른 계좌에는 돈을 입금하는) 이를 확인하는 복잡한 과정들의

연속입니다. 만일 담당 직원이 돈을 받아서 계좌 정보를 변경하지 않고 통장을 그냥

고객에게 돌려준다면 어떻게 될까요. 아마 난리 나겠죠.

 

이를 실제 컴퓨터에서 하는 작업으로 바꿔 봅시다. 컴퓨터에서 할 수 있는 작업은 INSERT,

UPDATE, DELETE 그리고 SELECT 작업 뿐입니다. (물론 이들을 어우러서 저장 프로시저나

혹은 다른 프로그래밍 방식을 이용해서 하나의 복잡한 작업(Task)을 생성해낼 수 는

있겠지만, 이 부분은 논외로 하고 작업해 봅니다. ) 그러면 코드는 대략 아래와 같이

될 것입니다. 실제 코드가 아니고 적당한 의사코드로 10만원을 이체하는 것입니다.

 

<그림 3> 계좌 이체 의사코드

 

대략 여섯 개의 작업들로 구분해 놓았는데 검사하는 루틴을 더 넣는다면 보다 복잡해 질

수도 있습니다. 10만원 이체를 어떻게 하는지는 아시겠죠. 중요한 것은 이 루틴이 아니라

이러한 작업들을 수행하는 중에 정말 불의의 사고가 발생한다면 어떻게 되느냐 하는

것입니다. 3번과 4번 작업 사이에 갑자기 절전이 되었다고 합시다.

 

그러면 실제로는 3번 작업으로 A 계좌에서는 돈이 빠져나간 후에, B 계좌에는 돈이

추가되지 않았으므로 이체된 돈이 중간에 사라지는 경우가 발생할 겁니다.

실제로는 은행이 가지게 되는 돈이 되겠죠. 이러한 경우는 위험한 상황이죠.

특히 은행과 같은 금융권에서 이러한 일이 발생된다면 아주 문제가 될 것입니다.

실제 데이터베이스의 무결성 제약 조건들을 위반하지는 않지만, 문맥(Context)상

잘못된 데이터 연산이 이루어지는 경우거든요.

 

이러한 경우에 발생할 수 있는 문제들을 해결하기위해서 트랜잭션(Transaction)이라는

개념을 도입했습니다. 이는 하나의 논리적인 작업들로 여러 개의 개별 작업들을 묶어서

수행하는 것입니다. 하나의 단위로 작동하기 때문에 오류가 발생하면 트랜잭션은 취소되고,

오류가 발생하지 않으면 트랜잭션은 완료됩니다. (취소되는 경우를

트랜잭션 롤백(Transaction rollback)이라 하고 완료되는 경우를

트랜잭션 커밋(Transaction commit)이라고 합니다.)

 

게다가 트랜잭션이 커밋되기 전까지는 실제로 데이터를 변경하지 않습니다.

이는 실제로는 트랜잭션이 이루어지는 아키텍처 부분을 좀더 아셔야 하는데, 이 부분은

상당히 복잡하므로, 단순하게 트랜잭션이 Read Ahead 작업이라는 것만 알고 계시기

바랍니다. 실제 중간에 데이터 연산 작업들이 수행되더라도 이를 실제로 반영하는 게

아니라 트랜잭션 로그들만을 죽 기록하게 됩니다. 데이터 변경이 이루어지는 것은

트랜잭션이 커밋되는 순간에서입니다. 만일 트랜잭션 중에 시스템에 문제가 생기게

되더라도 실제로 데이터 변경이 없었기 때문에 실제 데이터베이스에는 무결성 위반이

일어나지 않습니다.

 

이를 흔한 말로 All or Nothing 이라고 부릅니다. 즉 전부 아니면 암~것도 없다는 의미로

트랜잭션은 내부의 모든 작업이 완료된 이후야만이 수행되는 것이다라는 의미입니다.

하지만, 실제로는 트랜잭션이 커밋되지 않은 데이터도 읽을 수 있는 방법이 있는데 이를

팬텀 리드(Phantom Read) 즉, 유령 읽기(?)라고 부릅니다. 왜냐면 트랜잭션이 아직

커밋되지 않은 상태이므로 지금 읽은 값이 실제로는 반영되지 않을 수도 있기 때문에,

존재 하지 않은 값들을 읽을 수도 있다는 데서 이러한 이름이 붙여졌습니다. 팬텀 리드를

제외하면, 실제로는 커밋되는 데이터만을 읽게 되고 트랜잭션 중인 데이터들은 이러한

읽기 작업에 반영되지 않습니다.

 

이제 트랜잭션과 락에 대해서는 기본적인 개념들을 이해하고 계시리라 생각됩니다.

트랜잭션과 락의 종류 및 어떻게 이용하는가와 같은 보다 세부적인 사항들은 SQL 서버

관련 서적들을 참고하시길 바랍니다.

 

그러면 트랜잭션과 락을 연결시켜 볼까요. 트랜잭션과 락을 보통 같이 다루는 이유중의

하나는 트랜잭션들이 락을 이용하여 작업하는 경우가 자주 있기 때문이죠. 예를 들어서

내가 A라는 값을 읽고 이를 사용하여 다른 작업을 진행하려는 의도를 가지고 있는데,

다른 트랜잭션이 이 값을 변경해 버리면 안되겠죠. 이런 경우 해당 트랜잭션들은

로우(Row)  및 페이지(Page), 익스텐트(Extent), 테이블 등의 개체들에 대해서 락을 걸고

사용하게 됩니다. 이런 경우에는 내가 지금 읽고 있는 데이터가 트랜잭션 커밋전까지

유효하다는 사실을 보장할 수 있겠죠.

 

또 하나의 다른 이유는 락이 걸리는 대상들은 데이터베이스의 각종 개체들이 되지만,

락을 거는 행위자는 바로 트랜잭션이 된다는 사실입니다. 예를 들어 하나의 배치 프로세스로

동작하는 저장 프로시저나 트랜잭션을 명시적으로 수행하는 경우 외에, 하나의

INSERT나 DELETE 문을 수행하는 경우에도 이러한 동작 모두를 트랜잭션의 범위에 놓고

사용하게 된다는 것이죠. 마이크로소프트의 SQL 서버의 경우에는 이러한 트랜잭션의 경우

커밋(Commit)문을 사용하지 않고도 트랜잭션이 수행되기 때문에 흔히들 눈치채지

못하지만 이 또한 자동 커밋 트랜잭션(Auto commit transaction)들로 보다 큰 단위의

트랜잭션들과 다르지 않습니다. 즉 아래 리스트1의 코드1과 2는 모두 동일한 것들입니다.

 

 

참고: 자동 커밋 트랜잭션과 암시적 트랜잭션

 

일반적으로 자동 커밋 트랜잭션은 SET IMPLICIT_TRANSACTIONS 옵션이 OFF로

설정되어있기 때문에 자동으로 커밋되는 것입니다. 이를 ON으로 실행해 놓으면

COMMIT TRAN을 해야만 해당 트랜잭션이 완료됩니다. 코드 3은 옵션값을 ON으로 해서

ROLLBACK TRAN을 테스트해본 것입니다. 실제로 자동 실행 트랜잭션과 암시적

트랜잭션을 구분하는 것은 그다지 중요하지 않습니다. 암시적 트랜잭션은 옵션을

주고 커밋 혹은 롤백 할 수 있다는 점이 특징이고 자동 실행 트랜잭션은 아무런

옵션사항 설정 없이 한 문장을 수행할 경우 자동으로 BEGIN TRAN문과

COMMIT TRAN문이 붙어서 수행된다는 사실에만 차이가 있을 뿐입니다.

 

 

 

 

코드 1:

DELETE FROM table_name

 

코드 2:

BEGIN TRAN

  DELETE FROM table_name

COMMIT TRAN

 

코드 3:

USE PUBS

 

-- 샘플용 TEST 테이블 생성

SELECT * INTO TEST FROM TITLES

 

-- 암시적 트랜잭션 선언

SET IMPLICIT_TRANSACTIONS ON

GO

 

-- TITLES 테이블을 삭제

DELETE FROM TEST

GO

 

-- TITLES 테이블 검색

SELECT* FROM TEST

GO

 

-- 현재 트랜잭션이 수행중인 지를 확인

SELECT @@TRANCOUNT

GO

 

-- 트랜잭션 롤백

ROLLBACK TRAN

GO

 

-- 테이블 확인

SELECT * FROM TEST

Posted by gala
l