---
id: db-transaction-patterns-deep
title: DB Transaction Deep — isolation / SAVEPOINT / advisory lock
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [database, transaction, vibe-coding]
tech_stack: { language: "SQL", applicable_to: ["Database"] }
applied_in: []
aliases: [transaction, isolation, savepoint, advisory lock, nested transaction, explicit lock]
---

# DB Transaction Deep

> ACID 의 정밀 control. **Isolation, SAVEPOINT, advisory lock, FOR UPDATE**.

## 📖 핵심 개념
- ACID: Atomic, Consistent, Isolated, Durable.
- Isolation level (4 가지).
- SAVEPOINT = nested transaction.
- Advisory lock = app-level.

## 💻 코드 패턴

### Basic transaction
```sql
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 또는 ROLLBACK;
```

### Isolation levels
```
READ UNCOMMITTED: dirty read 가능 (Postgres 가 안 implement).
READ COMMITTED: dirty read 안 (Postgres default).
REPEATABLE READ: phantom 안 (range query OK).
SERIALIZABLE: 가장 strict (slow).
```

```sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- ...
COMMIT;
```

### SAVEPOINT (nested)
```sql
BEGIN;
  INSERT INTO orders (...) VALUES (...);
  
  SAVEPOINT after_order;
  
  INSERT INTO items (...) VALUES (...);
  -- Error
  
  ROLLBACK TO after_order;
  -- 매 order 보존, items 만 rollback.
COMMIT;
```

→ Partial rollback.

### Explicit lock (FOR UPDATE)
```sql
BEGIN;
  SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
  -- Other transaction 가 wait.
  
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
```

→ Exclusive row lock.

### FOR UPDATE SKIP LOCKED (queue)
```sql
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;

-- → Lock 된 row 가 skip. Multi-worker queue.
```

### FOR SHARE
```sql
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- → Shared lock. Read 가 OK, write 가 wait.
```

### Advisory lock (app-level)
```sql
-- Acquire
SELECT pg_advisory_lock(12345);

-- Critical section
-- ...

-- Release
SELECT pg_advisory_unlock(12345);
```

→ Cross-transaction. Cron / migration 친화.

### Transaction-scoped advisory
```sql
SELECT pg_advisory_xact_lock(12345);
-- → Transaction 끝 시 자동 release.
```

### Distributed lock (Postgres)
```ts
async function withDistributedLock<T>(key: number, fn: () => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query('SELECT pg_advisory_lock($1)', [key]);
    return await fn();
  } finally {
    await client.query('SELECT pg_advisory_unlock($1)', [key]);
    client.release();
  }
}
```

→ Single Postgres = 작은 system 의 distributed lock.

### Optimistic locking (version)
```sql
-- Version column
UPDATE users
SET name = 'Bob', version = version + 1
WHERE id = 1 AND version = 5;

-- Affected rows = 0 → conflict.
```

→ Lock 없음. Retry on conflict.

### Read-write split (transaction)
```ts
// Read replica
const reader = pool.replica;
const writer = pool.primary;

// Write transaction
await writer.transaction(async (tx) => {
  await tx.query('UPDATE ...');
});

// Read after write 의 함정:
// Write tx commit → replica 에 lag.
// → Read 가 stale.
```

→ Sticky reads.

### Long transaction 의 함정
```sql
BEGIN;
SELECT * FROM users LIMIT 1;
-- 1 hour 후
COMMIT;
```

→ Vacuum 가 freeze X. Bloat 누적.

```sql
-- Auto kill
SET statement_timeout = '30s';
SET idle_in_transaction_session_timeout = '60s';
```

### Deadlock
```
Tx A: lock row 1, wait row 2.
Tx B: lock row 2, wait row 1.

→ Postgres detect + abort 1.
```

```ts
try {
  await tx.query(...);
} catch (e) {
  if (e.code === '40P01') {
    // Deadlock — retry.
  }
}
```

### Deadlock 방지
```
- 모든 transaction 가 같은 order 로 lock.
- Short transaction.
- 최소 lock.
```

### Two-phase commit (XA, 2PC)
```sql
PREPARE TRANSACTION 'tx-1';
-- 다른 system 도 PREPARE.
-- Coordinator 가 모두 OK.

COMMIT PREPARED 'tx-1';
-- 또는
ROLLBACK PREPARED 'tx-1';
```

→ Cross-DB. 매우 느린. 거의 안 사용.

→ [[Backend_Saga_Choreography_vs_Orchestration]] 가 modern.

### Idempotent transaction
```sql
INSERT INTO transactions (id, ...) VALUES ($idempKey, ...)
ON CONFLICT (id) DO NOTHING;

-- Retry-safe.
```

### Outbox pattern
```sql
BEGIN;
  INSERT INTO orders (...);
  INSERT INTO outbox (event_type, payload) VALUES ('OrderCreated', ...);
COMMIT;

-- Background process: outbox → publish.
```

→ DB write + event 가 atomic.

→ [[Backend_Outbox_Pattern]].

### MVCC (Multi-Version Concurrency Control)
```
Postgres / MySQL InnoDB:
- 매 row 가 version 가 가짐.
- Read 가 lock X (snapshot).
- Write 가 새 version.

→ "Reader 가 writer 가 막지 않음".
```

→ [[CS_MVCC_Concurrency]].

### Connection pool + transaction
```ts
// 매 transaction 가 dedicated connection.
const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query('UPDATE ...');
  await client.query('COMMIT');
} finally {
  client.release();
}

// 또는 helper
await pool.transaction(async (tx) => {
  await tx.query('UPDATE ...');
});
```

### PgBouncer 함정 (transaction mode)
```
PgBouncer transaction mode:
- 매 transaction = 다른 connection.
- Session-bound feature 안 됨 (LISTEN, prepared, advisory lock).

→ Session mode 또는 raw query.
```

→ [[DB_Connection_Pooling_Patterns]].

### Phantom read (REPEATABLE READ 가 막음)
```
Tx A: SELECT count(*) FROM users WHERE age > 18 → 100.
Tx B: INSERT new user (age 20).
Tx A: SELECT count(*) → 101 (REPEATABLE READ X) / 100 (REPEATABLE READ).
```

### Serializable conflict
```sql
-- Postgres detect + abort.
ERROR: could not serialize access due to concurrent update
SQLSTATE: 40001
```

→ Retry on 40001.

### Performance
```
SERIALIZABLE: 가장 slow.
REPEATABLE READ: 약간 slow.
READ COMMITTED: default + 빠름.

→ Use case 의 적절.
```

### Real-world tip
```
1. Default = READ COMMITTED.
2. Transfer / financial = SERIALIZABLE 또는 explicit lock.
3. Long read = SET TRANSACTION READ ONLY.
4. Idempotency key 매 critical write.
5. Optimistic lock + version (web app).
6. Advisory lock (distributed lock).
```

### Distributed transaction (saga)
```
Cross-DB / cross-service:
- 2PC 안 사용 (slow, brittle).
- Saga + compensation.
- Idempotent step.
```

→ [[Backend_Saga_Choreography_vs_Orchestration]].

### 함정
```
- Long transaction: bloat / lock contention.
- Implicit transaction: ORM 의 auto-commit 무시.
- Lock order 다름: deadlock.
- Retry 무한: 내려.
- 2PC: 느린, 거의 안 사용.
```

## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Read | Default isolation |
| Critical write | SERIALIZABLE / FOR UPDATE |
| Queue | FOR UPDATE SKIP LOCKED |
| Web app | Optimistic + version |
| Distributed lock | pg_advisory_lock |
| Cross-service | Saga (NOT 2PC) |
| Idempotent | Idempotency key |

## ❌ 안티패턴
- **Long transaction**: bloat.
- **2PC**: brittle.
- **No retry on serialize fail**: lost.
- **모든 거 SERIALIZABLE**: slow.
- **Lock order inconsistent**: deadlock.
- **No timeout**: hang.

## 🤖 LLM 활용 힌트
- READ COMMITTED 가 default.
- FOR UPDATE SKIP LOCKED 가 queue 의 답.
- Advisory lock 가 app-level.
- Saga > 2PC.

## 🔗 관련 문서
- [[DB_Transaction_Isolation]]
- [[DB_Lock_Analysis]]
- [[CS_MVCC_Concurrency]]
