'DATABASE'에 해당되는 글 4건

  1. 2007.08.08 SQLite C API 소개 (4)
  2. 2007.07.06 Thread-safe(3) : Lock in SQLite (2)
  3. 2007.06.08 SQLite의 라이센스
  4. 2007.04.18 DB Isolation Level

SQLite C API 소개

|
꽤 간단한 내용이지만, SQLite는 한글 매뉴얼이 별로 없는 탓에 끄적여 봅니다.

그간 C API를 써 본 DB들을 보면, ODBC와 비슷한 형태를 갖고 있는 경우가 많습니다. 오라클은 C API보다는 Pro*C를 쓰니 예외로 하고요.. 오라클 C API가 어떻게 되어 있는 지도 모르겠네요 -_-;

대략 Open-Prepare-Bind-Execute-(Fetch)-Close의 과정을 거치는데 open-close는 당연히 DB를 열고 핸들러(DB 정보 구조체)를 초기화하는 것과 종료하는 것이니 어려울 것 없고요.

Prepare는 DB에게 자기가 실행할 SQL 문장을 던져주고 미리 실행 플랜을 준비하도록 합니다. binding 할 값이 없다면 prepare 하지 않고 곧바로 execute도 가능합니다만, 예를 들어 문장은 동일한데 값만 바뀌는 쿼리를 여러 번 실행할 경우에 다이렉트로 그 때마다 실행을 한다면, 매번 DB 엔진은 SQL 문장을 해석하고 어떻게 실행할 지를 결정하고, 값에 따라 실행하게 되겠죠. 값만 달라진다면, 문장을 해석하고 실행할 지 결정하는 부분은 사실 한 번만 해도 되는데 말이죠.

그리고 bind는 실행하기 전 쿼리에 값을 할당하는 것입니다. 바인딩할 부분은 보통 '?'으로 표현하는데 'SELECT * FROM test WHERE id = ?' 이런 식으로 쿼리 문장을 표현할 수 있습니다. 이 문장 그대로 prepare를 딱 한 번만 한 다음 실행할 때는 '?'위치에 integer 변수를 binding 하여 여러 가지로 id 값을 주어서 결과를 가져올 수 있습니다.

Execute는 말 그대로 문장을 실행하는 것인데 DB에서는 select 쿼리가 실행 되었을 경우 result set 공간에 결과값을 저장해 놓습니다.
Fetch는 그런 result set의 값을 가져오는 과정입니다. insert-update-delete를 실행했을 경우는 당연히 필요없는 과정이죠.

상당히 대충 설명했는데 -_- 직접 예를 보겠습니다.
testtbl의 스키마는 'CREATE TABLE testtbl(id int, value text);' 입니다.

예제 프로그램은 id를 1부터 3까지 돌리며 해당되는 레코드를 긁어오는 것입니다.


#include        <stdio.h>
#include        <stdlib.h>
#include        <string.h>

#include        <sqlite3.h>

#define EXIT_WITH_ERROR(func, dbh) \
do {    \
        if (func != SQLITE_OK)  \
        {       \
                fprintf(stderr, "line %d: %s \n: %s\n", __LINE__        \
                                , #func ,sqlite3_errmsg(dbh));        \
                exit(sqlite3_errcode(dbh));     \
        }       \
} while(0)

int
main()
{
        sqlite3    *dbh;
        sqlite3_stmt    *stmt;
        int     i, j;
        char    *errmsg;

        char    *sql = "select * from testtbl where id=?";

        /* "testdb"라는 DB 파일을 연다 */
        EXIT_WITH_ERROR(sqlite3_open("testdb", &dbh), dbh);

        /* sql 문장에 대해 질의 플랜을 준비한다 */
        EXIT_WITH_ERROR(sqlite3_prepare(dbh, sql, strlen(sql)
                                , &stmt, NULL)
                        , dbh);

        for (i=1; i<=3; i++)
        {
                /* '?' 위치에 i 값을 바인딩한다 */
                EXIT_WITH_ERROR(sqlite3_bind_int(stmt, 1, i) , dbh);

                /* sql 쿼리 실행 */
                while (sqlite3_step(stmt) != SQLITE_DONE)
                {
                        /* 실행된 결과값을 받아와 출력 */
                        printf("%d : %s\n" , sqlite3_column_int(stmt, 0)
                                        , sqlite3_column_text(stmt, 1));
                }

                sqlite3_reset(stmt);
        }
        sqlite3_finalize(stmt);
}

EXIT_WITH_ERROR는 에러 체크를 위해 제가 작성한 매크로입니다. 실제 sqlite3_ 로 시작하는 함수들만 보시면 됩니다.

처음에 DB 오픈을 하고 sql 문장에 대해 prepare를 수행하죠?
prepare의 마지막 NULL 인자는 원래 char *가 들어가는 곳입니다. sql 문장이 하나가 아니라 여러 개일 경우는 첫번째 것만 prepare하고 마지막 인자의 포인터 변수에 두번째 문장이 시작하는 위치를 가리키도록 되어 있습니다.
순차적으로 수행되어야 할 sql 문이라면 변수를 따로 안 두고 실행 단위별로 sql 문을 선언하여 쓸 수 있다는 장점이 있습니다(자세한 설명은 sqlite3_prepare()의 매뉴얼 페이지를..).

그 다음에는 for 문을 돌며 바인딩-실행-결과 페치를 반복합니다.
바인딩은 for 문의 변수인 i를 그대로 이용합니다. 바인딩 함수의 두번째 인자는 binding variable의 순서입니다. 첫번째니까 '1'이죠. 여러 개 있으면 앞에서부터 1,2,3,..이 됩니다. 숫자이므로 sqlite3_bind_int를 썼고, 그 외에 _null, _text 등 다양한 바인딩 함수가 있습니다.

그리고 sqlite3_step()을 써서 실행을 하는데 특이한 점은 이 함수가 실행과 페치를 동시에 수행한다는 점입니다.

예를 들어 MySQL이라면, mysql_query()로 실행하고, mysql_store_result()로 결과 셋을 가져 온 후, mysql_fetch_row()을 반복 실행하여 한 건씩 긁어오는 과정을 거칩니다.

반면 SQLite는 sqlite3_step을 실행하면 실행하고 결과 셋의 첫번째 레코드를 가져오는 과정까지 거칩니다.
그 다음은 각 컬럼을 sqlite3_column_* 함수들을 써서 가져오면 됩니다(컬럼 번호는 0부터 시작합니다).
그리고, 두번째 이후로 sqlite3_step()을 실행하면 sql을 실행하지는 않고 결과 셋의 다음 레코드들을 가져오게 되는 것이죠. 만약 가져올 결과 레코드가 없으면 SQLITE_DONE을 리턴합니다.
즉, 만약 결과가 실행할 때마다 한 건씩만 있다면 굳이 while 문을 돌릴 필요는 없습니다. 실행 결과는 다음과 같습니다.

1 : aaa
1 : ddd
2 : bbb
3 : ccc

만약 while 문을 쓰지 않았다면 결과에는 1:ddd가 나오지 않을 것입니다. id 컬럼이 unique 하다면 while문을 쓰지 않아도 되겠지만, 방어적인 프로그래밍을 위해서는 while을 사용하는 스타일이 나을 듯 합니다 ^^

그리고 결과셋을 다 긁어왔다면 sqlite3_reset을 실행해서 바인딩 된 변수들을 초기화 해 주어야 합니다.
그렇지 않을 경우 두번째 bind를 할 때에 에러가 납니다.

이상으로 간단히 SQLite의 C API에 대해 살펴 보았습니다, 간단하죠? ^^
개인적으로는 다른 DB에 비해 비교적 API 사용법이 쉽다는 느낌이 듭니다. DB 데몬이 없기 때문에 API를 더욱 간략하게 만들 수 있었는 지도 모르겠습니다.

하지만 C를 쓸 경우는 아무래도 다른 언어보다는 조금 번거롭죠.
pysqlite 같은 걸 쓸 경우는 훨씬 편합니다. 이후에는 C언어 이외의 SQLite API에 대해 한 번 보도록 하겠습니다.

'Programming Story > SQLite' 카테고리의 다른 글

SQLite non-C API 소개  (3) 2007.08.20
SQLite C API 소개  (4) 2007.08.08
Thread-safe(3) : Lock in SQLite  (2) 2007.07.06
SQLite의 라이센스  (0) 2007.06.08
Trackback 0 And Comment 4

Thread-safe(3) : Lock in SQLite

|

무지무지 오랜만의 포스팅이군요. 별로 바쁘지도 않았던 거 같은데...ㅡㅡ

시간 날 때마다 SQLite 소스를 보는 터라 -아직도 많이 부족하지만- thread-safe에 대한 포스팅을 몇 했던 만큼 SQLite의 Locking 메커니즘에 대해 간략히 적어볼까 합니다.
하지만 아시는 분은 다 아시듯 쓰고 보면 별로 안 간략하다는 거...


개  요

우선 DB에서의 lock이란 어떻게 보면 상당히 추상적인 개념이라고 할 수 있습니다.
semaphore나 pthread의 mutex같은 것은 거의 가장 하위 레벨의 원자적인 락을 위한 구현이고, DB에서의 락은 이런 것들을 이용해 좀 더 고차원적인 동시성 제어를 구현한 것입니다. 그러므로 DB의 Lock과 OS 차원에서 말하는 세마포어나 뮤텍스 등등의 락은 의미가 서로 다름을 염두에 두시기 바랍니다.




Locking Logic

SQLite는 데이터를 페이지 단위로 관리하며 페이지 단위로 락을 겁니다. 실제 데이터 파일은 페이지의 모음인 셈이지요.
SQLite의 락에는 다섯가지의 레벨이 있습니다. pthread의 rw락의 확장 스타일이라고 보면 되겠습니다(rw락은 읽기-쓰기 락이라고도 하는데 읽기 락은 동시에 여러 쓰레드가 락을 잡을 수 있지만 쓰기 락은 단 하나의 쓰레드만이 잡을 수 있습니다, 읽기만 하는 경우는 데이터 변경이 일어나지 않으므로 여러 개의 쓰레드가 동시에 읽어도 문제가 없으므로 듣기엔 꽤 좋습니다만.... 일반 mutex 락보다 느립니다 -_-).

다섯가지 레벨은 각각 Unlocked, Shared, Reserved, Pending, Exclusive인데 Shared는 읽기 락입니다. 동시에 여러 개의 쓰레드가 Shared 락을 갖고 읽을 수 있는 것입니다.
Reserved는 '내가 좀 있다 쓰기를 시작 할 거야'라는 의미의 락입니다. Reserved 락의 쓰레드가 하나 존재하면 다른 쓰레드들은 Reserved 락을 획득할 수 없습니다. 하지만 Shared를 획득하여 읽는 것은 가능합니다.
실제로 쓸 준비가 되면 Reserved 락의 쓰레드는 Pending 락을 획득합니다. Pending 상태가 되면 이제 더 이상 Shared 락이 추가되는 것이 금지되고 기존의 Shared락을 갖고 읽기를 수행하는 쓰레드들이 종료되기를 기다립니다(Shared락도 락을 얻기 전 먼저 Pending 락을 획득합니다).
Shared 락을 가진 쓰레드들이 모두 없어지면 Pending 락은 Exclusive 락으로 바뀌고 쓰기 작업을 수행합니다.

실제로 락을 요구할 때는 static 함수인 pager_wait_on_lock()을 이용해 호출하게 되는데 이는 일종의 wrapper함수이며 내부에서는 각 OS 별로 구현된 Locking 함수를 호출하게 됩니다. 컴파일시에 어떤 OS의 locking 함수를 호출할지가 결정되겠죠.

SQLite는 오라클, MySQL, PostgreSQL 등등의 여타 DB들처럼 DB 서버가 뜨고 클라이언트 툴이나 API를 이용하는 서버-클라이언트 방식이 아닌, 라이브러리를 통해 DB 파일에 직접 억세스하는 방식입니다. 다른 DB들처럼 로드 밸런싱이나 쓰레드 풀을 관리할 필요도 없고, 사용자 권한 같은 것도 관리할 필요가 없어지니(파일에 대한 Unix 계정 권한이 곧 DB권한이 되는 셈이니까요) 편하기는 하지만 서버 데몬에서 동시성 관리를 해 줄 수가 없다는 점도 있지요.

그래서 SQLite는 라이브러리 코드 내에서 mutex를 써서 멀티 쓰레드에 대한 대비를 해주면서 또한 멀티 프로세스를 위해 DB 파일에 fcntl()을 이용해 락을 겁니다.
즉, 프로세스간에든 쓰레드간에든 위에 언급한 Shared-Reserved-Pending-Exclusive 락의 구조는 동일하며 fcntl을 사용하는지 mutex를 사용하는 지가 다를 뿐입니다.


구  현

사설이 길었네요 ^^ 실제 코드를 잠깐 보지요.
실제 락을 다루는 함수는 OS별로 달리 구현되어 있지만(윈도우, 유닉스, OS/2 세 가지로 구현되어 있군요) 아는게 유닉스(or 리눅스)밖에 없으니 이 쪽만 살펴보겠습니다.

락을 거는 unixLock() 함수는 다음의 순서를 거칩니다.

1. mutex 락 획득(추가로 파일 락에 대한 ownership을 가진 쓰레드인지 확인(뒤에 설명))
2. Shared 락의 경우 다른 Shared 락이나 Reserved 락이 존재하면 락의 카운트만 증가시키고 종료
3. Pending 락을 파일에 건다.
4. Share 락인 경우 read lock을 파일에 걸고 Pending 락은 해제
5. Reserved나 Exclusive인 경우 write lock을 파일에 건다(다른 락이 진입을 시도하다가 Pending락을 발견하고 락 획득을 취소시켜야 하므로 Pending은 해제하지 않는다)
6. mutex 해제

위에서 5번 같은 경우 Reserved나 Exclusive랑 Pending이 같이 걸릴 수 있는 것 처럼 써놨는데 실제로 같이 걸립니다. fcntl은 파일 전체에 락을 거는게 아니라 원하는 옵셋에 원하는 바이트만큼 락을 걸기 때문에 SQLite는 Shared, Pending, Reserved 영역을 구분해 놓고 락을 걸도록 되어 있습니다(Exclusive는 Shared와 Pending에 동시에 락을 걸게 되므로 영역이 따로 없습니다).

경우에 따라 순서가 다르긴 하지만 기본적으로는 fcntl을 상황에 따라 다르게 사용하는 코드이므로 모두 볼 필요는 없고 4번의 경우를 한 번 보겠습니다(indentation이 좀 이상하군요 -_-).




/* SHARED_LOCK인 경우 fcntl으로 SHARED에 read 락을 걸고
** PENDING 락은 해제한다
*/

if( locktype==SHARED_LOCK ){
/* SHARED_LOCK의 지정 옵셋에 read-lock 획득,
** l_type은 이미 3번 과정에서 지정됨 */

lock.l_start = SHARED_FIRST;
lock.l_len = SHARED_SIZE;
s = fcntl(pFile->h, F_SETLK, &lock);

/* 임시로 걸었던 PENDING 락 해제 */
lock.l_start = PENDING_BYTE;
lock.l_len = 1L;
lock.l_type = F_UNLCK;
if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){
rc = SQLITE_IOERR;
goto end_lock;
}
if( s ){
rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
}else{
pFile->locktype = SHARED_LOCK;
pFile->pOpen->nLock++;
pLock->cnt = 1;
}
}









물론 SHARED_FIRST, SHARED_SIZE, PENDING_BYTE등은 매크로로 지정되어 있는 숫자들이구요.
마지막에는 구조체의 파일 락 카운트와 락 카운트를 1씩 증가시키고 끝냅니다. pFile->pOpen->nLock은 파일에 대한 락의 갯수이며, pLock->cnt는 락 자체의 카운트입니다(SHARED가 아니라면 1을 넘어갈 수 없겠죠).

위의 코드는 파일에 대한 락이므로 프로세스 간에는 관리가 되지만, 실제 파일을 다루지 않고 있던 쓰레드가 파일에 대한 락을 획득하려고 할 경우는 뮤텍스와 상관없이 문제가 될 수 있습니다.
이를 위해서 SQLite는 락 정보에 대한 해쉬 테이블을 유지하면서 필요시마다 thread id를 비교하여 올바른 쓰레드만이 파일 락에 접근할 수 있도록 제어하고 있습니다. 이 부분이 1번 과정의 ()의 내용에 대한 것입니다.

그럼 마지막으로 위의 과정들을 수행하기 전 뮤텍스 락을 어떤 방식으로 획득하는지 살펴 보지요.
단순히 pthread_mutex_lock만 하는 건 아니거든요. ^^


void sqlite3UnixEnterMutex(){
        /* 보조 뮤텍스 획득 */
        pthread_mutex_lock(&mutexAux);

        /* 메인 뮤텍스가 풀려 있는 상태거나
         * 현재 메인 뮤텍스를 가진 쓰레드가 아닐 경우 */

        if( !mutexOwnerValid || !pthread_equal(mutexOwner, pthread_self()) ){
                /* 보조 뮤텍스 풀고 메인 뮤텍스 획득 */
                pthread_mutex_unlock(&mutexAux);
                pthread_mutex_lock(&mutexMain);

                /* 다시 보조 뮤텍스 획득 */
                pthread_mutex_lock(&mutexAux);

                /* 뮤텍스 소유자로 쓰레드 자신을 지정,
                 * 소유 플래그 셋팅 */

                mutexOwner = pthread_self();
                mutexOwnerValid = 1;
        }

        /* 카운터 증가시키고 보조 뮤텍스 해제 */
        inMutex++;
        pthread_mutex_unlock(&mutexAux);
}


보시다시피, 실제로는 두 개의 mutex 변수를 쓰고 있습니다.
하나는 실제 락을 위한 메인 뮤텍스이며 하나는 뮤텍스 정보 저장을 위한 변수 억세스에 쓰이는 보조 뮤텍스입니다.
mutexOwner는 메인 뮤텍스를 갖고 있는 쓰레드의 tid, mutexOwnerValid는 뮤텍스를 갖고 있는 쓰레드가 있으면 1로 세팅되는 플래그 변수이며 inMutex는 같은 쓰레드가 여러 번 뮤텍스를 요구할 경우 증가하는 카운터입니다.

실제로 뮤텍스를 같은 쓰레드가 새로 획득하려고 시도하는 것은 Undefined Behavior입니다. 리눅스 같은 경우 데드락이 걸리더군요. Recursive Mutex를 위해서는 뮤텍스 타입을 PTHREAD_MUTEX_RECURSIVE으로 지정하면 되긴 합니다만, 위와 같은 방식이 아마 성능이 좀 더 낫다든가 하는 이유가 있겠죠 -0-

...아직도 잘 모르는 처지에 나름대로 소스 분석을 하고 글까지 쓰려니 시간이 많이 드는군요. 아직 저도 이해 안되는 부분들도 있고...^^;
소스는 SQLite-3.3.6 기준이므로 현재 버전과는 차이가 좀 있을 수 있습니다.

이번 글은 여기서 마치며... 담에 기회되면 다른 주제로 또 쓰도록 하겠습니다. 질문있는 분은 허심탄회하게 댓글로~

'Programming Story > SQLite' 카테고리의 다른 글

SQLite non-C API 소개  (3) 2007.08.20
SQLite C API 소개  (4) 2007.08.08
Thread-safe(3) : Lock in SQLite  (2) 2007.07.06
SQLite의 라이센스  (0) 2007.06.08
Trackback 0 And Comment 2

SQLite의 라이센스

|
SQLite는 라이센스가 없습니다 -_-;;
아무렇게나 소스를 써도 됩니다. 개발자들이 존경스럽지요.
리눅스나 gcc 같은 것들은 GPL을 따르기 때문에 소스를 공개해야 된다는 제약이 있습니다. 그리고 BSD 라이센스는 아무렇게나 써도 되지만 라이센스는 명시해야 될 겁니다(아마도.. 기억이 잘..).
하지만 SQLite는 라이센스가 없기 때문에 정말로 '아무렇게나' 써도 됩니다.

소스 코드의 앞 부분에는 주석으로 다음과 같은 내용이 있습니다. hash.c의 내용입니다.

/*
** 2001 September 22
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This is the implementation of generic hash-tables
** used in SQLite.
**
** $Id: hash.c,v 1.18 2006/02/14 10:48:39 danielk1977 Exp $
*/

번역하면 다음과 같은 뜻이 되겠습니다.

2001. 9. 22

저자는 이 소스 코드에 대한 저작권을 요구하지 않습니다. 법률적인 주의사항을 대신하여 여기 축복의 글을 씁니다.

선을 행하되 악을 행하지 않기를.
당신 자신과 타인에 대한 용서를 구하기를.
자유롭게 나눠 가지며, 당신이 준 것 이상 바라지 않기를 기도합니다.

********************************************************************

이 소스는 SQLite에서 쓰는 일반 해시 테이블에 대한 구현입니다.

~~~ (소스 수정 정보)


가슴이 왠지 찌르르 하군요 -_-;
기술 유출과 관련해 악다구니를 부리는 사람들에게 이 글을 바치고 싶습니다...ㅡㅡㅋ

다음에 기회가 되면 SQLite에 대해 분석한 내용에 대해 포스팅 할까 합니다(별로 본 건 없지만 -_-).

'Programming Story > SQLite' 카테고리의 다른 글

SQLite non-C API 소개  (3) 2007.08.20
SQLite C API 소개  (4) 2007.08.08
Thread-safe(3) : Lock in SQLite  (2) 2007.07.06
SQLite의 라이센스  (0) 2007.06.08
Trackback 0 And Comment 0

DB Isolation Level

|

데이터베이스는 단순히 데이터만을 관리하는 프로그램은 아닙니다. 단순 데이터 관리라면 엑셀 같은 프로그램을 쓰는게 훨씬 편할 수도 있지요.

DB의 또다른 중요한 기능은 데이터의 무결성 보장과 관련된 트랜잭션의 ACID 보장이라고 할 수 있습니다. 여기서 트랜잭션은 DB에 접속한 사용자(프로세스)의 작업 단위를 말하는데 은행 계좌에서 잔액을 읽어 1000원을 입금한다면 select 한 번과 +1000 된 값을 update 하는 것이 하나의 트랜잭션이라고 할 수 있습니다.
ACID는 Atomicity, Consistency, Isolation, Durability의 약자인데 트랜잭션 보장을 위해 갖추어야 할 속성들을 의미합니다.

어쨌든 이를 위해서 DB는 ACID가 의미하는 바와 같이 트랜잭션이 원자적이면서도 독립적인 수행을 하도록 지원해야 됩니다. 그래서 Locking이 필요한데 이는 간단히 하나의 트랜잭션이 DB를 다루는 동안 다른 트랜잭션이 관여하지 못하게 막는 것입니다. 세부적으로 말하면 Locking도 여러 종류가 있지만...

하지만 단순하게 무조건적인 Locking으로 동시에 수행되는 많은 트랜잭션들을 일렬로 죽 세워서 순서대로 처리하는 방식으로 구현된다면 DB의 응답성은 형편없이 떨어질 것입니다. 반대로 응답성을 높이기 위해 Locking 범위를 줄인다면 잘못된 값이 처리 될 여지가 있습니다. 다시 말하면 선택의 문제입니다.
이와 관련된 선택 사항이 Isolation Level이라고 보시면 됩니다.

DB 구조에 대해 조금 깊이 알지 않으면 생소한 단어이긴 합니다만, DB가 늘 완전하게 동작할 수만은 없다는 것을 알아둘 필요는 있겠지요.
Isolation Level에는 다음과 같은 네 가지가 있습니다.

  • Serializable
  • Repeatable Read
  • Read Committed
  • Read Uncommitted


1. Serializable

거의 무조건적으로 트랜잭션을 일렬 수행하는 레벨입니다. 위에서 말했던 무조건적인 Locking의 의미와 비슷합니다.


2. Repeatable Read

데이터를 읽는 쿼리는 쿼리에 해당되는 레코드에만 락을 지정합니다. 이 레벨에서는 Phantom Read를 허용하는데 이는 하나의 트랜잭션에서 같은 SELECT문을 두 번 날렸을 때 다른 트랜잭션이 그 사이에 INSERT를 했다면 두번째 SELECT에서는 페치된 레코드 건수가 늘어날 수 있다는 의미입니다.


3. Read Committed

Repeatable Read는 쿼리가 읽은 레코드를 수정하지 못하도록 막지만 RC 레벨에서는 막지 않습니다. 이 레벨에서는 Non-repeatable Read를 허용하는데 이는 한 트랜잭션의 동일한 SELECT 두 건 사이에 UPDATE가 일어났다면 읽은 값이 바뀔 수 있다는 의미입니다.
Repeatable Read 레벨에서는 두번째 SELECT에서 레코드 건수가 늘어날 수는 있었지만 첫번째 SELECT에서 읽은 레코드에 한해서는 그 값을 보장했습니다. 하지만 RC레벨에서는 그조차도 보장하지 않습니다. 미묘한 차이이니 주의 깊게 생각해 보시길...ㅡㅡ


4. Read Uncommitted

DB들이 잘 지원하지 않는 수준의 레벨입니다. 이 레벨에서는 Dirty Read가 허용됩니다. RR이나 RC레벨에서 허용하는 현상들은 어디까지나 INSERT나 UPDATE가 COMMIT 된 경우에 한해서였습니다만 이 레벨에서는 커밋되지 않은 데이터도 실시간으로 읽어오게 됩니다.
COMMIT이라는 것은 데이터에 대한 변경을 완료하고 승인한다는 의미이고 커밋하지 않고 ROLLBACK 한다면 쓸모없는 데이터가 되므로 커밋되지 않은 데이터를 읽어들이는 Dirty Read는 데이터 정합성에 문제가 생길 여지가 많습니다.


제가 오라클과는 별로 친하지 않아서 잘 모르겠지만 오라클의 경우는 RC가 디폴트이고 RC와 Serializable을 지원한다고 들었습니다.
Phantom Read나 Non-repeatable Read는 작업에 따라서는 허용해도 상관없을 수도 있고(어쨌든 커밋된 데이터이므로) DB의 응답성과 속도 문제도 있기 때문에 기본적으로 RC레벨을 허용하는 DB들이 있습니다.

만약 저런 현상을 두고 DB에 버그가 있다거나 하는 사람이 있다면 잘 설명해 주시기 바랍니다.
비슷한 일을 겪고 조금 열받아서 계몽 목적으로 씁니다 -_-;

'Programming Story' 카테고리의 다른 글

Thread-safe  (0) 2007.05.27
DB Isolation Level  (0) 2007.04.18
버전의 의미  (0) 2007.03.21
엔트로피  (0) 2007.02.27
Trackback 0 And Comment 0
prev | 1 | next