[MySQL] InnoDB์์ PHANTOM READ ๋ฅผ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ.
MySQL์ REPEATBLE READ ๊ฒฉ๋ฆฌ ์์ค์์๋ PHANTOM READ ํ์์ด ๋ฐ์ํ์ง ์๋๋ค๊ณ ๋ง์ด ๋ณธ๊ฑฐ ๊ฐ์๋ฐ ๊ณผ์ฐ ์ง์ง์ผ๊น? ๊ทธ๋ฐ๋ฐ PHANTOM READ๊ฐ ๋ฌด์์ด๊ธธ๋ ์ค์ํ๊ฒ ๋ค๋ฃจ๋ ๊ฒ์ผ๊น?
REPEATABLE READ ์ด๋?
REPEATABLE READ ๋ ๋์ผ ํธ๋์ ์ ๋ด์์ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฅํ๋๋ก ํ๋ ๊ฒฉ๋ฆฌ ์์ค์ ๋ปํฉ๋๋ค. MySql์ InnoDB ์คํ ๋ฆฌ์ง ์์ง์์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒฉ๋ฆฌ ์์ค์ธ๋ฐ, ์ด๋ป๊ฒ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฅํด์ค ์ ์๋ ๊ฑธ๊น์? ์ด๋ MVCC(Multi Version Concurrency Control, ๋ค์ค ๋ฒ์ ๋์์ฑ ์ ์ด)์ ๋ฉ์ปค๋์ฆ์ ์ด์ฉํด ์ด์ ๋ฒ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐฑ์ ํด๋๊ณ ์ค์ ๋ ์ฝ๋ ๊ฐ์ ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ณด์ฅํ ์ ์์ต๋๋ค.
MVCC: ๊ธฐ์กด์ ๋ฐ์ดํฐ๋ฅผ ๋ฎ์ด์์ฐ๋ ๊ฒ์ด ์๋ ๊ธฐ์กด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์ด์ ๋ฒ์ ์ ๋ฐ์ดํฐ์ ๋น๊ตํ์ฌ ๋ณ๊ฒฝ๋ ๋ด์ฉ์ ๊ธฐ๋กํ๋ ๋ฐฉ๋ฒ์ผ๋ก, ์ฌ๋ฌ ๋ฒ์ ์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๊ฒ ๋๊ฑฐ๋ ์ฌ์ฉ์๋ ๋ง์ง๋ง ๋ฒ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๋ฉ๋๋ค.
์์ ์ ํจ๊ป InnoDB์ PHANTOM READ ๋ฌธ์
- Transaction 1
1. ์ฒซ๋ฒ์งธ ์กฐํ:
→ ๊ฒฐ๊ณผ: 2๊ฐ์ ํ ๋ฐํSELECT * FROM Employees WHERE Gender = 'Male';
2. ๋ค์ ๋์ผํ ์ฟผ๋ฆฌ ์คํ:
3. ์ค๊ฐ ์์ ์ํ (Transaction 1์ ์์ง ๋๋์ง ์์์)SELECT * FROM Employees WHERE Gender = 'Male';
→ ๊ฒฐ๊ณผ: 3๊ฐ์ ํ์ ๋ฐํํ๋ค. - Transaction 2
1.์ค๊ฐ์ INSERT ์ฃผ์
2. ๊ทธ๋ฆฌ๊ณ ์ปค๋ฐ!INSERT INTO Employees (Name, Gender, Age) VALUES ('DOHYUN', 'Male', 20);
⇒ ๊ฒฐ๊ณผ
Transaction 1์ ๋์ผํ ์ฟผ๋ฆฌ๋ฅผ 2๋ฒ ์คํํ์์ ๋, ์ฒซ ๋ฒ์จฐ ์ฟผ๋ฆฌ์์๋ 2๋ช ์ ์ง์์ ๋ฐํ ํ์์ง๋ง ๋ ๋ฒ์งธ ์ฟผ๋ฆฌ์์ 3๋ช ์ ์ง์์ ๋ฐ๋ก ๋ฐํํ๊ณ ์์ต๋๋ค. ์ด๋ฌํ ํ์์ด ๋ํ๋๋ ๊ฒ์ PHANTOM READ๋ผ๊ณ ํฉ๋๋ค.
PHANTOM READ๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ์ด ์ค์ํ ์ด์ !
๊ทธ๋ฌ๋ฉด ์ด๋ฐ PHANTOM READ๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ์ด ์ ๊ทธ๋ ๊ฒ ์ค์ํ๋ค๊ณ ํ๋ ๊ฒ์ผ๊น์? PHANTOM READ๊ฐ ๋จ์ํ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์ ๋ณํ์ฒ๋ผ ๋ณด์ผ ์ ์์ง๋ง, ์ค์ ๋ก๋ ๋น์ฆ๋์ค ๋ก์ง์ ํฐ ์ํฅ์ ๋ฏธ์น๋ ๋์์ฑ ๋ฒ๊ทธ๋ฅผ ์ด๋ํ ์ ์์ต๋๋ค. ๋ค์์ ํฌํ ๋ฆฌ๋๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ์ด ์ค์ํ ์ด์ ๋ฅผ ์์๋ด ์๋ค.
1. ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ ํฉ์ฑ(Data Integrity)
๋ฌธ์ : ํน์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ๋ ์ฝ๋ ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ค์ํ ํ๋จ์ ๋ด๋ฆฌ๋ ๊ฒฝ์ฐ, PHANTOM READ๋ก ์ธํด ๊ฒฐ๊ณผ๊ฐ ๋ฐ๋๋ฉด ์๋ชป๋ ๋น์ฆ๋์ค ๋ก์ง์ด ์คํ๋ ์ ์์
⇒ ์์: "๋จ์ฑ ์ง์์ด 2๋ช ์ดํ์ผ ๋๋ง ์ ๊ท ์ฑ์ฉ ํ์ฉ"์ด๋ผ๋ ์กฐ๊ฑด์ด ์๋ ๊ฒฝ์ฐ, ํฌํ ๋ฆฌ๋๋ก ์ธํด ์ค์ ๋ก๋ 3๋ช ์ธ๋ฐ๋ ๋ถ๊ตฌํ๊ณ 2๋ช ์ด๋ผ๊ณ ํ๋จํด ์ค๋ณต ์ฑ์ฉ์ด ๋ฐ์ํ ์ ์์.
2. ํธ๋์ญ์ ์ผ๊ด์ฑ(Consistency)์ ๋ถ๊ดด
๋ฌธ์ : ํธ๋์ญ์ ์ ๊ฐ์ฅ ํต์ฌ์ ์ธ ๋ชฉ์ ์ค ํ๋๋ ๋์ผํ ํธ๋์ญ์ ๋ด์์๋ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ๋ณธ๋ค๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง PHANTOM READ๊ฐ ๋ฐ์ํ๋ฉด, ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฒ ํ๋๋ฐ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ ์ผ๊ด์ฑ์ด ๊นจ์ง๊ฒ ๋ฉ๋๋ค(๋ ธ์ฐ! )
⇒ ๊ฒฐ๊ณผ: ์ด๋ ๋น์ฆ๋์ค ์ ์ฑ ์๋ฐ, ๋ ผ๋ฆฌ์ ์ค๋ฅ, ๋๋ ์์ธก ๋ถ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ก ์ด์ด์ง ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
3. ๋ค์ค ํธ๋์ญ์ ํ๊ฒฝ์์ ์์ธก ๋ถ๊ฐ๋ฅํ ์ํ ๋ฐ์
๋ฌธ์ : PHANTOM READ๋ ์ฌ๋ฌ ํธ๋์ญ์ ์ด ๋์์ ์คํ๋๋ ํ๊ฒฝ์์๋ง ๋ฐ์ํฉ๋๋ค. ๋ฐ๋ผ์ PHANTOM READ๋ฅผ ํ์ฉํ๋ฉด, ํธ๋์ญ์ ๊ฐ ๊ฐ์ญ์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ฉฐ ํ ์คํธ๋ก ๋ฐ๊ฒฌํ๊ธฐ ์ด๋ ต๊ณ ์ฌํ์ด ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
⇒ ๊ฒฐ๊ณผ: ์ค์ ์ด์ ํ๊ฒฝ์์๋ง ๋ฐ์ํ๋ ๊ฐํ์ ์ค๋ฅ(bug), ๋ฐ์ดํฐ ๋ถ์ผ์น ๋ฑ์ผ๋ก ์ด์ด์ง๋๋ค.
์ฆ! ํฌํ ๋ฆฌ๋๋ฅผ ๋ฐฉ์งํ์ง ์์ผ๋ฉด, ํธ๋์ญ์ ๋ด ๋ ผ๋ฆฌ ์ค๋ฅ์ ๋น์ฆ๋์ค ์ ์ฑ ์๋ฐ์ด ๋ฐ์ํ ์ ์์ผ๋ฉฐ, ํนํ ์ค์๊ฐ ์ฒ๋ฆฌ ์์คํ ์์๋ ์ฌ๊ฐํ ์ด์ ์ฅ์ ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
PHANTOM READ ๋ฐฉ์ง ๋ฐฉ๋ฒ 1 - GAP LOCK(๊ฐญ๋ฝ)
GAP LOCK(๊ฐญ๋ฝ)์ ํน์ ์ธ๋ฑ์ค ๊ฐ ์ฌ์ด์ ๊ณต๊ฐ์ ์ ๊ทธ๋ ๋ฝ์ ๋๋ค. ๊ธฐ์กด ๋ ์ฝ๋ ๊ฐ์ ๊ฐ๊ฒฉ์ ๋ณดํธํ์ฌ ์๋ก์ด ๋ ์ฝ๋์ ์ฝ์ ์ ๋ฐฉ์งํฉ๋๋ค. ๊ฐญ ๋ฝ์ ๋ฒ์ ๋ด์ ํน์ ๋ ์ฝ๋๊ฐ ์กด์ฌํ์ง ์์ ๋ ์ ์ฉ๋ฉ๋๋ค. ํธ๋์ญ์ ์ด ํน์ ๋ฒ์ ๋ด์์ ๋ฐ์ดํฐ์ ์ฝ์ ์ ๋ง์ ํฌํ ์ฝ๊ธฐ(Phantom Read) ํ์์ ๋ฐฉ์งํฉ๋๋ค.
- ์์
-- Transaction 1 (REPEATABLE READ ๊ฒฉ๋ฆฌ ์์ค)
START TRANSACTION;
SELECT * FROM Employees WHERE Age BETWEEN 20 AND 30 FOR UPDATE;
-- → ์ด ์ฟผ๋ฆฌ๋ (20, 30) ๋ฒ์์ Gap Lock์ ๊ฑด๋ค
-- Transaction 2 (๋์์ ์คํ)
START TRANSACTION;
INSERT INTO Employees (Name, Gender, Age) VALUES ('DOHYUN', 'Male', 25);
-- Gap Lock ๋๋ฌธ์ ์ด ์ฝ์
์ ๋๊ธฐ(block) ๋๋ ์คํจ
-- Transaction 1์ด COMMIT ํด์ผ Transaction 2๊ฐ ์งํ ๊ฐ๋ฅ
⇒ ๊ฒฐ๊ณผ
1. ๋ค๋ฅธ ํธ๋์ญ์ ์ id=25 ๊ฐ์ ๊ฐ์ INSERTํ ์ ์์ต๋๋ค.
2. ๊ธฐ์กด row์๋ ๋ฝ์ด ๊ฑธ๋ฆฌ์ง ์์ง๋ง, ์ค๊ฐ gap์๋ ๋ฝ์ด ๊ฑธ๋ฆฝ๋๋ค.
PHANTOM READ ๋ฐฉ์ง ๋ฐฉ๋ฒ 2 - Next-Key Lock(๋ฅ์คํธํค ๋ฝ).
์คํธํค ๋ฝ์ ๋ ์ฝ๋ ๋ฝ๊ณผ ๊ฐญ๋ฝ์ ๊ฒฐํฉํ ํํ๋ก, ํน์ ์ธ๋ฑ์ค ๋ ์ฝ๋์ ๊ทธ ์ฃผ๋ณ์ ๊ฐญ์ ๋์์ ์ ๊ทธ๋ ๋ฝ์ ๋๋ค. ์ด๋ฅผ ํตํด ๋ ์ฝ๋ ์์ฒด์ ๋ณ๊ฒฝ๊ณผ ํจ๊ป ๊ทธ ์ฃผ๋ณ ๊ณต๊ฐ์ ๋ณ๊ฒฝ๋ ๋์์ ์ ์ดํ ์ ์์ต๋๋ค.
๋ฅ์คํธํค ๋ฝ์ ํน์ ๋ ์ฝ๋์ ๊ทธ ์ฃผ๋ณ ๊ณต๊ฐ์ ์ ๊ทธ๊ธฐ ๋๋ฌธ์, ๋ค๋ฅธ ํธ๋์ญ์ ์ด ์๋ก์ด ๋ ์ฝ๋๋ฅผ ์ฝ์ ํ์ฌ ํฌํ ๋ฆฌ๋๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- ์์
-- ํ์ฌ Employees ํ
์ด๋ธ ์ํ:
-- id: 10, 20, 30 ์ด ์กด์ฌ
-- Transaction 1
START TRANSACTION;
SELECT * FROM Employees WHERE id > 10 FOR UPDATE;
-- ์ด ์ฟผ๋ฆฌ๋ ๋ค์ ์์ญ์ Next-Key Lock์ ๊ฑด๋ค:
-- (10,20], (20,30], (30,∞)
-- ์ฆ, id=15๋ id=25 ๊ฐ์ ์ฝ์
์ ๋ชจ๋ ์ฐจ๋จ๋จ
-- Transaction 2
START TRANSACTION;
INSERT INTO Employees (id, Name, Gender, Age) VALUES (25, 'DOHYUN', 'Male', 20);
-- ์ next-key ๋ฝ์ ์ํด ์ฝ์
๋ถ๊ฐ ๋๋ ๋๊ธฐ
⇒ ๊ฒฐ๊ณผ
1. id > 10 ๋ฒ์์ ํฌํจ๋๋ ๋ชจ๋ ๊ตฌ๊ฐ๊ณผ ๋ ์ฝ๋์ ๋ฝ์ด ๊ฑธ๋ฆฌ๊ณ
2. ๊ธฐ์กด ๋ ์ฝ๋ + ์ฌ์ด gap ๋ชจ๋ ๋ณดํธ → ๋งค์ฐ ๊ฐ๋ ฅํ ํฌํ ๋ฆฌ๋ ๋ฐฉ์งํฉ๋๋ค.
๋ง๋ฌด๋ฆฌ
์ด๋ฌํ ๋ฐฉ๋ฒ๋ค์ ์์๋ณด๋ฉฐ InnoDB์์ REPEATABLE READ ์ํ์ผ ๋, PHANTOM READ๋ฅผ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์์ต๋๋ค. ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ ๋งค์ปค๋์ฆ์ ๋ง๋๋ ๊ฐ๋ฐ์๊ฐ ๋์๊ธธ ๋ฐ๋๋๋ค.
์ธ์ฉ:
https://dotnettutorials.net/lesson/phantom-read-concurrency-problem-sql-server/
https://parkmuhyeun.github.io/woowacourse/2023-11-28-Repeatable-Read/