
ํธ๋์ญ์ ์ ์ญํ
ํธ๋์ญ์ ์ด๋
ํธ๋์ญ์
์ ์ฌ๋ฌ ๊ฐ์ ์กฐํ๋ ์ฐ๊ธฐ๋ฅผ ๋
ผ๋ฆฌ์ ์ผ๋ก ํ๋์ ์ฐ์ฐ์ผ๋ก ๋ฌถ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํต์ฌ ๊ธฐ๋ฅ์
๋๋ค.
๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ๊ณผ์ ์์ ๋ฐ์ํ ์ ์๋ ๋ค์ํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํด์ค๋๋ค.
ํธ๋์ญ์ ์ ํต์ฌ ์์น
์์์ฑ (Atomicity)
- ํ๋์ ํธ๋์ญ์ ์ ํฌํจ๋ ๋ชจ๋ ์ฐ๊ธฐ๋ ๋ชจ๋ ์ ์ฉ๋๊ฑฐ๋(์ปค๋ฐ) ๋ชจ๋ ์ทจ์(๋กค๋ฐฑ)๋ฉ๋๋ค
- ํธ๋์ญ์ ์์ ์ฟผ๋ฆฌ ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด ํธ๋์ญ์ ์ ๋กค๋ฐฑํจ์ผ๋ก์จ ๋ฐ์ดํฐ๊ฐ ๊นจ์ง๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค
BEGIN;
-- ์ฌ๋ฌ ์์
์ด ํ๋์ ๋
ผ๋ฆฌ์ ๋จ์
UPDATE account SET balance = balance - 1000 WHERE user_id = 1;
UPDATE account SET balance = balance + 1000 WHERE user_id = 2;
COMMIT; -- ๋ชจ๋ ์ฑ๊ณตํ๊ฑฐ๋, ํ๋๋ผ๋ ์คํจํ๋ฉด ๋ชจ๋ ๋กค๋ฐฑ
๊ฒฉ๋ฆฌ์ฑ (Isolation)
- ๋์์ ์คํ๋๋ ์ฌ๋ฌ ํธ๋์ญ์ ์ด ์๋ก ๊ฐ์ญํ์ง ์๋๋ก ๊ฒฉ๋ฆฌํฉ๋๋ค
- ํธ๋์ญ์ ์ ๋ง์น ํผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ฒ๋ผ ๋์ํฉ๋๋ค
๋์์ฑ ๋ฌธ์ ์ ์ ํ
1. Lost Update (๊ฐฑ์ ์์ค)
๊ฐ์ฅ ํํ๊ณ ์น๋ช
์ ์ธ ๋ฌธ์ ์
๋๋ค.
๋ ๊ฐ์ ํธ๋์ญ์
์ด ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์์ ํ ๋, ๋์ค์ ์คํ๋ ํธ๋์ญ์
์ด ๋จผ์ ์คํ๋ ํธ๋์ญ์
์ ๋ณ๊ฒฝ์ ๋ฎ์ด์๋๋ค.
# ์ฌ์ฉ์ A์ ํฌ์ธํธ 1000P์์ ๋์์ ์ ๋ฆฝ(+500P)๊ณผ ์ฐจ๊ฐ(-300P) ๋ฐ์
# ์ด๊ธฐ: 1000P
์๊ฐ | ์ ๋ฆฝ ํธ๋์ญ์
(+500) | ์ฐจ๊ฐ ํธ๋์ญ์
(-300)
-----|------------------------|-------------------------
T1 | SELECT point = 1000 |
T2 | | SELECT point = 1000
T3 | ๊ณ์ฐ: 1000 + 500 |
T4 | | ๊ณ์ฐ: 1000 - 300
T5 | UPDATE point = 1500 |
T6 | | UPDATE point = 700 (๋ฎ์ด์!)
# ๊ฒฐ๊ณผ: 700P (๊ธฐ๋: 1200P) → 500P ์์ค
2. Dirty Read (์ค์ ์ฝ๊ธฐ)
์ปค๋ฐ๋์ง ์์ ๋ค๋ฅธ ํธ๋์ญ์ ์ ๋ณ๊ฒฝ์ฌํญ์ ์ฝ๋ ๋ฌธ์ ์ ๋๋ค.
3. Non-Repeatable Read (๋ฐ๋ณต ๋ถ๊ฐ๋ฅ ์ฝ๊ธฐ)
๊ฐ์ ํธ๋์ญ์ ๋ด์์ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ ๋ฒ ์ฝ์์ ๋ ๋ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ ๋ฌธ์ ์ ๋๋ค.
4. Phantom Read (์ ๋ น ์ฝ๊ธฐ)
๊ฐ์ ์กฐ๊ฑด์ผ๋ก ์กฐํํ์ ๋ ์ด์ ์ ์๋ ํ์ด ๋ํ๋๊ฑฐ๋ ์๋ ํ์ด ์ฌ๋ผ์ง๋ ๋ฌธ์ ์ ๋๋ค.
ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค
PostgreSQL์ 4๊ฐ์ง ๊ฒฉ๋ฆฌ ์์ค์ ์ ๊ณตํ์ฌ ๋์์ฑ๊ณผ ์ ํฉ์ฑ์ ๊ท ํ์ ์กฐ์ ํ ์ ์์ต๋๋ค.
| ๊ฒฉ๋ฆฌ ์์ค | Dirty Read | Non-Repeatable Read | Phantom Read | ํน์ง |
| READ UNCOMMITTED | ๊ฐ๋ฅ | ๊ฐ๋ฅ | ๊ฐ๋ฅ | PostgreSQL์์๋ READ COMMITTED์ฒ๋ผ ๋์ |
| READ COMMITTED | ๋ฐฉ์ง | ๊ฐ๋ฅ | ๊ฐ๋ฅ | PostgreSQL ๊ธฐ๋ณธ๊ฐ, ์ปค๋ฐ๋ ๋ฐ์ดํฐ๋ง ์ฝ์ |
| REPEATABLE READ | ๋ฐฉ์ง | ๋ฐฉ์ง | ๋ฐฉ์ง | ํธ๋์ญ์ ์์ ์์ ์ ์ค๋ ์ท ์ฌ์ฉ |
| SERIALIZABLE | ๋ฐฉ์ง | ๋ฐฉ์ง | ๋ฐฉ์ง | ์์ ํ ๊ฒฉ๋ฆฌ, ์ฑ๋ฅ ์ ํ |
-- ๊ฒฉ๋ฆฌ ์์ค ์ค์
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ์์
์ํ
COMMIT;
๊ฒฉ๋ฆฌ ์์ค๋ง์ผ๋ก ํด๊ฒฐ๋์ง ์๋ ๋ฌธ์
๊ฒฉ๋ฆฌ ์์ค์ ๋์ฌ๋ Lost Update ๋ฌธ์ ๋ ์์ ํ ํด๊ฒฐ๋์ง ์์ต๋๋ค. ์ด๋ฅผ ์ํด ๋ช ์์ ์ธ ์ ๊ธ ๊ธฐ๋ฒ์ด ํ์ํฉ๋๋ค.
๋์์ฑ ์ ์ด ์ ๋ต
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๋์์ฑ ์ ์ด๋ฅผ ์ํด ํฌ๊ฒ ๋ ๊ฐ์ง ์ ๊ธ ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค.
1. ๋น๊ด์ ์ ๊ธ (Pessimistic Lock)
๊ฐ๋
์ ์ ์ ๊ธ์ด๋ผ๊ณ ๋ ํ๋ฉฐ, ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์๊ฐ ์ ๊ธ์ ํ๋ํ์ฌ ๋ค๋ฅธ ํธ๋์ญ์ ์ ์ ๊ทผ์ ์ฐจ๋จํ๋ ๋ฐฉ์์ ๋๋ค. "์ถฉ๋์ด ๋ฐ์ํ ๊ฒ์ด๋ค"๋ผ๊ณ ๋น๊ด์ ์ผ๋ก ๊ฐ์ ํ๊ณ ๋ฏธ๋ฆฌ ์ ๊ธ์ ๊ฒ๋๋ค.
๋์ ์๋ฆฌ
- ํธ๋์ญ์ 1์ด ๋จผ์ ๋ฐ์ดํฐ์ ๋ํ ์ ๊ธ์ ๊ตฌํ๊ณ ,
- ํธ๋์ญ์ 2๋ ํธ๋์ญ์ 1์ด ๋๋ ๋ค์ ์๋ฃํด์ ์ ๊ธ์ ๋ฐํํ ๋๊น์ง ๋๊ธฐํ์ฌ ์ผ๊ด์ฑ์ด ๊นจ์ง๋ ๋ฌธ์ ๋ฅผ ๋ฐฉ์ง.

- ์ฃผ๋ฌธ ์ทจ์ ํธ๋์ญ์ ์ด ๋จผ์ ์ฃผ๋ฌธ ์ํ๋ฅผ ์ทจ์ ์ํ๋ก ๋ณ๊ฒฝ.
- ๋ฐฐ์ก ์์ ํธ๋์ญ์ ์ด ์ฃผ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ฉด ์ทจ์๋ ์ํ์ด๋ฏ๋ก, ๋ฐฐ์ก ์์ ์คํจ

FOR UPDATE ์ต์
-- ํฌ์ธํธ ์ ๋ฆฝ
BEGIN;
-- 1. ํ ์ ๊ธ ํ๋ (๋ค๋ฅธ ํธ๋์ญ์
์ ๋๊ธฐ)
SELECT balance FROM point
WHERE user_id = 1
FOR UPDATE; -- ๋ค๋ฅธ ํธ๋์ญ์
์ฐจ๋จ
-- 2. ์์ ํ๊ฒ ์
๋ฐ์ดํธ (๋
์ ์ํ)
UPDATE point
SET balance = balance + 500
WHERE user_id = 1
RETURNING balance;
INSERT INTO point_history (user_id, amount, reason, balance_after)
VALUES (1, 500, '๊ตฌ๋งค ์ ๋ฆฝ', 1500);
COMMIT; -- ์ ๊ธ ํด์
| ๋ช ๋ น์ด | ์ค๋ช | ์ฐจ๋จ ๋์ |
| FOR UPDATE | ์ฐ๊ธฐ ์ ๊ธ | ๋ค๋ฅธ ๋ชจ๋ ์ ๊ธ ์ฐจ๋จ |
| FOR NO KEY UPDATE | ์ธ๋ ํค ์ ์ธ ์ฐ๊ธฐ ์ ๊ธ | UPDATE ์ฐจ๋จ, ์ธ๋ ํค ์ฐธ์กฐ๋ ํ์ฉ |
| FOR SHARE | ์ฝ๊ธฐ ์ ๊ธ | UPDATE/DELETE ์ฐจ๋จ, SELECT๋ ํ์ฉ |
| FOR KEY SHARE | ์ธ๋ ํค์ฉ ์ฝ๊ธฐ ์ ๊ธ | DELETE ์ฐจ๋จ, UPDATE๋ ํ์ฉ |
-- FOR UPDATE: ์์ ํ ์ฐ๊ธฐ ์ ๊ธ
SELECT * FROM orders WHERE order_id = 1 FOR UPDATE;
-- FOR SHARE: ์ฝ๊ธฐ๋ ํ์ฉ, ์ฐ๊ธฐ๋ง ์ฐจ๋จ
SELECT * FROM products WHERE product_id = 1 FOR SHARE;
์ ๊ธ ํ์์์ ์ค์
-- ์ ๊ธ ๋๊ธฐ ์๊ฐ ์ ํ (5์ด)
SET lock_timeout = '5s';
BEGIN;
SELECT * FROM account WHERE user_id = 1 FOR UPDATE;
-- 5์ด ์ด์ ๋๊ธฐํ๋ฉด ์์ธ ๋ฐ์
์ฅ์
- ์ ํฉ์ฑ 100% ๋ณด์ฅ: ๋์ ์์ ๋ถ๊ฐ๋ฅ
- ๊ตฌํ์ด ๋จ์: SELECT์ FOR UPDATE๋ง ์ถ๊ฐ
- ์์ธก ๊ฐ๋ฅ: ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ๋์ด ๊ฒฐ๊ณผ ์์ธก ๊ฐ๋ฅ
๋จ์
- ๋๊ธฐ ์๊ฐ ์ฆ๊ฐ: ๋์ ์์ฒญ์ด ๋ง์ผ๋ฉด ์์ฐจ ๋๊ธฐ
- ์ฒ๋ฆฌ๋ ์ ํ: ๋ณ๋ ฌ ์ฒ๋ฆฌ ๋ถ๊ฐ๋ฅ
- ๋ฐ๋๋ฝ ์ํ: ์ฌ๋ฌ ์์ ์ ๊ธ ์ ์์ ๊ด๋ฆฌ ํ์
์ ํฉํ ์ํฉ
- ์ฌ๊ณ ์ฐจ๊ฐ, ํฌ์ธํธ ์ฐจ๊ฐ ๋ฑ ์ ํฉ์ฑ์ด ์ ๋์ ์ผ๋ก ์ค์ํ ๊ฒฝ์ฐ
- ๊ฒฐ์ ์ฒ๋ฆฌ, ์ข์ ์์ฝ ๋ฑ ์ถฉ๋์ด ๋น๋ฒํ๊ฒ ๋ฐ์ํ๋ ๊ฒฝ์ฐ
- ๊ธ์ ๊ฑฐ๋, ํ๊ณ ๋ฐ์ดํฐ ๋ฑ ๋ฐ์ดํฐ ์์ค์ด ํ์ฉ๋์ง ์๋ ๊ฒฝ์ฐ
2. ๋๊ด์ ์ ๊ธ (Optimistic Lock)
๊ฐ๋
๋น์ ์ ์ ๊ธ์ด๋ผ๊ณ ๋ ํ๋ฉฐ, ์ ๊ธ ์์ด ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์์ ํ ๋ ๊ฐ์ ๋น๊ตํด์ ์์ ํ๋ ๋ฐฉ์์
๋๋ค.
"์ถฉ๋์ด ๊ฑฐ์ ์์ ๊ฒ์ด๋ค"๋ผ๊ณ ๋๊ด์ ์ผ๋ก ๊ฐ์ ํ๊ณ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด ๊ทธ๋ ์ฒ๋ฆฌํฉ๋๋ค.
๋์ ์๋ฆฌ
- ๋น์ ์ ์ ๊ธ์ ์ฐ์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ์๋
- ์กฐํํ ๋ฒ์ ๊ฐ์ ๋น๊ตํด์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์ง ์์๋์ง๋ฅผ ํ์ธํ๊ณ ์ปค๋ฐํ๊ฑฐ๋ ๋กค๋ฐฑ ์คํ

PostgreSQL ๊ตฌํ
๋๊ด์ ์ ๊ธ์ ํต์ฌ์ ์ฟผ๋ฆฌ ์คํ์ ๋ง์ง ์์ผ๋ฉด์ ๋ฐ์ดํฐ๊ฐ ์๋ชป ๋ณ๊ฒฝ๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ค๋ ์ !
-- ํฌ์ธํธ ์ ๋ฆฝ
BEGIN;
-- ํ์ฌ ๋ฒ์ ์กฐํ (์ ๊ธ ์์)
SELECT balance, version FROM point WHERE user_id = 1;
-- ๊ฒฐ๊ณผ: balance=1000, version=5
-- ๋ฒ์ ํ์ธํ๋ฉฐ ์
๋ฐ์ดํธ
UPDATE point
SET balance = balance + 500,
version = version + 1
WHERE user_id = 1
AND version = 5; -- ๋ฒ์ ๋ถ์ผ์น ์ ์คํจ → ์ฌ์๋
INSERT INTO point_history (user_id, amount, reason, balance_after)
VALUES (1, 500, '๊ตฌ๋งค ์ ๋ฆฝ', 1500);
COMMIT;โ
-- ๋น๊ด์ ์ ๊ธ: SELECT ์์ ์ ๋ค๋ฅธ ํธ๋์ญ์
์ฐจ๋จ
SELECT * FROM account WHERE user_id = 1 FOR UPDATE;
-- ↑ ๋ค๋ฅธ ํธ๋์ญ์
์ ์ฌ๊ธฐ์ ๋๊ธฐ
-- ๋๊ด์ ์ ๊ธ: SELECT๋ ๋ชจ๋ ํ์ฉ
SELECT * FROM account WHERE user_id = 1;
-- ↑ ์ฌ๋ฌ ํธ๋์ญ์
์ด ๋์ ์กฐํ ๊ฐ๋ฅ
-- UPDATE ์์ ์๋ง version์ผ๋ก ๊ฒ์ฆ
์ฌ์๋ ๋ก์ง (ํ์)
์ถฉ๋ ๋ฐ์ ์ ์ฌ์๋ ๋ก์ง์ด ๋ฐ๋์ ํ์ํฉ๋๋ค.
-- ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ ์ฌ์๋ ๋ก์ง ์์
DO $$
DECLARE
retry_count INT := 0;
max_retries INT := 3;
rows_affected INT;
current_version BIGINT;
current_balance INT;
BEGIN
LOOP
-- ํ์ฌ ๊ฐ ์กฐํ
SELECT balance, version INTO current_balance, current_version
FROM account WHERE user_id = 1;
-- ์
๋ฐ์ดํธ ์๋
UPDATE account
SET balance = current_balance - 300,
version = version + 1
WHERE user_id = 1
AND version = current_version;
GET DIAGNOSTICS rows_affected = ROW_COUNT;
IF rows_affected > 0 THEN
EXIT; -- ์ฑ๊ณต ์ ์ข
๋ฃ
END IF;
-- ์ฌ์๋ ์นด์ดํธ ์ฆ๊ฐ
retry_count := retry_count + 1;
IF retry_count >= max_retries THEN
RAISE EXCEPTION '์ต๋ ์ฌ์๋ ํ์ ์ด๊ณผ';
END IF;
-- ์งง์ ๋๊ธฐ ํ ์ฌ์๋
PERFORM pg_sleep(0.01 * retry_count); -- ์ง์ ๋ฐฑ์คํ
END LOOP;
END $$;
์ฅ์
- ๋์ ๋์์ฑ: ์ ๊ธ ๋๊ธฐ ์์ด ๋ณ๋ ฌ ์ฒ๋ฆฌ ๊ฐ๋ฅ
- ์ฝ๊ธฐ ์ฑ๋ฅ ์ฐ์: SELECT ์ ์ ๊ธ ํ๋ ๋ถํ์
- ๋ฐ๋๋ฝ ์์: ์ ๊ธ์ ์ฌ์ฉํ์ง ์์ ๋ฐ๋๋ฝ ๋ฐ์ ์ ํจ
๋จ์
- ์ฌ์๋ ๋ก์ง ํ์: ์ถฉ๋ ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ ์ฒ๋ฆฌ ํ์
- version ๊ด๋ฆฌ: ๋ณ๋ ์ปฌ๋ผ ์ถ๊ฐ ๋ฐ ๊ด๋ฆฌ ํ์
- ์ถฉ๋ ๋น๋ฒ ์ ๋นํจ์จ: ์ฌ์๋๊ฐ ๋ง์์ง๋ฉด ์คํ๋ ค ์ฑ๋ฅ ์ ํ
์ ํฉํ ์ํฉ
- ๊ฒ์๋ฌผ ์กฐํ์, ์ข์์ ๋ฑ ์ถฉ๋์ด ์ ๊ณ ๊ฐ๋ ์คํจํด๋ ๊ด์ฐฎ์ ๊ฒฝ์ฐ
- ํต๊ณ ๋ฐ์ดํฐ, ์บ์ ๊ฐฑ์ ๋ฑ ์ ํฉ์ฑ๋ณด๋ค ์ฑ๋ฅ์ด ์ค์ํ ๊ฒฝ์ฐ
- ์ฝ๊ธฐ๊ฐ ๋ง๊ณ ์ฐ๊ธฐ๊ฐ ์ ์ ๊ฒฝ์ฐ
3. ์์์ ์ฐ์ฐ (์ถ๊ฐ ์ ๋ต)
๊ฐ๋
SELECT ์์ด UPDATE ๋ฌธ์์ ์ง์ ๊ณ์ฐํ์ฌ ์ฝ๊ธฐ-์ฐ๊ธฐ ๊ฐญ์ ์์ฒ์ ์ผ๋ก ์ ๊ฑฐํ๋ ๋ฐฉ์์ ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ง์ด ๋ด๋ถ์ ์ผ๋ก ๋์์ฑ์ ๋ณด์ฅํฉ๋๋ค.
PostgreSQL ๊ตฌํ
-- ์ฝ๊ธฐ ์์ด ๋ฐ๋ก ์
๋ฐ์ดํธ
BEGIN;
UPDATE point
SET balance = balance + 500
WHERE user_id = 1
RETURNING balance; -- ์
๋ฐ์ดํธ ํ ๊ฐ ๋ฐํ ํ์!
INSERT INTO point_history (user_id, amount, reason, balance_after)
VALUES (1, 500, '๊ตฌ๋งค ์ ๋ฆฝ', 1500);
COMMIT;โ
์ฅ์
- ๊ฐ์ฅ ์์ ํ๊ณ ๋น ๋ฆ: ๋ด๋ถ์ ์ผ๋ก ๋์์ฑ ๋ณด์ฅ
- ์ฝ๋ ๊ฐ๊ฒฐ: ์ถ๊ฐ ๋ก์ง ๋ถํ์
- ์ ๊ธ ๋ถํ์: ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์์์ ์ฒ๋ฆฌ
๋จ์
- ๋จ์ ์ฐ์ฐ๋ง ๊ฐ๋ฅ: ๋ณต์กํ ๊ณ์ฐ์ด๋ ์กฐ๊ฑด ์ฒ๋ฆฌ ๋ถ๊ฐ
- ๊ฒฐ๊ณผ๊ฐ ํ์ ์: RETURNING ์ ์ฌ์ฉ ํ์
๋ฐ๋๋ฝ (Deadlock) ๋ฐฉ์ง
๋ฐ๋๋ฝ์ด๋
๋ ๊ฐ ์ด์์ ํธ๋์ญ์ ์ด ์๋ก๊ฐ ๋ณด์ ํ ์ ๊ธ์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๋ฌดํ ๋๊ธฐ ์ํ์ ๋น ์ง๋ ํ์์ ๋๋ค.
ํธ๋์ญ์
A ํธ๋์ญ์
B
----------------- -----------------
์ ๊ธ(์์1) ์ ๊ธ(์์2)
๋๊ธฐ(์์2) ←-------→ ๋๊ธฐ(์์1)
๋ฐ๋๋ฝ ๋ฐ์ ์์
-- ํธ๋์ญ์
A
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
UPDATE account SET balance = balance + 100 WHERE user_id = 2; -- ๋๊ธฐ
COMMIT;
-- ํธ๋์ญ์
B (๋์ ์คํ)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 2;
UPDATE account SET balance = balance + 100 WHERE user_id = 1; -- ๋ฐ๋๋ฝ!
COMMIT;
๋ฐ๋๋ฝ ๋ฐฉ์ง ์ ๋ต
1. ์ ๊ธ ์์ ํต์ผ
์ฌ๋ฌ ์์์ ์ ๊ธ ๋ ํญ์ ๊ฐ์ ์์๋ก ์ ๊ธ์ ํ๋ํฉ๋๋ค.
-- โ
์ฌ๋ฐ๋ฅธ ์: user_id ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๊ธ
BEGIN;
SELECT * FROM account
WHERE user_id IN (1, 2)
ORDER BY user_id ASC -- ํญ์ ์์ ๋ฒํธ๋ถํฐ
FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
UPDATE account SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
2. ํ์์์ ์ค์
-- ๋ฐ๋๋ฝ ๊ฐ์ง ์๊ฐ ์ค์ (๊ธฐ๋ณธ 1์ด)
SET deadlock_timeout = '1s';
-- ์ ๊ธ ๋๊ธฐ ์ต๋ ์๊ฐ ์ค์
SET lock_timeout = '5s';
3. ํธ๋์ญ์ ์๊ฐ ์ต์ํ
-- โ ๋์ ์: ํธ๋์ญ์
์์์ ์ธ๋ถ API ํธ์ถ
BEGIN;
SELECT * FROM orders WHERE order_id = 1 FOR UPDATE;
-- ์ธ๋ถ ๊ฒฐ์ API ํธ์ถS (3์ด ์์)
UPDATE orders SET status = 'paid';
COMMIT;
-- โ
์ข์ ์: ํธ๋์ญ์
์ ์ ์ธ๋ถ ์์
์๋ฃ
-- ์ธ๋ถ ๊ฒฐ์ API ํธ์ถ
BEGIN;
SELECT * FROM orders WHERE order_id = 1 FOR UPDATE;
UPDATE orders SET status = 'paid';
COMMIT;
์ค๋ฌด ์ ์ฉ ์๋๋ฆฌ์ค
์๋๋ฆฌ์ค 1: ํฌ์ธํธ ์ฐจ๊ฐ (๋น๊ด์ ์ ๊ธ ํ์)
BEGIN;
-- 1. ํฌ์ธํธ ์กฐํ ๋ฐ ์ ๊ธ ํ๋ (๋ค๋ฅธ ํธ๋์ญ์
์ฐจ๋จ)
SELECT user_id, balance
FROM point
WHERE user_id = 1
FOR UPDATE;
-- ๊ฒฐ๊ณผ: balance = 10000
-- 2. ์์ก ํ์ธ ๋ฐ ์ฐจ๊ฐ
UPDATE point
SET balance = balance - 5000,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = 1
AND balance >= 5000; -- ์์ก ๋ถ์กฑ ๋ฐฉ์ง
-- 3. ์
๋ฐ์ดํธ ์คํจ ์ ์์ธ ์ฒ๋ฆฌ
IF NOT FOUND THEN
ROLLBACK;
RAISE EXCEPTION 'ํฌ์ธํธ ์์ก์ด ๋ถ์กฑํฉ๋๋ค';
END IF;
-- 4. ์ฐจ๊ฐ ์ด๋ ฅ ์ ์ฅ
INSERT INTO point_history (user_id, amount, reason, balance_after)
SELECT 1, -5000, '์ํ ๊ตฌ๋งค (์ฃผ๋ฌธ๋ฒํธ: ORD-12345)', balance
FROM point
WHERE user_id = 1;
COMMIT; -- ์ ๊ธ ํด์
-- ๋ค๋ฅธ ํธ๋์ญ์
์ ์ ๊ธ ํด์ ๊น์ง ๋๊ธฐํ๋ฏ๋ก ๋์ ์ฐจ๊ฐ ์์๋ ์ ํฉ์ฑ ๋ณด์ฅ
์๋๋ฆฌ์ค 2: ์กฐํ์ ์ฆ๊ฐ (๋๊ด์ ์ ๊ธ ๋๋ ์์์ ์ฐ์ฐ)
-- ๋ฐฉ๋ฒ 1: ์์์ ์ฐ์ฐ (๊ถ์ฅ)
UPDATE posts
SET view_count = view_count + 1
WHERE post_id = 1;
-- ๋ฐฉ๋ฒ 2: ๋๊ด์ ์ ๊ธ
UPDATE posts
SET view_count = view_count + 1,
version = version + 1
WHERE post_id = 1
AND version = 5;
์๋๋ฆฌ์ค 3: ์ ์ ๊ฐ ํฌ์ธํธ ์ด๋(์์ ๋ณด์ฅ)
ํต์ฌ ํฌ์ธํธ:
- ์์ ๋ณด์ฅ: ORDER BY user_id ASC : ๋ชจ๋ ํธ๋์ญ์ ์ด ๋์ผํ ์์(์์ ๋ฒํธ → ํฐ ๋ฒํธ)๋ก ์ ๊ธ ํ๋
- ์ฌ๋ฌ ํ์ ํ ๋ฒ์ ์ ๊ธ: WHERE user_id IN (1, 2)๋ก ํ์ํ ๋ชจ๋ ํ์ ๋จผ์
BEGIN;
-- 1. user_id ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๊ธ ํ๋ (๋ฐ๋๋ฝ ๋ฐฉ์ง)
SELECT user_id, balance
FROM point
WHERE user_id IN (1, 2)
ORDER BY user_id ASC -- ํญ์ ์์ ๋ฒํธ๋ถํฐ ์ ๊ธ
FOR UPDATE;
-- 2. ์ก์ ์(A) ์์ก ํ์ธ
DECLARE
sender_balance INT;
BEGIN
SELECT balance INTO sender_balance
FROM point
WHERE user_id = 1;
IF sender_balance < 1000 THEN
RAISE EXCEPTION '์ก์ ์์ ํฌ์ธํธ ์์ก์ด ๋ถ์กฑํฉ๋๋ค';
END IF;
END;
-- 3. ์ก์ ์(A) ์ฐจ๊ฐ
UPDATE point
SET balance = balance - 1000,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = 1;
INSERT INTO point_history (user_id, amount, reason, balance_after)
SELECT 1, -1000, 'ํฌ์ธํธ ์ ๋ฌผ (๋ฐ๋ ์ฌ๋: user_2)', balance
FROM point
WHERE user_id = 1;
-- 4. ์์ ์(B) ์ ๋ฆฝ
UPDATE point
SET balance = balance + 1000,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = 2;
INSERT INTO point_history (user_id, amount, reason, balance_after)
SELECT 2, 1000, 'ํฌ์ธํธ ์ ๋ฌผ ๋ฐ์ (๋ณด๋ธ ์ฌ๋: user_1)', balance
FROM point
WHERE user_id = 2;
COMMIT;
[๋ฐ๋๋ฝ ๋ฐ์]
ํธ๋์ญ์
A ํธ๋์ญ์
B
์ ๊ธ(user_1) ์ ๊ธ(user_2)
๋๊ธฐ(user_2) ←-→ ๋๊ธฐ(user_1) ← ๋ฐ๋๋ฝ!
[์์ ๋ณด์ฅ์ผ๋ก ๋ฐฉ์ง]
ํธ๋์ญ์
A ํธ๋์ญ์
B
์ ๊ธ(user_1, user_2) ๋๊ธฐ...
์์
์๋ฃ ๋๊ธฐ...
์ ๊ธ ํด์ ์ ๊ธ(user_1, user_2)
์์
์๋ฃ
์ ๊ธ ํด์
๊ฒฐ๋ก
- ํธ๋์ญ์ ์ ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ฅผ ํ๋์ ๋ ผ๋ฆฌ์ ๋จ์๋ก ๋ฌถ์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค
- ํ๋์ ํธ๋์ญ์ ์ ๋ชจ๋ ์ฑ๊ณตํ๊ฑฐ๋ ๋ชจ๋ ์คํจํฉ๋๋ค (์์์ฑ)
- ๊ฒฉ๋ฆฌ ์์ค๋ง์ผ๋ก๋ Lost Update ๋ฌธ์ ๋ฅผ ์์ ํ ํด๊ฒฐํ ์ ์์ต๋๋ค
- ๋น๊ด์ ์ ๊ธ(์ ์ ์ ๊ธ)์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์๊ฐ ์ ๊ธ์ ๊ฑธ์ด ์ถฉ๋์ ๋ฐฉ์งํฉ๋๋ค
- ๋๊ด์ ์ ๊ธ(๋น์ ์ ์ ๊ธ)์ ์ฟผ๋ฆฌ ์คํ ์์ฒด๋ ๋ง์ง ์์ผ๋ฉด์ version ๋น๊ต๋ก ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ฐฉ์งํฉ๋๋ค
- ์ ํฉ์ฑ์ด ์ค์ํ๋ฉด ๋น๊ด์ ์ ๊ธ, ์ฑ๋ฅ์ด ์ค์ํ๊ณ ์ถฉ๋์ด ์ ์ผ๋ฉด ๋๊ด์ ์ ๊ธ์ ์ฌ์ฉํฉ๋๋ค
DB ๋ ๋ฒจ์ ๋์์ฑ ์ ์ด ์ธ์๋, ์์คํ ์์ ์ฑ๊ณผ ์ค๋ฌด ์ ์ฐ์ฑ์ ์ํด ๋ค๋ฅธ ๊ด์ ๋ค๋ ํจ๊ป ๊ณ ๋ คํด๋ณด์์ผ ํ๋ค!
์๋ฅผ๋ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ ๋ฝ (Java/Spring ๊ธฐ๋ฐ), ๋ถ์ฐ ๋ฝ ์์คํ ๋ฑ
ํธ๋ํฝ์ด ๋ง์ด ๋ฐ์ํ๋ ์ค์๋น์ค์์๋ DB ๋ฝ๋ง์ผ๋ก๋ ๋ถ์กฑํ ๊ฒฝ์ฐ๊ฐ ๋ง์,
์์ ๊ฐ์ด ๋ค๊ณ์ธต ๋์์ฑ ์ ๋ต์ ํจ๊ป ์ค๊ณํ๋ ๋ฐฉ๋ฒ๋ ์ค์ํจ.
'Database' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ๋ ผ๋ฆฌ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ๋ง โ ํค(Key)์ ๋ชจ๋ ๊ฒ (0) | 2026.01.03 |
|---|---|
| Postgres์ PostGIS ๊ฐ๋ , ์ปค๋จธ์ค ํ์ฉ ์ฌ๋ก ๋ฐ ์ค์น ๊ฐ์ด๋ (1) | 2026.01.01 |
| ๊ฐ๋ ์ ๋ชจ๋ธ๋ง (2) - ์ํฐํฐ ๊ด๊ณ์ ERD ์ค์ต (0) | 2025.10.07 |
| ๊ฐ๋ ์ ๋ชจ๋ธ๋ง (1) - ์๊ตฌ์ฌํญ ๋ถ์๊ณผ ์ํฐํฐ์ ์ดํด (1) | 2025.10.07 |
| DB ์ค๊ณ์ ์ค์์ฑ๊ณผ 3๋จ๊ณ ์ค๊ณ ํ๋ก์ธ์ค (0) | 2025.10.07 |