트랜잭션 : 논리적인 작업 단위
하나의 작업을 수행하기 위해 더 이상 쪼갤 수 없는 작업의 단위
트랜잭션이란 위에서 말한 대로 논리적인 작업의 단위입니다.
논리적인 작업이라 하면 "은행에서 다른 사람에게 돈을 보내는 행위" 와 같이 하나의 행동이라고 생각할 수 있겠습니다.
자, 은행에서 다른 사람에게 돈을 보내는 상황을 예시로 들어보겠습니다.
A의 잔고는 100,000원, B는 50,000원의 잔고가 있습니다.
A는 B에게 50,000원을 보내주려고 합니다.
우리는 A의 통장에 50,000원이 남고, B의 통장에 100,000원이 남는 상황을 기대합니다.
다음과 같은 메서드가 실행될 겁니다.
public void send(String sender, String receiver, int money) {
계좌 보내는_사람_계좌 = repository.findByUsername(sender);
계좌 받는_사람_계좌 = repository.findByUsername(receiver);
보내는_사람_계좌.출금한다(money);
받는_사람_계좌.입금한다(money);
}
만약 보내는_사람_계좌.출금한다(money)
가 실행된 상태에서 서버에서 오류가 발생합니다.
그러면 어떤 상태가 될까요?
A의 잔고는 50,000원이 되었는데 B의 잔고는 갱신되지 못하고 50,000원 그대로 남아있을 겁니다.
50,000원이 증발했습니다. 실제 은행 서비스라면 대형사고입니다.
우리가 일반적으로 예상하는 상황은 두 가지입니다.
다시 말해 "은행에서 다른 사람에게 돈을 보내는 행위" 는 성공하거나, 혹은 실패해야 합니다. 어중간하게 끝나서는 안돼요.
바로 이 개념이 논리적인 작업 단위, 트랜잭션입니다. 트랜잭션이 성공하거나 실패한다는 건 "은행에서 다른 사람에게 돈을 보내는 행위"가 성공하거나, 혹은 실패한 상황입니다.
DB에서 트랜잭션을 지원한다면 ACID라고 불리는 속성들을 만족해야 합니다.
Atomatic, Consistency, Isolation, Durability 의 약어인데요. 하나씩 살펴보도록 하겠습니다.
트랜잭션은 모두 성공한 상태 혹은 모두 실패한 상태여야 한다는 의미입니다. 더 이상 쪼갤 수 없는 atomic하다는 의미인데요. 해당 속성을 지키기 위해 MySQL은 다음과 같이 동작합니다.
START TRANSACTION;
...
SELECT, UPDATE 등 여러 쿼리들이 동작합니다
...
// 둘 중 하나가 실행되어야 합니다.
COMMIT; // 성공해서 DB에 반영하겠다
ROLLBACK; // 실패했으니 원래 상태로 돌리겠다
java에서는 아래와 같이 조작할 수 있습니다.
...
public void send(String sender, String receiver, int money) {
try(Connection connection = DriverManager.getConnection(...)) {
connection.setAutoCommit(false);
try {
... connection을 사용해 sql을 실행한다
}
} catch (Exception e) {
transaction.rollback();
}
}
스프링을 사용한다면 실제로 Transaction을 조작할 일은 별로 없습니다. 그냥 @Transaction 어노테이션을 붙여주면 스프링이 알아서 위 작업들을 수행합니다. 궁금하신 분들은 AOP 를 학습하세요.
일관성이라 하면 트랜잭션이 실행되기 전, 실행된 이후 DB의 일관성이 유지되어야 한다는 의미입니다.
일관성이라 하면 DB의 제약조건들을 의미합니다. 예를 들어 은행의 잔고는 음수가 될 수 없습니다. 만약 트랜잭션이 끝날 때, 보내는 사람 혹은 받는 사람의 잔고가 음수면 해당 트랜잭션이 잘못된 거겠죠?
필자의 개인적인 생각으로는 Consistency 속성은 다른 개념들에 비해 덜 중요하다고 느껴집니다.
요새 만들어지는 프로그램들의 대부분은 서버에서 데이터의 검증이 일어납니다. DB에 이런저런 로직이 들어가면 에러가 발생했을 때 디버깅하기가 정말 정말 힘들거든요.
그러니까 이런 개념이 있다 정도만 알고 넘어갑시다. 혹시나 더 공부하고 싶으시다면 Trigger를 학습하는 걸 추천하겠습니다.
DB는 여러 트랜잭션이 동시에 일어날 수 있습니다. 하지만 하나의 트랜잭션만 일어난 것처럼 동작해야 합니다. 그러기 위해 트랜잭션이 일어날 때 다른 트랜잭션은 영향을 받아서는 안됩니다.
해당 개념은 복잡하니 추후에 별도의 글로 다루겠습니다.(링크 걸어둘게요)
트랜잭션이 끝나면 해당 내용은 영구적으로 저장되어야 한다는 의미입니다. 앞서 "은행에서 다른 사람에게 돈을 보내는 행위" 예시를 다시 들어보겠습니다.
성공 or 실패 두 가지 상황만이 가능하다 말씀드렸습니다. 만약 트랜잭션이 성공해 사용자에게 송금에 성공했다는 메세지를 보냈습니다. 그런데 사용자가 확인해보니 돈이 보내지지 않았다면 이 역시 대형사고입니다.
간단하게는 트랜잭션이 끝나면 보조기억장치에 저장하면 되는데요. 사실 그정도로 간단한 건 아닙니다. MySQL 같은 경우 성능상의 이유로 새로 변경된 데이터를 캐시에 저장합니다.
일정 시간이 지나면 캐시에 있는 데이터들을 보조기억장치에 저장합니다. 만약 서비스 도중 데이터베이스에 오류가 생겨 재시작을 하면 어떤 일이 벌어질까요?
파일 시스템에 저장된 데이터는 무사하겠지만, 캐시에 있던 데이터들은 다 휘발됩니다. 그러면 최근 변경사항들이 전부 날아가버리는 참사가 벌어집니다.
이를 위해 MySQL은 로그 파일을 저장합니다. 로그에는 어떤 데이터를 어떻게 바꿔야 하는지가 적힙니다.
만약 서버가 튕겨서 캐시 데이터가 날아가버리면 로그를 사용해서 날아가버린 내용을 살려냅니다.
웹 프로그래머를 위한 데이터베이스를 지탱하는 기술 - 마쯔노부 요리노시
https://www.youtube.com/watch?v=sLJ8ypeHGlM&list=PLcXyemr8ZeoREWGhhZi5FZs6cvymjIBVe&index=14