트랜잭션(Transaction)이란? ACID와 격리수준까지 쉽게 이해하기
트랜잭션(Transaction)이란? ACID와 격리수준까지 쉽게 이해하기
데이터베이스를 공부하다 보면 트랜잭션(Transaction)과 ACID는 거의 반드시 만나게 됩니다. 그런데 처음 보면 용어 자체가 조금 딱딱해서, “대충 중요한 개념인 것 같은데 정확히는 모르겠다”는 상태로 넘어가기 쉽습니다.
하지만 트랜잭션은 생각보다 어렵지 않습니다. 오히려 실무에서는 굉장히 직관적인 개념입니다.
이번 글에서는 트랜잭션이 왜 필요한지, COMMIT과 ROLLBACK은 무엇인지, ACID는 어떻게 이해하면 되는지, 그리고 실무에서 자주 함께 나오는 격리수준(Isolation Level) 까지 초보도 이해하기 쉽게 정리해보겠습니다.
예를 들어 은행 이체를 생각해보겠습니다.
- A 계좌에서 10만 원 출금
- B 계좌에 10만 원 입금
이 작업은 사실상 하나의 업무입니다. 그런데 만약 첫 번째 SQL은 성공하고, 두 번째 SQL은 실패하면 어떻게 될까요?
- A 계좌에서는 돈이 빠졌는데
- B 계좌에는 돈이 안 들어가는
심각한 데이터 불일치가 발생합니다.
그래서 데이터베이스는 이런 여러 작업을 하나로 묶어서 처리할 수 있어야 합니다. 그 묶음이 바로 트랜잭션입니다.
트랜잭션에는 대표적으로 두 가지 결과가 있습니다.
COMMIT
문제가 없을 때, 지금까지의 변경 내용을 최종 반영합니다.
ROLLBACK
문제가 생겼을 때, 지금까지의 변경 내용을 전부 취소합니다.
예를 들어 아래처럼 생각하면 쉽습니다.
BEGIN;
UPDATE account SET balance = balance - 100000 WHERE id = 'A';
UPDATE account SET balance = balance + 100000 WHERE id = 'B';
COMMIT;
중간에 오류가 나면 이렇게 처리할 수 있습니다.
BEGIN;
UPDATE account SET balance = balance - 100000 WHERE id = 'A';
UPDATE account SET balance = balance + 100000 WHERE id = 'B';
ROLLBACK;
ACID는 트랜잭션의 핵심 성질 4가지를 말합니다.
- Atomicity : 원자성
- Consistency : 일관성
- Isolation : 격리성
- Durability : 지속성
이 4개를 하나씩 보면 훨씬 쉽습니다.
원자성은 트랜잭션 안에 있는 작업들이 쪼개져서 일부만 반영되면 안 된다는 의미입니다.
예를 들어 계좌이체에서 출금만 되고 입금이 안 되면 안 됩니다. 둘 다 성공하거나, 둘 다 취소되어야 합니다.
일관성은 트랜잭션이 끝난 뒤에도 데이터가 정상적인 규칙을 유지해야 한다는 뜻입니다.
예를 들어,
- 잔액은 음수가 되면 안 된다
- 주문 상태는 정의된 값만 들어가야 한다
- FK 관계가 깨지면 안 된다
같은 규칙이 계속 지켜져야 합니다.
즉, 트랜잭션이 끝난 뒤 데이터가 이상한 상태가 되어서는 안 됩니다.
격리성은 여러 트랜잭션이 동시에 실행될 때, 서로의 작업이 이상하게 섞여 보이지 않도록 하는 성질입니다.
예를 들어,
- 아직 COMMIT도 안 된 데이터를 다른 트랜잭션이 읽어버리면 문제가 생길 수 있습니다.
- 같은 데이터를 동시에 수정하면서 꼬일 수도 있습니다.
그래서 DBMS는 격리 수준(Isolation Level)을 통해 이런 충돌을 조절합니다.
초보 단계에서는 우선 이렇게만 이해하면 충분합니다.
여러 사용자가 동시에 작업하더라도, 트랜잭션은 서로 너무 함부로 간섭하면 안 된다.
지속성은 한 번 COMMIT된 결과는 장애가 발생해도 사라지지 않아야 한다는 뜻입니다.
예를 들어 결제가 성공했다고 사용자에게 보여줬는데, 서버가 재시작된 뒤 결제 데이터가 사라지면 큰 문제가 됩니다. 그래서 DBMS는 로그와 디스크 반영 등을 통해 COMMIT된 데이터가 유지되도록 보장합니다.
격리성 이야기를 할 때 꼭 같이 나오는 것이 바로 격리수준(Isolation Level)입니다.
트랜잭션은 서로 간섭하지 않아야 하지만, 현실에서는 성능도 중요합니다. 그래서 DBMS는 무조건 완벽하게 막는 방식 대신, 어느 정도까지 읽기 충돌을 허용할지 단계별로 선택할 수 있게 해둡니다.
쉽게 말하면,
- 격리를 강하게 하면 데이터는 더 안전하게 보이지만 성능과 동시성이 떨어질 수 있고
- 격리를 약하게 하면 성능은 좋아질 수 있지만 이상한 읽기 현상이 생길 수 있습니다.
1) READ UNCOMMITTED
가장 낮은 격리수준입니다. 아직 COMMIT되지 않은 데이터도 읽을 수 있는 상태라서, 이른바 Dirty Read가 발생할 수 있습니다.
예를 들어 다른 트랜잭션이 수정만 해두고 나중에 ROLLBACK했는데, 그 중간값을 내가 읽어버릴 수 있습니다.
2) READ COMMITTED
다른 트랜잭션이 COMMIT한 데이터만 읽을 수 있는 수준입니다. Dirty Read는 막을 수 있지만, 같은 SELECT를 두 번 했을 때 중간에 다른 트랜잭션이 COMMIT하면 결과가 달라질 수 있습니다. 이런 현상을 Non-Repeatable Read라고 합니다.
3) REPEATABLE READ
한 트랜잭션 안에서 같은 행(Row)을 여러 번 읽어도 처음 읽은 결과가 반복해서 유지되도록 보장하는 수준입니다. 그래서 Non-Repeatable Read를 막는 데 유리합니다.
다만 범위 조회에서는 새 행이 끼어드는 Phantom Read 같은 이슈를 완전히 막지 못하는 경우가 있습니다. (DBMS 구현마다 차이가 있습니다.)
4) SERIALIZABLE
가장 높은 격리수준입니다. 여러 트랜잭션이 동시에 실행되더라도 거의 순차 실행처럼 보이게 만듭니다.
데이터 정합성 측면에서는 가장 안전하지만, 잠금이 많아지고 동시 처리 성능이 떨어질 수 있어서 항상 무조건 좋은 선택은 아닙니다.
| 격리수준 | 특징 | 이해 포인트 |
|---|---|---|
| READ UNCOMMITTED | COMMIT 전 데이터도 읽을 수 있음 | Dirty Read 위험 |
| READ COMMITTED | COMMIT된 데이터만 읽음 | Dirty Read 방지 |
| REPEATABLE READ | 같은 행 조회 결과를 유지 | Non-Repeatable Read 완화 |
| SERIALIZABLE | 거의 순차 실행처럼 처리 | 가장 안전하지만 성능 부담 큼 |
| 요소 | 의미 | 쉽게 이해하면 |
|---|---|---|
| A | Atomicity | 전부 성공하거나 전부 취소 |
| C | Consistency | 규칙이 깨지지 않는 상태 유지 |
| I | Isolation | 동시 작업끼리 함부로 간섭하지 않음 |
| D | Durability | COMMIT된 결과는 유지됨 |
1) SQL 한 줄 = 항상 트랜잭션?
단일 SQL도 하나의 트랜잭션처럼 처리될 수 있지만, 보통 우리가 중요하게 보는 것은 여러 작업을 묶는 비즈니스 트랜잭션입니다.
2) COMMIT만 하면 무조건 끝?
아닙니다. 중간 설계가 잘못되면 애초에 비정상 데이터가 들어갈 수도 있습니다.
3) ACID는 외우기만 하면 된다?
아닙니다. 계좌이체, 주문결제, 재고차감 같은 실제 사례에 연결해서 이해해야 오래 갑니다.
실무에서는 보통 이런 작업에서 트랜잭션이 중요합니다.
- 결제 처리
- 계좌 이체
- 주문 생성 + 재고 차감
- 게시글 작성 + 첨부파일 메타 저장
- 회원 가입 + 권한 부여
즉, 여러 SQL이 함께 성공해야 의미가 있는 작업은 거의 모두 트랜잭션 대상입니다.
초보 단계에서는 우선 이렇게 기억하면 충분합니다.
트랜잭션은 함께 처리돼야 하는 작업을 하나로 묶는 장치이고, ACID는 그 작업이 안전하고 믿을 수 있게 동작하도록 보장하는 원칙입니다.