[PostgreSQL 시리즈 3편] DML과 트랜잭션 - Oracle UNDO vs PostgreSQL MVCC (시리즈 핵심)
테스트 환경: PostgreSQL 17, Rocky Linux 9 / Oracle 19c
이 시리즈에서 가장 중요한 한 편을 꼽으라면 이 글입니다.
Oracle DBA가 PostgreSQL을 다룰 때 SQL 문법은 금방 익숙해집니다. 하지만 트랜잭션이 내부에서 어떻게 동작하는지는 근본적으로 다릅니다. 이 차이를 모르면 "왜 테이블이 안 줄어들지?", "왜 디스크가 계속 늘지?" 같은 문제에서 헤맵니다.
핵심은 하나입니다. Oracle은 UNDO, PostgreSQL은 MVCC. 이 차이가 운영의 많은 것을 결정합니다.
이번 편에서 다룰 내용입니다.
- DML 문법 비교 (INSERT/UPDATE/DELETE)
- ★ Oracle UNDO vs PostgreSQL MVCC (이 시리즈의 심장)
- 왜 PostgreSQL엔 ORA-01555가 없는가
- 대신 등장하는 dead tuple 문제
- 트랜잭션 격리 수준 비교
개념이 중심이지만, 실습으로 직접 확인하며 진행합니다.
DML 문법 비교 (빠르게)
먼저 기본 DML은 대부분 동일합니다. 차이나는 부분만 짚습니다.
INSERT
-- 단일 행 (동일)
INSERT INTO employees (emp_name) VALUES ('홍길동');
-- 다중 행 (PostgreSQL이 더 깔끔)
INSERT INTO employees (emp_name) VALUES
('홍길동'), ('김철수'), ('이영희');
-- Oracle의 INSERT ALL 대신 위 방식 사용
UPDATE / DELETE
-- UPDATE (동일)
UPDATE employees SET salary = salary * 1.1 WHERE dept_id = 10;
-- DELETE (동일)
DELETE FROM employees WHERE emp_id = 5;
RETURNING - PostgreSQL의 편리 기능
-- INSERT/UPDATE/DELETE 결과를 바로 반환!
INSERT INTO employees (emp_name) VALUES ('박신입')
RETURNING emp_id, emp_name;
UPDATE employees SET salary = 5000 WHERE emp_id = 1
RETURNING emp_id, salary;
DELETE FROM employees WHERE emp_id = 10
RETURNING *;
RETURNING은 Oracle의 RETURNING INTO와 유사하지만 더 간편합니다. 방금 생성된 ID를 바로 받을 때 특히 유용합니다.
UPSERT - MERGE의 대안
-- PostgreSQL의 ON CONFLICT (Oracle MERGE 대응)
INSERT INTO employees (emp_id, emp_name)
VALUES (1, '홍길동')
ON CONFLICT (emp_id)
DO UPDATE SET emp_name = EXCLUDED.emp_name;
Oracle의 MERGE보다 간결합니다. PostgreSQL 15부터는 MERGE 문도 지원하지만, 간단한 경우 ON CONFLICT가 더 편합니다.
★ 핵심: Oracle UNDO vs PostgreSQL MVCC
이제 이 글의 심장입니다. 두 DB가 동시성 제어를 어떻게 다르게 처리하는지 이해하면, PostgreSQL 운영의 절반을 이해한 것입니다.
공통 목표: 읽기 일관성
둘 다 같은 목표를 갖습니다.
"A가 데이터를 수정 중일 때,
B는 수정 전 데이터를 일관되게 읽어야 한다"
(읽기가 쓰기를 막지 않고, 쓰기가 읽기를 막지 않는다)
방법이 완전히 다릅니다.
Oracle의 방법: UNDO
1. UPDATE 실행
2. 변경 전 데이터를 UNDO 세그먼트에 저장
3. 실제 데이터 블록을 새 값으로 덮어씀
4. 다른 세션이 옛 값을 읽으려 하면?
→ UNDO에서 옛 값을 재구성해서 보여줌
데이터 블록: [새 값] ← 제자리에서 덮어씀
UNDO: [옛 값] ← 별도 저장, 읽기 일관성용
특징: 데이터는 제자리에서 갱신되고, 옛 값은 UNDO에 따로 보관됩니다.
PostgreSQL의 방법: MVCC
1. UPDATE 실행
2. 기존 행은 그대로 두고 (dead 표시만)
3. 새 값으로 완전히 새로운 행 버전을 생성
4. 다른 세션이 옛 값을 읽으려 하면?
→ 옛 행 버전이 아직 그 자리에 있음, 그대로 읽음
UPDATE 전:
[행 v1: 홍길동, 급여 3000] ← 유효
UPDATE 후 (급여 3500으로):
[행 v1: 홍길동, 급여 3000] ← dead tuple (옛 트랜잭션용)
[행 v2: 홍길동, 급여 3500] ← 유효 (새 트랜잭션용)
특징: 데이터를 덮어쓰지 않고 새 버전을 추가합니다. 옛 버전은 그 자리에 남습니다(dead tuple).
핵심 차이 정리
항목 Oracle (UNDO) PostgreSQL (MVCC)
| 갱신 방식 | 제자리 덮어쓰기 | 새 버전 추가 |
| 옛 값 저장 | UNDO 세그먼트 | 테이블 내 dead tuple |
| 읽기 일관성 | UNDO에서 재구성 | 옛 버전 직접 읽기 |
| 옛 값 정리 | UNDO 자동 관리 | VACUUM 필요 |
| 대표 문제 | ORA-01555 | 테이블 bloat |
왜 PostgreSQL엔 ORA-01555가 없는가
Oracle DBA라면 ORA-01555: snapshot too old에 시달린 기억이 있을 것입니다.
ORA-01555의 원인
1. 긴 조회 쿼리가 실행 중
2. 그 사이 다른 세션들이 활발히 UPDATE
3. UNDO 세그먼트가 재사용되어 옛 값이 사라짐
4. 긴 쿼리가 옛 값을 읽으려는데 UNDO에 없음!
5. → ORA-01555 발생
UNDO 공간이 한정되어 있어서 재사용되면서 생기는 문제입니다.
PostgreSQL엔 왜 없나
PostgreSQL은 옛 행 버전이 테이블 안에 그대로 남아있음
→ 긴 쿼리가 필요로 하는 한 그 버전은 삭제 안 됨
→ 읽을 값이 사라질 일이 없음
→ ORA-01555 같은 에러 없음!
"긴 쿼리에 유리하다" 는 것이 MVCC의 장점입니다. 대용량 배치나 리포팅 쿼리가 많은 환경에서 이 차이는 큽니다.
★ 대신 등장하는 문제: dead tuple과 bloat
공짜 점심은 없습니다. MVCC는 ORA-01555를 없앤 대신 새로운 관리 포인트를 만듭니다.
dead tuple이 쌓인다
UPDATE/DELETE가 많으면:
→ dead tuple(죽은 행 버전)이 계속 쌓임
→ 테이블이 실제 데이터보다 훨씬 커짐 (bloat)
→ 조회 성능 저하 (죽은 행도 스캔)
→ 디스크 낭비
실습으로 확인
-- 테스트 테이블
CREATE TABLE test_bloat (id INTEGER, val TEXT);
INSERT INTO test_bloat SELECT g, 'data' FROM generate_series(1, 100000) g;
-- 대량 UPDATE 반복 (dead tuple 생성)
UPDATE test_bloat SET val = 'updated';
UPDATE test_bloat SET val = 'updated2';
UPDATE test_bloat SET val = 'updated3';
-- dead tuple 확인
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
WHERE relname = 'test_bloat';
n_dead_tup(죽은 튜플 수)이 쌓인 것을 볼 수 있습니다. 10만 건 테이블을 3번 UPDATE하면 dead tuple이 30만 개까지 생길 수 있습니다.
해결: VACUUM
-- 수동 VACUUM (dead tuple 공간 회수)
VACUUM test_bloat;
-- 통계까지 갱신
VACUUM ANALYZE test_bloat;
-- 완전 회수 (테이블 잠금, 주의!)
VACUUM FULL test_bloat;
중요: 평소에는 autovacuum이 자동으로 이 일을 합니다. 하지만 autovacuum 설정이 부적절하면 dead tuple이 쌓여 문제가 됩니다. 이건 PostgreSQL 운영의 핵심이라 11편에서 통째로 다룹니다.
Oracle DBA의 사고 전환:
"UNDO 관리" → "VACUUM 관리"로 관점 이동 필요
트랜잭션 격리 수준 비교
트랜잭션 격리 수준도 차이가 있습니다.
기본 격리 수준
Oracle 기본: READ COMMITTED
PostgreSQL 기본: READ COMMITTED
→ 기본값은 같음
지원 격리 수준
격리 수준 Oracle PostgreSQL
| READ UNCOMMITTED | (READ COMMITTED로 처리) | (READ COMMITTED로 처리) |
| READ COMMITTED | ✅ 기본 | ✅ 기본 |
| REPEATABLE READ | (SERIALIZABLE로) | ✅ 진짜 지원 |
| SERIALIZABLE | ✅ | ✅ (SSI 방식) |
핵심 차이:
- Oracle은 REPEATABLE READ를 명시적으로 지원하지 않고 SERIALIZABLE로 처리합니다.
- PostgreSQL은 REPEATABLE READ를 진짜로 지원하며, SERIALIZABLE은 SSI(Serializable Snapshot Isolation)라는 정교한 방식을 씁니다.
트랜잭션 문법 비교
-- Oracle: 자동으로 트랜잭션 시작, COMMIT 필요
-- PostgreSQL: 동일하게 COMMIT/ROLLBACK
-- 명시적 트랜잭션
BEGIN; -- 트랜잭션 시작
UPDATE employees SET salary = 5000 WHERE emp_id = 1;
-- 문제 없으면
COMMIT;
-- 문제 있으면
-- ROLLBACK;
-- 격리 수준 지정
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ...
COMMIT;
주의:
Oracle: DDL은 자동 커밋 (암묵적)
PostgreSQL: DDL도 트랜잭션 안에서 롤백 가능! (강점)
PostgreSQL은 CREATE TABLE, ALTER TABLE 같은 DDL도 트랜잭션 안에서 롤백할 수 있습니다. Oracle DBA에게는 놀라운 부분이며, 마이그레이션 스크립트를 안전하게 실행할 수 있는 장점입니다.
BEGIN;
CREATE TABLE test_ddl (id INT);
ALTER TABLE test_ddl ADD COLUMN name TEXT;
-- 문제 발견!
ROLLBACK; -- 테이블 생성이 통째로 취소됨 (Oracle은 불가)
★ AI 활용 - 동시성 문제 분석
MVCC 관련 문제를 만났을 때 Claude를 활용하는 법입니다.
프롬프트 예시:
"PostgreSQL에서 이 테이블의 dead tuple이 계속 쌓입니다.
pg_stat_user_tables 결과는 다음과 같습니다:
[결과 붙여넣기]
1. 현재 상태 진단
2. autovacuum이 제대로 동작하는지 확인 방법
3. 개선 방안
을 Oracle DBA가 이해하기 쉽게 설명해줘"
MVCC는 개념이 복잡해서 AI에게 "Oracle에 비유해서 설명해줘"라고 하면 이해가 빠릅니다.
다음 글 예고
다음 글은 [PostgreSQL 시리즈 4편] psql 완벽 활용 + AI로 학습 가속 입니다. Phase 1(입문)의 마지막으로, psql의 강력한 메타 명령어들과 Claude를 활용한 Oracle→PostgreSQL 학습법을 다룹니다.
마무리
이 시리즈의 핵심인 MVCC를 정리하면:
- Oracle은 UNDO, PostgreSQL은 MVCC — 근본적 차이
- MVCC는 새 버전 추가 — 덮어쓰지 않음
- ORA-01555가 없다 — 긴 쿼리에 유리
- 대신 dead tuple/bloat — VACUUM 관리 필요
- DDL도 롤백 가능 — 마이그레이션에 안전
이 개념을 이해하셨다면 PostgreSQL 운영의 가장 큰 산을 넘으신 것입니다. "UNDO 관리에서 VACUUM 관리로" 사고를 전환하는 것이 Oracle DBA의 핵심 과제입니다. 이 dead tuple 관리는 나중에 11편(VACUUM)과 12편(운영 문제)에서 다시 깊게 다룹니다.
MVCC를 이해하며 "아하" 했던 순간이나 궁금한 점이 있다면 댓글로 공유해주세요. 같은 전환을 겪는 Oracle DBA들에게 큰 도움이 됩니다.
'DBA 실무 > PostgreSQL' 카테고리의 다른 글
| [PostgreSQL 시리즈 2편] 자료형과 DDL 완벽 비교 - Oracle 타입을 PostgreSQL로 옮기기 (0) | 2026.08.14 |
|---|---|
| PostgreSQL 시리즈 1편] Oracle DBA를 위한 PostgreSQL 첫걸음 - 아키텍처부터 첫 접속까지 (1) | 2026.08.12 |
| PostgreSQL, 설치할 때 정한 걸로 3년을 삽니다 — 설치부터 초기 설정까지 (0) | 2026.08.10 |