
์ค์ ๋ก SQL ์ฌ๋ฌ ๊ฐ๋ฅผ ๋ฌถ์ด์ ์คํํด๋ณด๊ณ , ์ค๊ฐ์ ์ค๋ฅ๊ฐ ๋๋ฉด ์ ๋ถ ๋๋๋ฆฌ๋ ๊ฑธ ๊ฒฝํํด๋ณด๋๊น ์ ๊ทธ๋ฐ ๋ง์ด ๋์ค๋์ง ์ดํด๊ฐ ๋์ด์. ์ค์ต์ ๋ฐ๋ผ๊ฐ๋ฉด์ rollback๊ณผ commit์ด ์ด๋ค ์ํฉ์์ ํ์ํ์ง๋ ํ์คํ ์๊ฒ ๋์๊ณ ์. '์~ ์ด๋์ ์ค๋ฌด์์๋ ๊ผญ ์ฐ๋๊ตฌ๋!' ์ถ์ ์๊ฐ์ด ์์์ด์.
ํธ๋์ญ์ (Transaction)์ ํ๋์ ๋ ผ๋ฆฌ์ ์ธ ์์ ๋จ์๋ก, ์ฌ๋ฌ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ด ๋ชจ๋ ์ฑ๊ณตํ๊ฑฐ๋ ๋ชจ๋ ์คํจํด์ผ๋ง ํ๋ ๊ทธ๋ฃน์ ๋งํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๊ณ์ข ์ด์ฒด๋ฅผ ์๊ฐํด๋ด ์๋ค. A ๊ณ์ข์์ 1๋ง ์์ ๋นผ๊ณ B ๊ณ์ข์ 1๋ง ์์ ๋ฃ๋ ๋ ์์ ์ด ํจ๊ป ์คํ๋ผ์ผ ์๋ฏธ๊ฐ ์์ต๋๋ค. ํ์ชฝ๋ง ์ฒ๋ฆฌ๋๋ฉด ๋ฐ์ดํฐ๋ ํ์ด์ง๊ฒ ๋์ฃ . ์ด์ฒ๋ผ ์ฌ๋ฌ ์์ ์ ํ๋๋ก ๋ฌถ๊ณ ์์์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋จ์๊ฐ ๋ฐ๋ก ํธ๋์ญ์ ์ ๋๋ค.
๐ ๋น์ ํ์๋ฉด, ํธ๋์ญ์ ์ '๊ฒฐ์ ํ๊ธฐ ์ ์ฅ๋ฐ๊ตฌ๋'์ ๊ฐ์ต๋๋ค. ๊ฒฐ์ ๋ฒํผ์ ๋๋ฅด๊ธฐ ์ ๊น์ง ์ด๋ค ์ํ๋ ์ค์ ๊ตฌ๋งค๋์ง ์์์. ํ์ง๋ง ๊ฒฐ์ ๋ฅผ ๋๋ฅด๋ ์๊ฐ, ๋ชจ๋ ์ํ์ด ํ๊บผ๋ฒ์ ๊ฒฐ์ ๋์ฃ . ํ๋๋ผ๋ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ ์ฒด ๊ฒฐ์ ๋ ์ทจ์๋ฉ๋๋ค.
ํธ๋์ญ์ ์ด ๊ฐ์ถ์ด์ผ ํ ๋ค ๊ฐ์ง ์ฑ์ง์ ACID๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
| ์ฝ์ด | ์ด๋ฆ | ์ค๋ช |
|---|---|---|
| A | Atomicity (์์์ฑ) | ๋ชจ๋ ์คํ๋๊ฑฐ๋, ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด ์ทจ์๋จ |
| C | Consistency (์ผ๊ด์ฑ) | ํธ๋์ญ์ ์ ํ์ ๋ฐ์ดํฐ๋ ํญ์ ์ผ๊ด์ฑ์ ์ ์งํจ |
| I | Isolation (๊ฒฉ๋ฆฌ์ฑ) | ๋์์ ์คํ๋๋ ํธ๋์ญ์ ๊ฐ ๊ฐ์ญ์ด ์์ด์ผ ํจ |
| D | Durability (์ง์์ฑ) | ํธ๋์ญ์ ์๋ฃ ํ ๋ฐ์ดํฐ๋ ์๊ตฌ์ ์ผ๋ก ๋ฐ์๋จ |
๐ Atomicity โ ํ ๋ชธ์ฒ๋ผ ์์ง์ด๋ ์์ !
๐ Consistency โ ๊ท์น์ด ๊นจ์ง์ง ์๋๋ก ๋ณด์ฅ!
๐ Isolation โ ๋ด ์์ ์ ๋ฐฉํด๋ฐ์ง ์๋๋ก!
๐ Durability โ ์ ์์ด ๋๊ฐ๋ ๊ฒฐ๊ณผ๋ ๋จ์์์ด์ผํด!
BEGIN; -- ํธ๋์ญ์
์์
-- A ๊ณ์ข์์ 1๋ง ์ ์ฐจ๊ฐ
UPDATE Account SET balance = balance - 10000 WHERE id = 1;
-- B ๊ณ์ข์ 1๋ง ์ ์ถ๊ฐ
UPDATE Account SET balance = balance + 10000 WHERE id = 2;
COMMIT; -- ํธ๋์ญ์
์ฑ๊ณต ์ ํ์ ์ ์ฅ
์ ์์๋ A โ B๋ก 1๋ง ์์ ์ด์ฒดํ๋ ์ํฉ์ ๋๋ค. ์ด ๋ ์ฟผ๋ฆฌ๋ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ์ผ ํฉ๋๋ค. ๋ ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด๋ฅผ ROLLBACK ํด์ผ ๋ฐ์ดํฐ ๋ถ์ผ์น๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
๐งพ ๊ณ์ฝ์์ ์์ชฝ ๋์ฅ์ด ๋ชจ๋ ์ฐํ์ผ ๊ณ์ฝ์ด ์ฑ๋ฆฝ๋๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class TransactionExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/testdb";
String user = "root";
String pw = "password";
Connection conn = null;
try {
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ
conn = DriverManager.getConnection(url, user, pw);
// ์๋ ์ปค๋ฐ์ ๊บผ์ ์๋ ํธ๋์ญ์
์์
conn.setAutoCommit(false);
// A ๊ณ์ข์์ 10000์ ์ฐจ๊ฐ
String sql1 = "UPDATE Account SET balance = balance - 10000 WHERE id = 1";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.executeUpdate();
// B ๊ณ์ข์ 10000์ ์ถ๊ฐ
String sql2 = "UPDATE Account SET balance = balance + 10000 WHERE id = 2";
PreparedStatement pstmt2 = conn.prepareStatement(sql2);
pstmt2.executeUpdate();
// ๋ชจ๋ ์์
์ด ๋ฌธ์ ์์ด ๋๋๋ฉด ์ปค๋ฐ
conn.commit();
System.out.println("ํธ๋์ญ์
์ฑ๊ณต: ์ปค๋ฐ ์๋ฃ");
} catch (Exception e) {
try {
// ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๋กค๋ฐฑ
if (conn != null) {
conn.rollback();
System.out.println("์ค๋ฅ ๋ฐ์: ๋กค๋ฐฑ ์ฒ๋ฆฌ๋จ");
}
} catch (Exception rollbackEx) {
rollbackEx.printStackTrace();
}
} finally {
try {
if (conn != null) conn.close(); // ์ฐ๊ฒฐ ์ข
๋ฃ
} catch (Exception closeEx) {
closeEx.printStackTrace();
}
}
}
}
JDBC์์๋ setAutoCommit(false)๋ฅผ ํธ์ถํด ์๋ ํธ๋์ญ์
์ ์์ํฉ๋๋ค.
๋ชจ๋ ์์
์ด ์ฑ๊ณตํ๋ฉด commit(), ์คํจํ๋ฉด rollback()์ ํธ์ถํด ๋ณต๊ตฌํฉ๋๋ค.
โ๏ธ ์ง์ ์ ์ฅ ๋ฒํผ์ ๋๋ฌ์ผ ์ ์ฅ๋๋ ์๋ํฐ์ฒ๋ผ, ํธ๋์ญ์ ๋ ์๋์ผ๋ก ์๋ฃ์์ผ์ผ ๋ฐ์๋ฉ๋๋ค.
๐งฉ Spring์์๋
@Transactional์ ์ฌ์ฉํ๋ฉด ๋์ฑ ๊น๋ํ๊ฒ ํธ๋์ญ์ ์ ์ ์ดํ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ ๊ฒฉ๋ฆฌ ์์ค(Isolation Level) ์ค์ ์ ํตํด ์ ์ดํ ์ ์์ต๋๋ค.
| ์์ค | ์ค๋ช | ๋ง์ ์ ์๋ ํ์ |
|---|---|---|
| READ UNCOMMITTED | ๊ฐ์ฅ ๋ฎ์ ์์ค. Dirty Read ํ์ฉ | ์์ |
| READ COMMITTED | ์ปค๋ฐ๋ ๋ฐ์ดํฐ๋ง ์ฝ์ | Dirty Read โ |
| REPEATABLE READ | ๊ฐ์ ํ์ ๋ํ ์กฐํ ์ผ๊ด์ฑ ๋ณด์ฅ | Non-repeatable Read โ |
| SERIALIZABLE | ๊ฐ์ฅ ๋์ ๊ฒฉ๋ฆฌ, ์ฑ๋ฅ ์ ํ | Phantom Read โ |
โ ๏ธ ๊ฒฉ๋ฆฌ ์์ค์ด ๋์์๋ก ์์ ํ์ง๋ง ์ฑ๋ฅ์ ๋ฎ์์ง๋๋ค. ์ํฉ์ ๋ง๋ ์ค์ ์ด ์ค์ํฉ๋๋ค!
@Transactional๋ก ์ ์ธ์ ์ฒ๋ฆฌ ๊ฐ๋ฅ์ฒ์์ commit, rollback ๊ฐ์ ๊ฒ๋ ๋ฏ์ค๊ณ ์ด๋ ต๊ฒ ๋๊ปด์ก์ง๋ง, ์๋ฌ๋ฅผ ์ผ๋ถ๋ฌ ๋ง๋ค์ด๋ณด๋ฉด์
'์ ํ์ํ์ง'๋ฅผ ๋ชธ์ผ๋ก ๋๋ ์ ์์๋ค.
์ค๊ฐ์ ์ฟผ๋ฆฌ ํ๋๊ฐ ์คํจํ๋ฉด ๋๋จธ์ง๋ ํจ๊ป ๋๋๋ ค์ผ ํ๋ค๋ ๊ฒ ์ค์ ๋ก ์๋ํ๋๊น ์ ๊ธฐํ๋ค.
์์ง ๊ฒฉ๋ฆฌ ์์ค ๊ฐ์ ๊ฑด ์กฐ๊ธ ํท๊ฐ๋ฆฌ์ง๋ง, ํ๋์ฉ ํ
์คํธํด๋ณด๋๊น '๊ทธ๋์ ์ด๋ฐ ๊ธฐ๋ฅ์ด ํ์ํ๊ตฌ๋' ์ถ์ด์ ์ฌ๋ฏธ์์๋ค. (๋ฌผ๋ก ์ง๊ธ๋ ํท๊ฐ๋ฆฌ๋ ๊ฑด ๋ง์์!)