DBA 실무/PostgreSQL

[PostgreSQL 시리즈 3편] DML과 트랜잭션 - Oracle UNDO vs PostgreSQL MVCC (시리즈 핵심)

isony 2026. 8. 17. 07:42
반응형

[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를 정리하면:

  1. Oracle은 UNDO, PostgreSQL은 MVCC — 근본적 차이
  2. MVCC는 새 버전 추가 — 덮어쓰지 않음
  3. ORA-01555가 없다 — 긴 쿼리에 유리
  4. 대신 dead tuple/bloat — VACUUM 관리 필요
  5. DDL도 롤백 가능 — 마이그레이션에 안전

이 개념을 이해하셨다면 PostgreSQL 운영의 가장 큰 산을 넘으신 것입니다. "UNDO 관리에서 VACUUM 관리로" 사고를 전환하는 것이 Oracle DBA의 핵심 과제입니다. 이 dead tuple 관리는 나중에 11편(VACUUM)과 12편(운영 문제)에서 다시 깊게 다룹니다.

MVCC를 이해하며 "아하" 했던 순간이나 궁금한 점이 있다면 댓글로 공유해주세요. 같은 전환을 겪는 Oracle DBA들에게 큰 도움이 됩니다.

 

 

반응형