자, READ COMMITTED 격리 수준이 뭘 하는지 같이 파헤쳐보자. 이름에서 알 수 있듯이, 트랜잭션 안에서 읽는 모든 데이터는 이미 다른 트랜잭션에서 "커밋"된 것들이야. 마치 우리가 공식적으로 기록된 소문만 믿는 것처럼 말이지.
진지하게 얘기하자면, 이 수준은 트랜잭션이 다른 트랜잭션에서 커밋되지 않은 변경사항을 볼 수 없게 보장해줘. 이건 "더티 리드"(Dirty Read)라고 불리는 문제를 해결해주지. 하지만, 네가 읽은 데이터가 다른 트랜잭션이 커밋을 해버리면 바뀔 수도 있다는 점은 기억해야 해. 이게 바로 "반복 불가능한 읽기"(Non-Repeatable Read)의 위험이야.
PostgreSQL에서는 READ COMMITTED 격리 수준이 기본값이야. 그냥 디폴트 모드라서, 따로 설정할 필요 없이 바로 쓸 수 있어.
READ COMMITTED 격리 수준 사용 예시
실제로 어떻게 동작하는지 예시로 알아보자. accounts라는 테이블이 있다고 해보자. 여기엔 사용자랑 그들의 잔고 정보가 들어있어:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
account_name TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (account_name, balance)
VALUES ('Alice', 1000.00), ('Bob', 500.00);
이제 이런 상황을 상상해보자. 세션 1과 세션 2, 두 명의 주인공이 accounts 테이블을 가지고 작업하고 있어. 상황은 이래:
세션 1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_name = 'Alice';
-- 아직 COMMIT이나 ROLLBACK 안 했어.
이 순간, Alice의 balance에서 100이 임시로 빠졌지만, 아직 확정된 건 아니야.
세션 2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE account_name = 'Alice';
결과: 세션 2는 Alice의 잔고를 1000.00으로 봐. 왜냐면 세션 1의 트랜잭션이 아직 끝나지 않았거든(COMMIT 안 됨). READ COMMITTED 덕분에 "더티 리드"를 막아주는 거지.
세션 1이 트랜잭션을 끝내:
COMMIT;
이제 Alice의 잔고가 업데이트돼서, DB에 900.00으로 저장됐어.
세션 2가 다시 쿼리해봐:
SELECT balance FROM accounts WHERE account_name = 'Alice';
결과: 이제 세션 2는 Alice의 업데이트된 잔고 900.00을 보게 돼. 이전 쿼리 결과(1000.00)랑 다르지? 이게 바로 "반복 불가능한 읽기" 문제야.
언제 READ COMMITTED를 써야 할까?
READ COMMITTED 격리 수준은 성능과 일관성 사이의 밸런스야. 근데 이런 상황에서 특히 잘 맞아:
간단한 CRUD 작업: 그냥 데이터 읽거나 업데이트만 할 때, 복잡한 연관관계가 없을 때.
레코드 업데이트: 예를 들어, 테이블에서 대량으로 데이터를 업데이트할 때, 바로바로 커밋된 변경사항을 보고 싶을 때.
트랜잭션 처리: 결제 시스템처럼, 유저가 오직 확정된 데이터만 볼 수 있어야 할 때.
근데 복잡한 분석 쿼리나 대용량 데이터를 다룬다면, REPEATABLE READ 같은 다른 격리 수준을 고려하는 게 좋을 수도 있어.
READ COMMITTED 격리 수준의 장점과 단점
READ COMMITTED 격리 수준은 일종의 황금 중간이야. 더티 데이터로부터 보호해주지: 다른 트랜잭션이 시작했지만 아직 끝나지 않은 변경사항은 볼 수 없어. 즉, 누군가 "날것"의 정보를 읽어서 바로 롤백되는 상황은 안 생겨.
이 모드는 더 엄격한 수준(REPEATABLE READ나 SERIALIZABLE)보다 빠르게 동작해. 복잡한 락이나 추가 체크가 필요 없으니까. 꽤 가볍고, 동시에 믿을 만해서 기본값으로 쓰이고, 대부분의 일상적인 작업에 딱 맞아.
더티 리드는 막아주지만, READ COMMITTED는 다음은 막아주지 못해:
- "반복 불가능한 읽기"(
Non-Repeatable Read): 다른 트랜잭션이 쿼리 사이에 데이터를 바꿔버릴 수 있어. - "팬텀 리드"(
Phantom Read): 다른 트랜잭션이 쿼리 결과에 영향을 주는 새로운 행을 추가할 수 있어.
READ COMMITTED 사용할 때 팁
항상 트랜잭션을 끝내자: COMMIT이나 ROLLBACK을 꼭 써줘. 안 그러면 락 문제 생길 수 있어.
격리 수준이 충분한지 확인하자: 트랜잭션 중에 데이터가 절대 바뀌면 안 된다면, REPEATABLE READ를 고려해봐.
인덱싱을 활용하자: PostgreSQL이 데이터를 더 빨리 찾고, 변경사항을 적용하는 데 도움이 돼.
예시: 주문 처리
예를 들어, orders라는 테이블이 있고, 여기에 주문 데이터가 저장돼 있다고 해보자:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
);
INSERT INTO orders (customer_name, status)
VALUES ('Alice', 'pending'), ('Bob', 'pending');
우리는 상태가 "pending"인 주문들의 상태를 업데이트하고 싶어:
BEGIN;
SELECT * FROM orders WHERE status = 'pending';
UPDATE orders SET status = 'completed' WHERE status = 'pending';
COMMIT;
이 과정에서 다른 트랜잭션이 새로운 "pending" 상태의 주문을 추가하고 COMMIT하면, 우리 트랜잭션은 그 행을 반영하지 못해. 왜냐면 읽기 시작한 이후에 추가된 거니까.
이게 바로 "팬텀 리드" 예시야. 이런 상황을 피하고 싶으면 SERIALIZABLE을 써야 해.
READ COMMITTED 격리 수준은 PostgreSQL을 포함한 대부분의 데이터베이스에서 기본 선택이야. 더티 리드를 막아주기 때문에, 대부분의 표준 작업에 좋은 옵션이지. 하지만 엄격한 일관성이 필요한 시나리오라면 더 강력한 격리 수준이 필요할 수도 있어. 격리 수준 선택은 네가 하고자 하는 작업과 성능 요구사항에 따라 달라져야 해.
GO TO FULL VERSION