
트랜잭션(Transaction)은 데이터베이스에서 하나의 논리적인 작업 단위를 의미합니다. 일반적으로 여러 개의 SQL 문장을 하나로 묶어 실행하며, 모두 성공하거나 하나라도 실패하면 전체 작업을 취소해야 하는 상황에서 사용됩니다.
예를 들어, 계좌 이체의 경우 출금과 입금이 함께 수행되어야 하므로, 둘 중 하나라도 실패하면 전체 작업을 롤백해야 합니다. 이처럼 데이터의 일관성과 무결성을 보장하기 위해 트랜잭션 개념이 필요합니다.
모든 작업이 하나의 단위로 수행되어야 하며, 중간에 일부만 수행되는 일은 없어야 합니다. 하나라도 실패하면 전체를 롤백(Rollback)합니다.
트랜잭션 수행 전과 수행 후의 데이터 상태가 항상 일관된 상태를 유지해야 합니다. 무결성 제약조건(Primary Key, Foreign Key 등)을 항상 만족해야 합니다.
여러 트랜잭션이 동시에 실행될 경우, 각 트랜잭션은 서로 간섭받지 않아야 합니다. 즉, 하나의 트랜잭션이 완료되기 전에는 그 중간 결과를 다른 트랜잭션에서 볼 수 없어야 합니다.
Isolation은 실제 DB 구현에서 완벽히 보장하기 어렵기 때문에 격리 수준(Isolation Level) 이라는 형태로 선택적으로 조정됩니다.
트랜잭션이 성공적으로 커밋되면, 시스템이 장애를 겪더라도 결과는 영구적으로 보존되어야 합니다. 이를 위해 DBMS는 로그(Write-Ahead Logging)나 체크포인트 등을 활용합니다.
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| Read Uncommitted | ❌ 발생 | ✅ 발생 | ✅ 발생 |
| Read Committed | ✅ 방지 | ❌ 발생 | ✅ 발생 |
| Repeatable Read | ✅ 방지 | ✅ 방지 | ❌ 발생 |
| Serializable | ✅ 방지 | ✅ 방지 | ✅ 방지 |
가장 낮은 격리 수준. 커밋되지 않은 데이터도 읽을 수 있어 Dirty Read가 발생합니다. 성능은 좋지만 데이터 정합성이 낮아 거의 사용되지 않습니다.
커밋된 데이터만 읽을 수 있어 Dirty Read는 방지되지만, 같은 데이터를 두 번 조회할 때 값이 달라지는 Non-Repeatable Read가 발생할 수 있습니다.
※ Oracle, SQL Server의 기본 격리 수준
동일한 데이터를 반복 조회해도 값이 변하지 않도록 보장합니다. 대부분의 변경 작업에는 잠금이 걸립니다.
단, Phantom Read(같은 조건의 조회인데 행 개수가 바뀜)는 발생할 수 있습니다.
※ MySQL InnoDB의 기본 격리 수준
가장 높은 격리 수준. 트랜잭션을 직렬화한 것처럼 처리하여 모든 이상 현상을 방지합니다. 하지만 성능이 크게 저하되어 대량 트랜잭션 환경에서는 비효율적입니다.

| Active(활동) | 트랜잭션이 실행 중에 있는 상태, 연산들이 정상적으로 실행 중인 상태 |
|---|---|
| Failed(실패) | 트랜잭션이 실행에 오류가 발생하여 중단된 상태 |
| Aborted(철회) | 트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태 |
| Partially Committed(부분 완료) | 트랜잭션이 마지막 연산까지 실행했지만, Commit 연산이 실행되기 직전의 상태 |
| Committed(완료) | 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태 |
트랜잭션의 고립성을 보장하기 위해 DBMS는 다양한 Lock 메커니즘을 사용
경쟁 상태(race)를 막기 위해 DB가 레코드/페이지/테이블 등에 걸어두는 잠금.
※ 격리 수준에 따라 Lock의 범위와 강도가 달라지며, 무분별한 Lock은 데드락(Deadlock) 의 원인이 됩니다.
충돌이 자주 발생한다고 보고 DB 락을 사용. SELECT ... FOR UPDATE 같은 명령으로 락을 걸고 처리.
예: 은행 계좌 이체
BEGIN;
SELECT balance FROM accounts WHERE id=123 FOR UPDATE; -- 해당 행에 X 락
UPDATE accounts SET balance = balance - 100 WHERE id=123;
COMMIT; -- 락 해제
충돌이 드물다고 가정. 업데이트 시점에 충돌 검사.
예: version 컬럼 방식
-- 초기 테이블: id, data, version
UPDATE items
SET data = 'new', version = version + 1
WHERE id = 10 AND version = 3; -- 업데이트된 행 수가 0이면 충돌 -> 재시도
-- 1. 세션 격리 수준 설정 (이 세션에서만 적용됨)
-- READ COMMITTED: 커밋된 데이터만 읽기, SELECT 시 오래 락 안 잡음
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 2. 트랜잭션 시작
START TRANSACTION;
-- 3. 특정 행에 배타락(X-Lock) 걸기
-- 'FOR UPDATE' → 해당 행을 다른 트랜잭션이 읽거나 수정 불가
-- 주로 읽고 바로 수정할 때 사용 (은행 계좌 이체 등)
SELECT balance
FROM accounts
WHERE id = 1
FOR UPDATE;
-- 4. 데이터 수정
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
-- 5. 특정 행에 공유락(S-Lock) 걸기
-- 'LOCK IN SHARE MODE' → 읽기는 허용, 수정은 차단
-- 다른 트랜잭션이 이 행을 UPDATE/DELETE하려면 대기
SELECT *
FROM accounts
WHERE id = 2
LOCK IN SHARE MODE;
-- 6. 트랜잭션 종료 (모든 락 해제)
COMMIT;
트랜잭션 도중 중간 지점을 설정하여, 전체 롤백이 아닌 부분 롤백을 수행할 수 있게 합니다.
SAVEPOINT sp1;
-- 중간 처리
ROLLBACK TO sp1;
MySQL과 같은 DB는 기본적으로 Auto Commit이 활성화되어 있어, 각 SQL 문이 실행되면 자동으로 Commit 됩니다.
트랜잭션 단위로 묶고 싶다면, 다음과 같이 설정해야 합니다:
SET autocommit = 0;
BEGIN;
-- 여러 쿼리
COMMIT;
또는 JDBC에서 connection.setAutoCommit(false) 설정을 통해 수동 제어합니다.
실패 시 DBMS는 자동 롤백하거나, 애플리케이션에서 수동으로 롤백해야 합니다.
| 항목 | RDB | NoSQL |
|---|---|---|
| 트랜잭션 지원 | 강력한 ACID | 약한 일관성 (Eventually Consistent) |
| 데이터 모델 | 정형, 정규화된 테이블 | 유연한 비정형 (문서, 키-값 등) |
| 확장성 | 수직 확장 중심(CPU, RAM, Disk 성능 업그레이드) | 수평 확장(노드 추가로 처리량 증가)에 유리 |
| 조인 | 자유롭게 가능 | 대부분 미지원 또는 비효율적 |
| 스키마 변경 | 민감 | 유연함 |
→ NoSQL은 일부 트랜잭션을 지원하기도 하나, 범위가 제한적이며 보통은 성능과 확장성 측면에서 일관성(Consistency)을 희생합니다.