DataScience

[PostgreSQL] 트랜잭션

Grace 2023. 5. 31. 12:30

트랜잭션

트랜잭션은 PostgreSQL을 비롯한 DBMS에서 작업 수행의 단위로 쓰이는 논리적인 개념입니다.

데이터를 처리하는 작업 중에 오류가 발생하여 원하는 결과값이 나오지 못할 상황이 되면 트랜잭션은 해당 작업 전체를 취소합니다 .트랜잭션의 개념을 활용하면 단계별로 분리되어 있던 작업이 하나의 작업으로 그룹화됩니다. 즉, 작업 전체가 완벽하게 잘 수행되거나 아니면 아예 수행되지 않거나로 결과가 나뉩니다. 따라서 트랜잭션은 작업 중간의 오류로 인해 발생할 수 있는 데이터의 불일치를 방지함으로써 데이터 작업의 신뢰도를 높이는 효과를 얻을 수 있습니다.

트랜잭션의 특징: ACID

트랜잭션은 네 가지 특징을 가지고 있습니다.

  • 원자성(Atomicity): 작업의 수행은 전체가 다 수행되거나, 아무것도 안되는 결과값이 1 또는 0으로만 이루어진다는 개념의 원자성은 작업을 부분별로 보지 않고, 하나의 단위로 보는 것을 말합니다. 즉 작업의 부분들이 ‘동시에’ 수행되거나 ‘동시에’ 수행되지 않거나의 특징을 지닙니다.
  • 일관성(Consistency): 일관성은 작업 과정에서 오류가 없이 완벽히 수행되었을 때 갑자기 다른 트랜잭션에 의해 해당 테이블의 값이 수정되지 않는 특징을 말합니다.
  • 고립성(Isolation): 한 트랜잭션이 수행될 때 다른 트랜잭션이 간섭한다면 오류가 일어날 것입니다. 그러므로 트랜잭션이 수행되는 동안에는 다른 외부 요소들의 간섭이 없어야 합니다. 이것을 고립성이라고 합니다. 고립성은 일관성과 연관된 개념으로 일관성을 어떻게 유지할 것인가라는 질문에 대한 답이라고 생각하면 이해하기 쉽습니다.
  • 지속성(Durability): 한 트랜잭션이 외부와 내부의 문제없이 잘 수행되었으면, 수행된 트랜잭션에 대한 데이터는 수정되거나 없어지지 않고 남아있습니다. 이것을 지속성이라고 합니다.

트랜잭션의 원리

트랜잭션의 수행과 종료

PostgreSQL에서 트랜잭션을 수행하려면 비긴과 커밋 명령어를 사용해야 합니다. 비긴 명령은 트랜잭션을 시작하겠다고 선언하는 역할을 합니다. 트랜잭션이 오류없이 잘 수행되었다면, 커밋 명령어가 실행됩니다. 커밋은 트랜잭션이 잘 수행되었다는 보고를 트랜잭션 관리자에게 하는 명령입니다. 커밋이 실행되면 트랜잭션이 종료되고 변경사항들이 PostgreSQL에 저장됩니다. 커밋이 되고 모든 변경 사항들이 데이터베이스에 저장이 되면, 저장된 데이터들은 어떠한 경우에도 변경되지 않습니다.

그런데 비긴 명령 이후 트랜잭션 수행 도중 오류가 발생하여 트랜잭션이 제대로 수행하지 못할 경우에는 롤백 명령어를 사용합니다. 롤백은 수행되고 있는 트랜잭션의 잘못된 지점에서 멈추고 다시 처음으로 돌아가는 명령어입니다.

롤백을 사용해서 트랜잭션의 처음으로 돌아가면 이전에 잘못 수행했던 과정은 초기화됩니다. 롤백을 통해 모든 오류를 수정했다면 커밋을 입력해 트랜잭션을 종료합니다.

트랜잭션이 수행되는 동안 다른 트랜잭션은 간섭할 수 없으므로, 새로운 트랜잭션이 시작되려면 이전 트랜잭션이 종료된 후여야 합니다. 더 이상 새로운 트랜잭션을 시작할 필요가 없다면 모든 작업을 종료합니다. 새로운 트랜잭션을 시작한다면 비긴 명령어를 사용합니다. 작업을 수행하고 수행도중 오류가 없었다면 커밋을 실행하여 해당 트랜잭션을 종료합니다. 그런데 수행도중 오류가 있었다면 롤백을 실행해 오류가 있기전으로 돌아갑니다. 이런 과정이 반복되면서 트랜잭션이 수행됩니다. PostgreSQL에서 실행되는 모든 SQL 명령어들은 트랜잭션 내에서 실행됩니다.

세이브 포인트

트랜잭션 작업이 길어지는 중간에 오류가 나서 롤백하기에 애매한 경우, 세이브포인트라는 개념을 이용할 수 있습니다. 세이브포인트는 말그대로 원하는 특정 지점을 기준으로 이전에 수행한 작업까지 문제가 없었다면 세이브하겠다는 의미의 명령입니다.

세이브 포인트를 사용할 때는 롤백이 아니라 롤백투 명령어를 사용합니다. 특정 지점을 세이브포인트로 지정해놓았다면, 세이브포인트 이후 트랜잭션 수행 과정에 오류가 발생하더라도 트랜잭션 전체를 취소할 필요없이 지정해놓은 세이브포인트로 이동할 수 있습니다. 세이브포인트 이전의 트랜잭션 내용은 그대로 유지됩니다.

원자성 보장 방법

트랜잭션에서 원자성을 보장하려면, 변경된 데이터와 이전에 저장한 데이터가 따로 존재해야 합니다. 그러면 현재 수행하고 있는 트랜잭션에서 오류가 발생했을 때 오류가 발생하지 않은 지점으로 되돌아가서 다시 작업을 수행할 수 있습니다. 현재 변경되고 있는 데이터는 테이블에 저장이 되고, 이전에 저장된 데이터는 임시저장소에 저장됩니다. 수행하는 트랜잭션에서 오류가 발생하면, 수행하던 내용을 날리고 임시저장소에 저장된 데이터로 다시 접근하여 트랜잭션을 수행합니다. 모든 수행을 다 없애기 싫다면 세이브포인트를 사용하여 특정 지점으로 돌아갑니다.

WAL 파일

데이터베이스에서 데이터를 정확하고 일관성있게 유지하는 것을 무결성이라고 합니다. PostgreSQL에서는 데이터를 일관성 있게 유지하기 위해서 WAL(Write-Ahead Logging) 파일을 사용합니다.

  • 로그(Log): DBMS에서 변경되는 모든 작업들을 표시하고 기록하는 개념입니다.로그가 저장되는 곳은 데이터가 손실될 걱정이 없는 영구적 저장소입니다.
  • WAL 파일: 데이터를 손실없이 정확하게 저장하기 위해서는 로그를 사용하여 다시 돌아갈 방법을 대비해놓는 것이 좋습니다. 완벽하게 데이터 손실을 방지하기 위해서 로그를 작성하지 않고는 다음 데이터 변경 단계로 넘어갈 수 없게 하는 방법을 사용할 수 있는데 이런 원리를 이용해서 데이터 변경을 하기 전에 변경 사항을 미리 기록해두는 파일이 WAL 파일입니다. 변경 사항을 미리 기록하기 때문에 WAL을 사용한다는 것은 데이터 무결성을 보장한다는 말과 같습니다.

트랜잭션 실행

트랜잭션 기본 명령어

  • BEGIN: 새로운 트랜잭션을 시작한다
  • COMMIT: 트랜잭션 수행이 끝난 후 변경된 결과를 저장한다는 것을 의미. PostgreSQL에서는 END 확장 명령어를 쓰기도 합니다.
  • ROLLBACK: 현재 수행되고 있는 트랜잭션을 중지하고 BEGIN 시점으로 데이터를 되돌리는 것을 의미. PostgreSQL에서는 ABORT 확장 명령어를 쓰기도 합니다.

자동 커밋

트랜잭션이 시작되고 끝날 때 비긴과 커밋 명령어가 있어야 합니다. 그런데 커밋 명령어를 쓰지 않아도 자동으로 커밋이 도는 경우도 있습니다. PostgreSQL에서는 DML 명령어인 INSERT, UPDATE, DELETE 같은 명령어는 따로 커밋을 쓰지 않아도 자동으로 커밋이 됩니다. 자동으로 커밋이 되는 것이 일상적인 상황에서는 편하긴 하지만 특정 상황에서는 문제가 생길 수도 있습니다.

postgres=# DELETE FROM develop_book WHERE book_id=1 or book_id=2 or book_id=3;
DELETE 3
progres=# COMMIT;
경고: 현재 트랜잭션 작업을 하지 않고 있습니다
COMMIT

테이블에 book_id 1, 2, 3인 데이터 밖에 없을 경우 데이터 전체를 삭제하고 커밋을 실행하면 DELETE 명령어를 입력했을 때 커밋이 자동으로 완료되었기 때문에 현재 트랜잭션 작업을 하고 있지 않다는 경고가 뜹니다. 일반적으로 수동커밋을 수행할때는 정확하고 유효한 데이터를 확인 후 작업을 수행하여 데이터의 무결성을 지킬 수 있습니다. 따라서 자동 커밋을 원치 않을 경우 다음의 명령어를 입력합니다.

\set AUTOCOMMIT off

자동 커밋이 켜져있는지 꺼져 있는지 확인하는 명령어는 다음과 같습니다.

\echo :AUTOCOMMIT

트랜잭션 고립화 수준

트랜잭션 일관성 문제

DBMS는 다수의 사용자가 사용해도 문제가 없게끔 일관성을 유지하는 것이 중요합니다. 한 명의 사용자가 트랜잭션을 수행하고 있는 도중에 다른 사용자가 다른 트랜잭션에서 해당 트랜잭션과 연관되어 있는 값을 변경한다면 데이터의 불일치가 일어나게 됩니다. 이런 경우 트랜잭션의 일관성을 어떻게 통제할 것인가에 대해서 고민할 필요가 있습니다. 이렇게 데이터를 읽을 때 생기는 문제를 방지하기 위해서 트랜잭션은 고립화 수준(Isolation level)이라는 개념을 사용합니다.

트랜잭션 병행 문제

한 트랜잭션에서 작업을 수행하는 도중 다른 트랜잭션이 간섭하면 문제가 발생합니다. 이와 같이 2개 이상의 트랜잭션이 병행적으로 처리가 되는 경우 다양한 문제들이 발생할 수 있습니다. 첫 번째 트랜잭션이 완벽하게 끝나지 않았는데 데이터가 다른 트랜잭션에서 수정될 때 생기는 문제입니다. 이런 문제는 뒤에 수행되는 트랜잭션이 먼저 수행된 트랜잭션을 간섭할 수 있을 때 발생합니다. 이처럼 첫번째 트랜잭션이 커밋되지 않은 변경을 잘못 읽는 결과가 나오는 문제를 표준화된 명칭으로 오손읽기(dirty read)라고 합니다. 그러나 대부분의 DBMS는 뒤의 트랜잭션이 먼저 수행된 트랜잭션의 데이터에 접근하는 것을 막기 때문에 이러한 문제는 거의 발생하지 않습니다.

두 번째 문제는 한 트랜잭션에서 데이터를 두 번 읽었을 때, 데이터를 두 번 읽는 사이에 다른 트랜잭션이 데이터를 변경하면 결과값이 다르게 나오는 경우에 발생합니다. 이처럼 두 트랜잭션이 서로 간섭하여 값이 다르게 읽히는 문제를 표준화된 명칭으로 비반복적 읽기라고 합니다.

세 번째 문제는 한 트랜잭션에서 데이터를 두 번 읽었을 때, 처음 읽었을 때는 없었던 데이터가 새로 생기는 경우에 발생합니다. 이렇게 원래 없었던 데이터가 새로 생긴 것을 확인했을때 발생하는 문제를 표준화된 명칭으로 가상 읽기라고 합니다.

트랜잭션 고립화 수준

PostgreSQL은 데이터 읽기 중 발생하는 일관성 문제를 해결하기 위해 고립화 수준 개념을 사용합니다.

고립화 수준명칭특징문제

0단계 실행되지 않는 읽기
READ UNCOMMITTED
트랜잭션에서 처리중인 데이터를 다른 트랜잭션이 읽는 것을 허용합니다.
  • 오손읽기
  • 비반복적 읽기
  • 가상 읽기
1단계 실행된 읽기
READ COMMITTED
완료된 트랜잭션이 데이터만 읽는 것을 허용합니다. 대부분의 DBMS가 채택하고 있는 고립화 레벨입니다.
  • 비반복적 읽기
  • 가상 읽기
2단계 반복적 읽기
REPEATABLE READ
먼저 실행된 트랜잭션이 종료될 때까지 뒤에 실행된 트랜잭션이 먼저 실행된 트랜잭션의 데이터를 수정하는 것을 허용하지 않습니다.
  • 가상읽기
3단계 직렬화
SERIALIZABLE
먼저 실행된 트랜잭션의 데이터를 뒤에 실행된 트랜잭션이 수정, 삭제, 생성하는 것을 허용하지 않습니다.


PostgreSQL은 표준 SQL 트랜잭션 고립화 수준 중 1단계~3단계를 지원합니다. 기본 고립화 수준은 1단계입니다.

고립화 수준 설정하기

BEGIN:
  SET TRANSACTION ISOLATION LEVEL [READ COMMITTED | SERIALIZABLE]
...
COMMIT; 

고립화 수준을 바꾸는 명령어는 SET TRANSACTION ISOLATION LEVEL 뒤에 READ COMMITTED 과 같이 원하는 고립화 수준 이름을 입력하면 됩니다.

트랜잭션 결과

트랜잭션 고립화 수준을 높이면 데이터의 불일치가 일어날 확률을 줄입니다. 그 이유는 고립화 수준을 높일수록 동시에 처리하기보다 순차적으로 안전한 처리를 하기 떄문입니다. 따라서 고립화 수준이 높아질수록 데이터가 충돌할 가능성이 낮아져 데이터의 일관성은 높아지고, 동시에 데이터가 조작되지 않기 때문에 데이터의 동시성은 낮아집니다. 동시성이 낮아지면 시간이 더 걸리게 되고, 이는 성능을 저하시킵니다. 반대로 고립화 수준이 낮으면 동시에 데이터를 처리하게 되고, 이는 성능을 향상시키지만 데이터 일관성에 문제가 생길 수 있습니다.