(Database) 트랜잭션(ACID)과 무결성

2022. 7. 9. 02:40컴퓨터 공학/DB

반응형

구글에 트랙잭션을 검색하면 가장 많이 나오는 말이 있다.

"DB의 상태를 변화시키기 수행하는 단위"

그런데 이것 만으로는 트랜잭션에 관해 전혀 감이 오지 않는다. (나만 그런가?)

나는 이 문장을 접했을 때 드는 생각은

아 DB에 접근하면 다 트랜잭션이구나


트랙잭션 자체의 개념에 대해 이해하지 못하면 스프링 서비스 레이어에 붙이는 @Transactional이나

MySQL에서의 잠금, 동시성에 대한 이해를 할 수 없다고 생각한다.


🧐What is Transaction?


트랙잭션을 이해하는데 가장 도움되는 말은 이것이라고 생각한다.

트랙잭션은 작업의 완전성을 보장해 주는 것이다. 즉, 논리적인 작업 셋을 모두 완벽하게 처리하거나, 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능이다. - Real MySQL


작업의 완전성.

작업의 완전성이 나왔으니 트랜잭션의 특징인 ACID에 대해서 이야기 해보려 한다.


ACID는 각각 원자성(atomicity), 일관성(consistency), 격리성(isolation), 지속성(durability)을 말한다.

이 ACID의 특징을 모두 갖춰야지 작업의 완전성을 보장해주고, 이것을 트랜잭션이라고 할 수 있는 것이다.

그럼 각각 ACID에 대해서 살펴보자.

🧐원자성(Atomicity)


원자성이란 트랜잭션과 관련된 일이 모두 수행되었거나 되지 않았거나를 보장하는 것을 말한다.

예시를 들어보자.

테이블 foo에는 cnt라는 속성이 있고, 이는 pk(기본키)이다.

그리고 foo 테이블에 다음과 같은 SQL쿼리를 날렸다.

insert foo (cnt) values(3);



그리고 다시 한번 다음과 같은 쿼리를 날렸다.

insert foo (cnt) values(1), (2), (3);


결과가 어떻게 될까?

Duplicate entry '3' for key 'PRIMARY'

중복 키 에러가 발생한다.

그리고 Select를 통해서 조회해보면,

3만 남아있는 것을 확인할 수 있다.

어째서 그런 것인가? 1, 2, 3 중에서 1과 2는 DB에 없는 키인데?

이것이 트랜잭션에서의 원자성이다.

3에서 에러가 발생했지만, 3만 롤백하는 것이 아니라, 1,2,3 모두에 대해 롤백을 했다.

1과 2는 커밋이 됐지만, 3은 커밋이 않고 롤백을 시킨다.

하지만 원자성의 원칙에 따라 일부만 취소될 수 없으므로, 모두 롤백된다.

이것이 바로 원자성의 원칙이다.

🧐일관성(Consistency)


일관성은 '올바른 상태'로만 데이터를 변경해야 하는 것을 말한다.

'올바른 상태'가 무엇인가?

대표적인 예시로 은행거래를 들 수 있다.

A는 500만원이 있고, B는 0원이 있다면 B는 A에게 300만원을 입금하는게 불가능해야한다. (마이너스 통장은....)

이처럼 일관성 있는 규칙을 유지해야하며 유효해야 한다.

🧐격리성(Isolation)


격리성은 트랜잭션 수행 시 서로 끼어들지 못하는 것을 말한다.

'그럼 완전히 끼어들 수 없어야 트랜잭션인가?' 라는 생각이 들 수 있다.

그건 아니다.

격리성에는 격리 수준이 존재한다.

격리 수준은 크게 "Read Uncommitted", "Read Committed", "Repeatable Read", "Serializable"

이렇게 4가지로 나뉜다.

source realMySQL

아래로 내려갈 수록 격리 수준이 매우 높아지고, 동시 처리 성능도 떨어지는 것이 일반적이다.
(MySQL에서는 SERIALIZABLE만 아니라면 성능의 개선이나 저허가 거의 발생하지 않는다.)

표에 나와있는 Dirty Read, Non-Repeatable Read, Phantom read에 대해서는 격리 수준을 설명하면서 같이 하면 좋을 것 같다.

각기 격리 수준은 이름 그대로 이해하면 편하다.

먼저 READ UNCOMMITTED 는 말 그대로 DB에 커밋되기 이전에 읽을 수 있는 것이다.

DB의 작업 순서를 생각해보자.

쿼리를 날려 트랜잭션이 수행되고, 커밋을 수행해 DB에 결과가 반영된다.

그럼 READ UNCOMMITTED는 언제 상태를 말하는 것일까?

바로 트랜잭션이 DB에 완전히 적용되기 직전 즉, 커밋이 수행된 직전을 말하는 것이다.


그러니까 READ UNCOMMITTED는 트랜잭션 이후에 아직 커밋되지 않은 데이터를 다른 사용자가 조회할 수 있다는 것이다.

이렇게 되면 어떤 문제가 발생할까?

예를 들어 사용자 A가 insert 쿼리를 날려 DB에 새로운 데이터를 저장하려 했고,
커밋 직전에 사용자 B가 select를 통해서 DB값을 모두 불러왔다.


그런데 사용자 A가 날린 쿼리가 알 수 없는 문제로 인해 롤백되었다.


하지만 사용자 B는 여전히 데이터가 존재한다고 생각하고 처리를 할 것이다.


이때 발생하는 것,

즉 어떤 트랜잭션에서 처리한 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상을 바로 더티 리드(Dirty Read)라고 한다.

더티 리드가 허용되는 격리 수준은 READ UNCOMMITTED이다.

다른 격리 수준에서는 더티 리드가 나타나지 않는다.

READ UNCOMMITTED는 데이터 정합성에 문제가 많은 격리 수준이다.
(데이터 정합성을 위해 존재하는 트랙잭션의 목적과는 거리가 멀다.)


다음으로 READ COMMITTED가 있다.

커밋이 완료된 데이터에 대해서만 읽을 수 있다는 것이다.

이 격리 수준에도 문제는 존재한다.

사용자 A가 id가 3인 사용자의 이름을 laura에서 mike로 바꾸려고 쿼리를 날렸다.

쿼리가 커밋 되기 이전에 사용자 B가 id가 3인 사용자의 이름을 조회했다.

그러면 사용자 B에게 전달되는 이름은 mike가 아니라, laura이다.

왜 그럴까?

이는 사용자 B가 조회한 데이터는 언두 로그에 복사된 데이터이기 때문이다.

이런 문제를 Non-Reapeatble read현상이라고 한다.(반복조회가 불가능하다.)

시점 A에서는 조회가 불가능 했는데, 시점 B에서는 조회가 가능하는 이런 문제가 생길 수 있다.

이는 항상 같은 결과를 가져와야 한다는 데이터 정합성에 어긋나는 것이다.


다음은 Repeatable Read이다.

이 격리 수준은 InnoDB에서 기본으로 사용되는 격리 수준이다.
(당연히 MyISAM, MEMORY 스토리지 엔진은 트랜잭션을 지원하지 않기 때문에 해당사항이 없다.)


이 격리 수준부터는 Non-Reapeatble read 부정합이 발생하지 않는다.

MySQL의 경우 InnoDB가 롤백 될 가능성을 대비해 언두 로그에 변경 되기 전 레코드를 저장한다.

이러한 변경 방식을 MVCC(Multi version Concurrency Control)라고 부른다. (MVCC는 다음에...)

이 격리 수준에서는 다른 트랜잭션이 수정한 행을 다른 트랜잭션이 수정할 수 없도록 막아준다.
(당연히 동시성에 대한 제약이 걸린다.)

그렇기 때문에 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있다.(언두 영역에 백업된 데이터 덕분에)


하지만 이 격리 수준도 문제는 있다.

행의 수정이 아니라 새로 추가된 행에 대해서는 막을 수 없다는 것이다.

가장 먼저 사용자 B가 300원 이상 가진 사용자를 모두 조회했더니 Laura 한명이 결과로 반환되었다.

그리고 사용자 A가 insert로 jordan, 500을 추가했다.

그리고 다시 사용자 B가 300원 이상 가진 사용자를 모두 조회했더니 Laura 한명이 아니라, jordan까지 두명이 나왔다.

이러한 현상을 Phantom Read라고 한다.

레코드가 보였다 안 보였다 하는 현상을 말한다.


마지막으로 SERIALIZABLE이 있다.

이 격리 수준은 매우 매우 엄격하면서도 매우 단순하다.

당연히 엄격하기 때문에 동시성 성능이 엉망진창이다.

잠금이 필요 없는 일관된 읽기(Non-locking consistent read)를 지원하며,

당연히 다른 트랜잭션에서 현재 다른 사용자가 사용하고 있는 행에 대해 격리를 시키고,

접근 하려 할 경우 기다려야한다.

특히 읽기에도 작업 공유 잠금(읽기 잠금)을 획득해야만 하며, 다른 트랜잭션에서 절대 절대 접근할 수 없다.

팬텀리드 뿐만 아니라 어떠한 문제가 되는 현상이 발생하지 않지만, 동시성과 속도 측면에서 엄청난 손해를 본다.


특히 MySQL에서는 InnoDB 스토리지 엔진이 갭 락과 넥스트 키 락 덕분에
Repetable read 격리 수준에서도 이미 팬텀리드가 발생하지 않는다.

그렇기 때문에 굳이...



지금까지 알아본 것이 격리성이다.

🧐지속성(Durabilty)


지속성은 간단하다.

성공한 트랜잭션 즉, 커밋된 트랜잭션은 영원히 DB에 반영되어야 한다는 것이다.

이는 DB에 장애가 있어서 원래 상태로 복구하는 회복 기능이 있어야 하는 것을 의미하고,

이를 위해 checksum, 저널링, 롤백 등의 기능을 제공한다.


# checksum
중복 검사의 한 형태로, 오류 정정을 통해 송신된 자료의 무결성을 보호하는 방법(TCP, UDP의 그것과 동일)

#저널링
파일 시스템 또는 DB시스템에 변경 사항을 반영하기 전에 로깅하는 것, 트랜잭션 등 변경 사항에 대한 로그를 남기는 것.



🧐무결성이란 무엇인가?


위에 ACID를 설명하면서 정합성이라는 단어는 많이 나왔지만 무결성은 많이 등장하지 않았다.


무결성은 데이터의 정확성, 일관성, 유효성을 유지하는 것을 말한다.


무결성이 유지되어야 DB에 저장된 값과 그 값에 해당하는 현실 세계의 값이 일치하는지에 대한 신뢰가 생긴다.


무결성은 여러 종류가 있다.

  • 개체 무결성(PRIMARY KEY): 기본키로 선택된 필드는 빈 값을 허용하지 않는다.
  • 참조 무결성(FOREIGN KEY): 서로 참조 관계에 있는 두 테이블의 데이터는 항상 일관된 값을 유지해야한다.
  • 고유 무결성(UNIQUE): 특정 속성에 대해 고유한 값을 가지도록 조건이 주어진 경우 그 속성 값은 모두 고유한 값을 가진다.
  • NULL 무결성(NOT NULL): 특정 속성 값에 NULL이 올 수 없다는 조건이 주어진 경우 그 속성은 NULL이 될 수 없다.
  • 도메인 무결성(CHECK): 저장 가능한 데이터 값의 범위나 조건을 지정하여 설정한 값만을 허용한다.

ERD 설계를 해봤거나, DDL을 작성해봤다면 어떤 것이 있는지 잘 알고 있으리라 생각된다.




지금까지 DB의 트랜잭션과 무결성에 대해 알아봤다.

너무 많은 정보를 쏟아낸게 아닌가 싶은 생각이 들지만, 내 공부를 위한 것도 있고

이 블로그에서 최대한 많은 정보를 얻어갈 수 있으면 좋겠단 생각을 가지고 있다.

많은 도움이 되었기를.

반응형