ํŠธ๋žœ์žญ์…˜

ํŠธ๋žœ์žญ์…˜

1. ํŠธ๋žœ์žญ์…˜ ๊ฐœ๋…

ํŠธ๋žœ์žญ์…˜์€ ํ•˜๋‚˜์˜ ๋…ผ๋ฆฌ์  ์ž‘์—… ๋‹จ์œ„๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์—ฐ์‚ฐ๋“ค์˜ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     ๊ณ„์ขŒ ์ด์ฒด ํŠธ๋žœ์žญ์…˜                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  1. A ๊ณ„์ขŒ์—์„œ 10๋งŒ์› ์ฐจ๊ฐ                               โ”‚
โ”‚  2. B ๊ณ„์ขŒ์— 10๋งŒ์› ์ถ”๊ฐ€                                 โ”‚
โ”‚  โ†’ ๋‘˜ ๋‹ค ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜, ๋‘˜ ๋‹ค ์‹คํŒจํ•ด์•ผ ํ•จ                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

2. ACID ์†์„ฑ

์†์„ฑ ์˜๋ฌธ ์„ค๋ช…
์›์ž์„ฑ Atomicity ์ „๋ถ€ ์„ฑ๊ณต ๋˜๋Š” ์ „๋ถ€ ์‹คํŒจ
์ผ๊ด€์„ฑ Consistency ํŠธ๋žœ์žญ์…˜ ์ „ํ›„๋กœ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์œ ์ง€
๊ฒฉ๋ฆฌ์„ฑ Isolation ๋™์‹œ ์‹คํ–‰ ํŠธ๋žœ์žญ์…˜ ๊ฐ„ ๊ฐ„์„ญ ๋ฐฉ์ง€
์ง€์†์„ฑ Durability ์™„๋ฃŒ๋œ ํŠธ๋žœ์žญ์…˜์€ ์˜๊ตฌ ์ €์žฅ

3. ๊ธฐ๋ณธ ํŠธ๋žœ์žญ์…˜ ๋ช…๋ น

BEGIN / COMMIT / ROLLBACK

-- ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
BEGIN;
-- ๋˜๋Š”
START TRANSACTION;

-- ์ž‘์—… ์ˆ˜ํ–‰
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;

-- ์ปค๋ฐ‹ (๋ณ€๊ฒฝ์‚ฌํ•ญ ํ™•์ •)
COMMIT;

-- ๋˜๋Š” ๋กค๋ฐฑ (๋ณ€๊ฒฝ์‚ฌํ•ญ ์ทจ์†Œ)
ROLLBACK;

์‹ค์Šต ์˜ˆ์ œ

-- ํ…Œ์ด๋ธ” ์ƒ์„ฑ
CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    balance NUMERIC(12, 2) DEFAULT 0
);

INSERT INTO accounts (name, balance) VALUES
('๊น€์ฒ ์ˆ˜', 1000000),
('์ด์˜ํฌ', 500000);

-- ์ด์ฒด ํŠธ๋žœ์žญ์…˜
BEGIN;

UPDATE accounts SET balance = balance - 100000 WHERE name = '๊น€์ฒ ์ˆ˜';
UPDATE accounts SET balance = balance + 100000 WHERE name = '์ด์˜ํฌ';

-- ํ™•์ธ
SELECT * FROM accounts;

-- ํ™•์ • ๋˜๋Š” ์ทจ์†Œ
COMMIT;  -- ๋˜๋Š” ROLLBACK;

4. ์ž๋™ ์ปค๋ฐ‹ (Autocommit)

psql์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ž๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ์ž…๋‹ˆ๋‹ค.

-- ์ž๋™ ์ปค๋ฐ‹ ๋ชจ๋“œ์—์„œ ๊ฐ ๋ฌธ์žฅ์€ ๊ฐœ๋ณ„ ํŠธ๋žœ์žญ์…˜
INSERT INTO accounts (name, balance) VALUES ('๋ฐ•๋ฏผ์ˆ˜', 300000);
-- ์ฆ‰์‹œ ์ปค๋ฐ‹๋จ

-- ์ž๋™ ์ปค๋ฐ‹ ๋น„ํ™œ์„ฑํ™”
\set AUTOCOMMIT off

-- ์ด์ œ ๋ช…์‹œ์  COMMIT ํ•„์š”
INSERT INTO accounts (name, balance) VALUES ('์ตœ์ง€์˜', 400000);
COMMIT;

-- ์ž๋™ ์ปค๋ฐ‹ ๋‹ค์‹œ ํ™œ์„ฑํ™”
\set AUTOCOMMIT on

5. SAVEPOINT

ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋ถ€๋ถ„ ๋กค๋ฐฑ ์ง€์ ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

BEGIN;

INSERT INTO accounts (name, balance) VALUES ('์‹ ๊ทœ1', 100000);
SAVEPOINT sp1;

INSERT INTO accounts (name, balance) VALUES ('์‹ ๊ทœ2', 200000);
SAVEPOINT sp2;

INSERT INTO accounts (name, balance) VALUES ('์‹ ๊ทœ3', 300000);

-- sp2๋กœ ๋กค๋ฐฑ (์‹ ๊ทœ3๋งŒ ์ทจ์†Œ)
ROLLBACK TO SAVEPOINT sp2;

-- sp1์œผ๋กœ ๋กค๋ฐฑ (์‹ ๊ทœ2, ์‹ ๊ทœ3 ์ทจ์†Œ)
ROLLBACK TO SAVEPOINT sp1;

-- ์ „์ฒด ์ปค๋ฐ‹ (์‹ ๊ทœ1๋งŒ ์ €์žฅ)
COMMIT;

SAVEPOINT ๊ด€๋ฆฌ

-- SAVEPOINT ํ•ด์ œ
RELEASE SAVEPOINT sp1;

-- SAVEPOINT ๋ฎ์–ด์“ฐ๊ธฐ (๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ ์žฌ์ƒ์„ฑ)
SAVEPOINT mypoint;
-- ... ์ž‘์—… ...
SAVEPOINT mypoint;  -- ์ƒˆ ์ง€์ ์œผ๋กœ ๋Œ€์ฒด

6. ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€

๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ํŠธ๋žœ์žญ์…˜ ๊ฐ„์˜ ๊ฒฉ๋ฆฌ ์ •๋„๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์ข…๋ฅ˜

์ˆ˜์ค€ Dirty Read Non-repeatable Read Phantom Read
READ UNCOMMITTED ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ
READ COMMITTED ๋ฐฉ์ง€ ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ
REPEATABLE READ ๋ฐฉ์ง€ ๋ฐฉ์ง€ ๊ฐ€๋Šฅ*
SERIALIZABLE ๋ฐฉ์ง€ ๋ฐฉ์ง€ ๋ฐฉ์ง€

*PostgreSQL์˜ REPEATABLE READ๋Š” Phantom Read๋„ ๋ฐฉ์ง€

PostgreSQL ๊ธฐ๋ณธ๊ฐ’

PostgreSQL์˜ ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์€ READ COMMITTED์ž…๋‹ˆ๋‹ค.

๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์„ค์ •

-- ํŠธ๋žœ์žญ์…˜๋ณ„ ์„ค์ •
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- ๋˜๋Š”
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- ์„ธ์…˜ ์ „์ฒด ์„ค์ •
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- ํ˜„์žฌ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ํ™•์ธ
SHOW transaction_isolation;

7. ๋™์‹œ์„ฑ ๋ฌธ์ œ

Dirty Read (๋”ํ‹ฐ ๋ฆฌ๋“œ)

์ปค๋ฐ‹๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ๋ฌธ์ œ โ†’ PostgreSQL์—์„œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

Non-repeatable Read (๋น„๋ฐ˜๋ณต ์ฝ๊ธฐ)

๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‘ ๋ฒˆ ์ฝ์—ˆ์„ ๋•Œ ๋‹ค๋ฅธ ๊ฐ’์ด ๋‚˜์˜ค๋Š” ๋ฌธ์ œ

-- ํŠธ๋žœ์žญ์…˜ A
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 1000000

-- ํŠธ๋žœ์žญ์…˜ B๊ฐ€ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์ปค๋ฐ‹
-- UPDATE accounts SET balance = 900000 WHERE id = 1; COMMIT;

SELECT balance FROM accounts WHERE id = 1;  -- 900000 (๋‹ค๋ฅธ ๊ฐ’!)
COMMIT;

Phantom Read (ํŒฌํ…€ ๋ฆฌ๋“œ)

๊ฐ™์€ ์กฐ๊ฑด์œผ๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ํ–‰์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ

-- ํŠธ๋žœ์žญ์…˜ A
BEGIN;
SELECT COUNT(*) FROM accounts WHERE balance > 500000;  -- 2๊ฐœ

-- ํŠธ๋žœ์žญ์…˜ B๊ฐ€ ์ƒˆ ํ–‰ ์‚ฝ์ž…ํ•˜๊ณ  ์ปค๋ฐ‹
-- INSERT INTO accounts VALUES (...); COMMIT;

SELECT COUNT(*) FROM accounts WHERE balance > 500000;  -- 3๊ฐœ (์œ ๋ น ํ–‰!)
COMMIT;

8. ๊ฒฉ๋ฆฌ ์ˆ˜์ค€๋ณ„ ๋™์ž‘

READ COMMITTED (๊ธฐ๋ณธ)

BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ฆ‰์‹œ ๋ณผ ์ˆ˜ ์žˆ์Œ
SELECT * FROM accounts;  -- ์ตœ์‹  ์ปค๋ฐ‹๋œ ๋ฐ์ดํ„ฐ

COMMIT;

REPEATABLE READ

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ์‹œ์ ์˜ ์Šค๋ƒ…์ƒท์„ ๋ด„
SELECT * FROM accounts;

-- ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹ํ•ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ
SELECT * FROM accounts;  -- ๋™์ผ

COMMIT;

SERIALIZABLE

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- ๊ฐ€์žฅ ์—„๊ฒฉํ•œ ๊ฒฉ๋ฆฌ
-- ์ง๋ ฌํ™” ์ถฉ๋Œ ์‹œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
SELECT * FROM accounts WHERE balance > 500000;
UPDATE accounts SET balance = balance + 10000 WHERE id = 1;

COMMIT;
-- ERROR: could not serialize access due to concurrent update
-- (๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜๊ณผ ์ถฉ๋Œ ์‹œ)

9. ์ž ๊ธˆ (Locking)

ํ–‰ ์ˆ˜์ค€ ์ž ๊ธˆ

-- SELECT FOR UPDATE: ์กฐํšŒํ•˜๋ฉด์„œ ์ž ๊ธˆ
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์€ ์ด ํ–‰์„ ์ˆ˜์ •/์‚ญ์ œ ๋ถˆ๊ฐ€

UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
COMMIT;

-- SELECT FOR SHARE: ๊ณต์œ  ์ž ๊ธˆ (์ฝ๊ธฐ๋Š” ํ—ˆ์šฉ, ์“ฐ๊ธฐ ๋ถˆ๊ฐ€)
SELECT * FROM accounts WHERE id = 1 FOR SHARE;

์ž ๊ธˆ ์˜ต์…˜

-- ๋Œ€๊ธฐํ•˜์ง€ ์•Š๊ณ  ์‹คํŒจ
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;

-- ์ง€์ •๋œ ์‹œ๊ฐ„๋งŒ ๋Œ€๊ธฐ
SELECT * FROM accounts WHERE id = 1 FOR UPDATE SKIP LOCKED;

ํ…Œ์ด๋ธ” ์ˆ˜์ค€ ์ž ๊ธˆ

-- ๋ช…์‹œ์  ํ…Œ์ด๋ธ” ์ž ๊ธˆ (๋“œ๋ฌผ๊ฒŒ ์‚ฌ์šฉ)
LOCK TABLE accounts IN EXCLUSIVE MODE;

10. ๊ต์ฐฉ์ƒํƒœ (Deadlock)

๋‘ ํŠธ๋žœ์žญ์…˜์ด ์„œ๋กœ์˜ ์ž ๊ธˆ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํƒœ

-- ํŠธ๋žœ์žญ์…˜ A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- id=1 ์ž ๊ธˆ

-- ํŠธ๋žœ์žญ์…˜ B
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
-- id=2 ์ž ๊ธˆ

-- ํŠธ๋žœ์žญ์…˜ A
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- id=2 ๋Œ€๊ธฐ...

-- ํŠธ๋žœ์žญ์…˜ B
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
-- id=1 ๋Œ€๊ธฐ... โ†’ ๊ต์ฐฉ์ƒํƒœ!

-- PostgreSQL์ด ์ž๋™์œผ๋กœ ํ•œ ํŠธ๋žœ์žญ์…˜์„ ์ค‘๋‹จ์‹œํ‚ด
-- ERROR: deadlock detected

๊ต์ฐฉ์ƒํƒœ ๋ฐฉ์ง€

-- ํ•ญ์ƒ ๊ฐ™์€ ์ˆœ์„œ๋กœ ์ž ๊ธˆ ํš๋“
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- ํ•ญ์ƒ ์ž‘์€ id ๋จผ์ €
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

11. ์‹ค์Šต ์˜ˆ์ œ

์‹ค์Šต 1: ๊ธฐ๋ณธ ํŠธ๋žœ์žญ์…˜

-- ๊ณ„์ขŒ ์ด์ฒด
CREATE OR REPLACE PROCEDURE transfer(
    from_id INTEGER,
    to_id INTEGER,
    amount NUMERIC
)
AS $$
BEGIN
    -- ์ถœ๊ธˆ
    UPDATE accounts SET balance = balance - amount WHERE id = from_id;

    -- ์ž”์•ก ํ™•์ธ
    IF (SELECT balance FROM accounts WHERE id = from_id) < 0 THEN
        RAISE EXCEPTION '์ž”์•ก ๋ถ€์กฑ';
    END IF;

    -- ์ž…๊ธˆ
    UPDATE accounts SET balance = balance + amount WHERE id = to_id;

    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
$$ LANGUAGE plpgsql;

-- ์‚ฌ์šฉ
CALL transfer(1, 2, 100000);

์‹ค์Šต 2: SAVEPOINT ํ™œ์šฉ

BEGIN;

-- ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
INSERT INTO orders (user_id, amount) VALUES (1, 50000);
SAVEPOINT order_created;

-- ์žฌ๊ณ  ์ฐจ๊ฐ ์‹œ๋„
UPDATE products SET stock = stock - 1 WHERE id = 10;

-- ์žฌ๊ณ  ํ™•์ธ
IF (SELECT stock FROM products WHERE id = 10) < 0 THEN
    ROLLBACK TO SAVEPOINT order_created;
    -- ์ฃผ๋ฌธ์€ ์œ ์ง€ํ•˜๋˜ ์žฌ๊ณ  ์ฐจ๊ฐ๋งŒ ์ทจ์†Œ
END IF;

COMMIT;

์‹ค์Šต 3: ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ํ…Œ์ŠคํŠธ

ํ„ฐ๋ฏธ๋„ 2๊ฐœ๋ฅผ ์—ด์–ด ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

-- ํ„ฐ๋ฏธ๋„ 1
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM accounts;

-- ํ„ฐ๋ฏธ๋„ 2
UPDATE accounts SET balance = balance + 50000 WHERE id = 1;
COMMIT;

-- ํ„ฐ๋ฏธ๋„ 1
SELECT * FROM accounts;  -- ๋ณ€๊ฒฝ ์ „ ๊ฐ’ (์Šค๋ƒ…์ƒท)
COMMIT;

SELECT * FROM accounts;  -- ์ด์ œ ๋ณ€๊ฒฝ๋œ ๊ฐ’ ๋ณด์ž„

์‹ค์Šต 4: FOR UPDATE ์ž ๊ธˆ

-- ์žฌ๊ณ  ํ™•์ธ ํ›„ ์ฐจ๊ฐ (๋™์‹œ์„ฑ ์•ˆ์ „)
BEGIN;

-- ์ž ๊ธˆ์„ ๊ฑธ๋ฉฐ ์กฐํšŒ
SELECT stock FROM products WHERE id = 1 FOR UPDATE;

-- ์žฌ๊ณ  ํ™•์ธ ๋ฐ ์ฐจ๊ฐ
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0;

COMMIT;

12. ํŠธ๋žœ์žญ์…˜ ๋ชจ๋‹ˆํ„ฐ๋ง

-- ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ํŠธ๋žœ์žญ์…˜ ํ™•์ธ
SELECT
    pid,
    now() - xact_start AS duration,
    query,
    state
FROM pg_stat_activity
WHERE xact_start IS NOT NULL;

-- ์ž ๊ธˆ ๋Œ€๊ธฐ ์ค‘์ธ ์ฟผ๋ฆฌ ํ™•์ธ
SELECT
    blocked.pid AS blocked_pid,
    blocking.pid AS blocking_pid,
    blocked.query AS blocked_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid));

๋‹ค์Œ ๋‹จ๊ณ„

12_Triggers.md์—์„œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค!

to navigate between lessons