[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user