트랜잭션이란 데이터베이스(DB)에 상태 변화를 주는 작업의 단위를 의미한다.
예시를 들어 설명하면,
카드 결제를 할 때, 결제 요청에 의해 구매자의 카드 사용 금액이 증가하고 판매자의 판매 금액이 증가하는 데이터를 모두 처리하는 과정을 모두 포함하여 하나의 작업 단위로 인식한다는 것이다.
즉, 하나의 명령문만이 아니라 관련된 명령문 전체를 하나의 단위로 한다.
ACID (Atomicity, Consistency, Isolation, Durability)
트랜잭션에는 ACID 4가지의 특징이 존재한다.
- Atiomicity(원자성)트랜잭션, 카드 결제로 인한 사용 금액 증가와 판매 금액 증가는 전체적으로 성공/실패 해야한다. 둘 중 한가지만 성공하는 경우는 발생해서는 안되기 때문에, 중간에 문제가 발생하면 이전 작업은 모두 롤백(roll back)되어야한다.
- Consistency(일관성)결제 시의 모든 데이터는 일관된 상태여야 한다. 정해진 결제 규칙에 따라 결제 내용에 대한 처리를 항상 일관되게 수행하여야 한다.
- Isolation(독립성) 다른 트랜잭션, 다른 결제 요청에 영향을 받지 않도록 독립되어야한다.
- Durability(지속성)성공적으로 완료된 트랜잭션, 처리된 결제는 영구히 저장되어야한다.
카드 결제 과정을 코드 상에서 DB 작업으로 처리할 때, 앞서 말한 것처럼 결제 시에 구매자와 판매자의 변화에 대해 모두 처리해주어야 한다.
const buyQuery = 'UPDATE users SET card_balance = card_balance + ? WHERE id = ?';
db.query(buyQuery, [amount, buyerId], (err, result) => {
if (err) {
//에러 처리
}
const sellQuery = 'UPDATE sellers SET revenue = revenue + ? WHERE id = ?';
db.query(sellQuery, [amount, sellerId], (err, result) => {
if (err) {
//에러 처리
}
});
});
만일 이처럼 트랜잭션 처리를 해주지 않으면, 두번째 쿼리문에서 에러 발생시에 구매자의 카드 사용 금액만 올리가는 불상사가 있을 수 있다.
그렇기에,
//트랜잭션 시작
db.beginTransaction(err => {
if(err) {
return callback(err);
}
const buyQuery = 'UPDATE users SET card_balance = card_balance + ? WHERE id = ?';
db.query(buyQuery, [amount, buyerId], (err, result) => {
if (err) {
return db.rollback(() => {
callback(err); // 트랜잭션 롤백
});
}
const sellQuery = 'UPDATE sellers SET revenue = revenue + ? WHERE id = ?';
db.query(sellQuery, [amount, sellerId], (err, result) => {
if (err) {
return db.rollback(() => {
callback(err); // 트랜잭션 롤백
});
}
// 트랜잭션 커밋 (두 작업이 성공적으로 완료된 경우)
db.commit(err => {
if (err) {
return db.rollback(() => {
callback(err); // 트랜잭션 롤백
});
}
callback(null, 'Payment processed successfully');
});
});
});
});
이런식으로 처리해주면 에러가 발생하지 않는다.
이 과정을 표현해보면,
- 트랜잭션 처리 성공:
- beginTransaction → 모든 쿼리 성공 → commit → 데이터베이스에 영구 적용.
- 에러 발생 시:
- beginTransaction → 일부 쿼리 실패 → rollback → 모든 변경 사항 취소.