트랜잭션 격리 수준

과녁스·2022년 3월 9일
0

DB

목록 보기
5/5
post-thumbnail

개요


MySQL 기준으로 트랜잭션 격리 수준에 대한 내용을 학습하고 정리해보았습니다.

격리 수준이란❓


여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경 또는 조회하는 데이터를 볼 수 있도록 허용할지 결정하는 것을 의미합니다.

격리 수준은 다음과 같이 크게 4가지로 나눌 수 있습니다.

  • READ UNCOMMITED
  • READ COMMITED
  • REPEATABLE READ
  • SERIALIZABLE

4가지의 격리 수준에서 아래로 갈수록 트랜잭션 간의 데이터 격리 정도가 높아지고, 동시성도 떨어집니다.

  • READ UNCOMMITTED는 일반적인 DB에서 잘 사용되지 않습니다
  • SERIALIZABLE은 동시성이 중요한 DB에서는 거의 사용되지 않습니다.
  • SERIALIZABLE은 격리 수준에서 크게 성능 저하가 발생할 수 있습니다.

데이터 부정합


DB의 격리 수준에서 데이터 부정합은 항상 언급되는 문제입니다. 부정합 문제는 아래와 같이 3가지가 있고 격리 수준의 따라 발생할 수도, 안할 수도 있습니다.

DIRTY READ

트랜잭션에서 작업이 완료되지 않았지만, 다른 트랜잭션에서 작업 내용을 볼 수 있는 것을 DIRTY READ라고 합니다. 변경된 데이터가 롤백이 될지 또는 커밋이 될지 알 수 없는 상황에서 작업 내용을 조회할 수 있기 때문에 데이터 정합성에 큰 문제를 유발할 수 있습니다.

  • READ UNCOMMITTED : 발생
  • READ COMMITTED : 발생하지 않음
  • REPEATABLE READ : 발생하지 않음
  • SERIALIZABLE : 발생하지 않음

NON-REPEATABLE READ

하나의 트랜잭션에서 같은 SELECT문으로 조회할 때, 항상 같은 결과를 가져와야 합니다. 이를 REPEATABLE READ 라고 부르는데 SELECT 할 때마다 다른 결과를 받는다면 NON-REPEATABLE READ 상태라고 부릅니다.

  • READ UNCOMMITTED : 발생
  • READ COMMITTED : 발생
  • REPEATABLE READ : 발생하지 않음
  • SERIALIZABLE : 발생하지 않음

PHANTOM READ

하나의 트랜잭션에서 같은 SELECT문으로 조회 시 이전 SELECT문에서는 존재하지 않던 값이 다음 SELECT에서 조회되는 것을 의미합니다.

  • READ UNCOMMITTED : 발생
  • READ COMMITTED : 발생
  • REPEATABLE READ : 발생(Inno db에서는 발생하지 않음)
  • SERIALIZABLE : 발생하지 않음

일반적은 온라인 서비스 DB의 경우 READ COMMITTED와 REPEATABLE READ 둘 중에서 하나를 사용합니다.

격리 수준(Isolation Level)


READ UNCOMMITTED

트랜잭션에서 변경하는 내용이 commit과 rollback 여부에 관계 없이 다른 트랜잭션에게 노출됩니다.

발생 가능한 문제점 : DIRTY READ, NON-REPEATABLE READ, PHANTOM READ

위 그림처럼 하나의 트랜잭션에서 처리한 작업이 완료되지 않았지만, 다른 트랜잭션에서 볼 수 있게 되는 현상을 Dirty Read라 하고, 허용되는 격리 수준이 READ UNCOMMITTED이다.

Dirty READ는 데이터가 보여지다 안보여지다 하기 때문에 사용자를 혼란스럽게 할 수 있다. 또한 READ UNCOMITTED는 RDBMS 표준에서는 트랜잭션 격리 수준으로 인정하지 않을 정도로 정합성이 문제가 많다.

READ COMMITTED

오라클 DBMS에서 기본적으로 사용하는 격리 수준이며, 온라인 서비스에서 가장 많이 선택되는 격리 수준입니다.

트랜잭션에서 변경한 레코드는 commit이 완료된 데이터만 조회할 수 있게 만듭니다. commit 전에 조회를 시도한다면 UNDO 영역에 백업된 레코드를 조회할 수 있습니다.

발생 가능한 문제점 : NON-REPEATABLE READ, PHANTOM READ

한 트랜잭션에서 데이터를 변경하였을 때 새로운 값은 테이블에 기록이되고, 이전 값은 UNDO 영역으로 백업된다.
변경하는 트랜잭션에서 커밋을 수행하기 전에 다른 트랜잭션에서 조회를 할때 이 결과는 UNDO 영역에서 가지고 온 데이터이다.

READ COMITTED 격리 수준에서는 어떤 트랜잭션에서 변경한 내용이 커밋되기 전까지는 다른 트랜잭션에서 그러한 변경 내역을 조회할 수 없기 때문이다. 최정적으로 사용자가 변경된 내용을 커밋하면 그때부터는 다른 트랜잭션에서도 새롭게 변경된 값을 참조할 수 있다.

UNDO 레코드는 InnoDB의 시스템 테이블 스페이스의 UNDO 영역에 기록이 되는데, UNDO 레코드는 트랜잭션의 격리 수준을 보장하기 위한 용도일 뿐만 아니라, 트랜잭션의 ROLLBACK에 대한 복구에도 사용된다.

READ COMMITTED 격리 수준에서도 NON-REPEATABLE READ 부정합 문제가 있다.

BEGIN 명령으로 트랜잭션을 시작하고 사용자를 검색하였지만, 일치하는 결과는 없었따. 하지만 다른 사용자가 이름을 변경하고 커밋을 실행한 후 기존 사용자는 똑같은 SELECT문으로 조회하면 이번에는 결과가 1건이 조회된다. BEGIN으로 조회한 사용자는 같은 트랜잭션 내에서 똑같은 쿼리를 실행했을 때는 같은 결과를 가져와야하는 "REPEATABLE READ" 정합성에 어긋난다.

REPEATABLE READ

REPEATABLE READ는 InnoDB에서 사용하는 기본 격리 수준입니다.
REPEATABLE READ란 하나의 트랜잭션 내부에서 SELECT문은 항상 같은 결과를 보여주는 것입니다.

InnoDB 스토리지 엔진은 트랜잭션이 ROLLBACK 될 가능성에 대비해 변경되기 전 레코드를 Undo 영여겡 백업해두고 실제 레코드 값을 변경합니다. 이러한 변경 방식을 MVCC라고 합니다.

REPEATABLE READ는 MVCC를 위해 UNDO 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있도록 보장합니다. REPEATABLE READ와 READ COMMITTED의 차이는 UNDO 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 이전 버전까지 찾아 들어가야 하는지에 있습니다.

모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적으로 증가하는 값)를 가지며, UNDO 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어 있습니다. 그리고 UNDO 영역의 백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제합니다.

REPEATABLE READ 격리 수준에서는 MVCC를 보장하기 위해 실행 중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 UNDO 영역의 데이터는 삭제할 수 없습니다. 그렇다면 가장 오래된 트랜잭션 번호 이전의 트랜잭션에 의해 변경된 모든 UNDO 데이터가 필요한 것은 아닙니다. 정확하게는 특정 트랜잭션 번호의 구각 내에서 백업된 UNDO 데이터가 보존되어야 하는 것입니다.

하나의 트랜잭션에서 데이터를 변경하고 커밋을 수행했을 때, 다른 사용자가 앞선 트랜잭션이 변경을 실행하기 전과 변경을 실행한 후에 가각 SELECT를 했지만, 커밋을 수행 했음에도 조회되는 데이터는 변하지 않았습니다. 다른 트랜잭션은 10번이라는 번호를 부여받았는데, 이때부터 트랜잭션 안에서 실행되는 모든 SELECT 쿼리는 트랜잭션 번호가 10(자신의 트랜잭션 번호)보다 작은 트랜잭션 번호에서 변경한 것만 보게 됩니다.

위 그림에서는 UNDO 영역에 백업된 데이터가 하나만 있는 것으로 표현되었지만, 사실 하나의 레코드에 대해 백업이 하나 이상 얼마든지 존재할 수 있습니다. 만약 BEGIN으로 트랜잭션을 시작하고 장시간 동안 트랜잭션을 종료하지 않으면 UNDO 영역이 백업된 데이터로 무한정 커질 수 있습니다. UNDO에 백업된 레코드가 많아지면 MySQL 서버의 성능이 떨어질 수 있습니다.

하나의 트랜잭션에서 BEGIN으로 시작한 뒤, SELECT를 수행하고 있습니다. 위 그림의 REPETABLE READ에서 확인한 것 처럼 SELECT 쿼리 결과는 같아야 하지만, 아래 그림에서는 쿼리 결과가 서로 다릅니다.

다른 트랜잭션에서 수행하는 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상을 PHANTOM READ라고 합니다.

SELECT ... FOR UPDATE 쿼리는 SELECT 하는 레코드에 쓰기 잠금을 걸어야하는데, UNDO 레코드에는 잠금을 걸 수 없습니다. 그래서 SELECT ...FOR UPDATE나 SELECT ...LOCK IN SHARE MODE로 조회되는 레코드는 UNDO 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드 값을 가져오게 됩니다.

SERIALIZABLE

격리 수준 중 가장 높은 격리 수준입니다. 레코드를 조회할 때 Shared Lock을 획득해야만 조회할 수 있고, 데이터를 변경할 때에는 Exclusive Lock을 획득해야만 변경할 수 있습니다. 하나의 트랜잭션에서 사용하는 데이터는 다른 트랜잭션에서 접근할 수 없게 만듭니다.

일반적인 DBMS에서 "PHANTOM READ" 문제가 해당 격리 수준에서 발생하지 않습니다. 하지만 InnoDB 스토리지 엔진에서는 REPEATABLE READ 격리 수준에서도 나타나지 않기 때문에 SERIALIZABLE을 잘 사용하지 않습니다. 데이터 정합성을 지키는 면에서는 가장 우수하지만, 동시 처리 성능이 떨어집니다.

출처🙏


profile
ㅎㅅㅎ

0개의 댓글