가끔 트랜잭션은 슈퍼히어로 영화에 나오는 캐릭터 같아. 장애나 에러, 문제 상황에서 우리 데이터베이스를 구해주거든. 여러 작업을 한 번에 처리해야 하고, 그 작업들이 따로따로 실행되면 안 될 때 트랜잭션이 한 번에 처리해줘. 결제 처리 예시로 어떻게 동작하는지 같이 살펴보자.
결제 처리
클래식한 상황을 상상해봐: 두 개의 은행 계좌가 있고, 한 계좌에서 다른 계좌로 돈을 보내고 싶어. 이건 그냥 "한 번에 끝"나는 작업이 아니야. 한 계좌에서 돈을 정확히 빼고, 다른 계좌에 제대로 더해야 해. 실수하면 큰일 나지: 둘 다 그대로거나, 밸런스가 꼬일 수도 있어(예를 들어 돈이 사라지거나 갑자기 생길 수도 있음).
시나리오: 계좌 간 돈 이체
이게 우리 코드야. 멀리 은하에서 온 메시지처럼 집중해서 읽어보자:
-- 트랜잭션 시작
BEGIN;
-- 1단계. 송금자 계좌에서 돈 빼기
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
-- 2단계. 수신자 계좌에 돈 더하기
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- 다 잘 됐으면 변경사항 저장!
COMMIT;
여기서 중요한 건 뭐야?
1단계나2단계에서 뭔가 잘못되면(예: 쿼리 에러, 잔액 부족 등), 트랜잭션은 롤백(ROLLBACK)으로 원래 상태로 돌릴 수 있어.COMMIT은 모든 단계가 성공해야만 변경사항이 적용된다는 걸 보장해.
잔액 체크 추가하기
만약 송금자 잔액이 부족하면 어떡하지? 실수로 "마이너스"로 만들지 않게 잔액 체크를 추가해보자.
-- 트랜잭션 시작
BEGIN;
-- 송금자 현재 잔액 가져오기
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE account_id = 1;
-- 돈이 충분한지 확인
IF current_balance >= 100 THEN
-- 충분하면 이체 실행
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- 변경사항 저장
COMMIT;
ELSE
-- 부족하면 롤백
ROLLBACK;
RAISE NOTICE '이체할 금액이 부족해!';
END IF;
END $$;
여기서 더 흥미로운 점은?
- PL/pgSQL 블록에서
IF로 조건 체크를 해. 잔액이 부족하면 트랜잭션이 거절되고 아무것도 안 바뀌어. ROLLBACK은 변경사항이 있으면 취소해주고(여기선 아직 바뀐 게 없지만, 습관적으로 넣는 게 좋아).
이 강의는 트랜잭션의 실제 사용 시나리오에 관한 거라서, 실제 생활에서 쓸 수 있는 예시를 들었어. 여기엔 저장 프로시저도 들어가고, PL-SQL로 작성됐어. 이제 너희가 이 코드가 어떻게 동작하는지 이해할 만큼 충분히 경험이 쌓였다고 생각해. 나중에 PL-SQL 주제로 돌아와서 훨씬 더 복잡한 예제도 다뤄볼 거야.
트랜잭션으로 데이터 대량 업데이트
트랜잭션은 돈 이체 같은 작업뿐만 아니라 여러 상황에서 유용해. 예를 들어, 인터넷 쇼핑몰 데이터베이스에서 매일 수십 개의 주문이 "배송 중"에서 "완료"로 상태가 바뀔 수 있어. 여러 레코드를 한 번에 업데이트할 때, 혹시라도 장애가 나면 변경사항을 되돌릴 수 있어야 하지. 당연히 트랜잭션을 써야지.
또 다른 시나리오: 주문 상태 업데이트.
예시 코드:
-- 트랜잭션 시작
BEGIN;
-- 1단계. 배송일이 지난 주문 업데이트
UPDATE orders
SET status = '완료'
WHERE delivery_date < CURRENT_DATE;
-- 2단계. 성공적으로 업데이트됐다고 알림
RAISE NOTICE '모든 주문 상태가 성공적으로 업데이트됐어.';
-- 변경사항 적용
COMMIT;
뭔가 잘못되면?
항상 에러가 날 가능성은 있어. 예를 들어, WHERE 조건을 깜빡해서 모든 주문의 상태가 완료로 바뀌었을 수도 있지. 이런 상황을 막으려면 트랜잭션을 끝내거나 명시적으로 롤백하는 게 중요해.
롤백 시나리오 예시:
-- 트랜잭션 시작
BEGIN;
-- 1단계. 조건 없이 주문 업데이트 시도(앗, 실수!)
UPDATE orders
SET status = '완료';
-- 에러 때문에 트랜잭션 롤백
ROLLBACK;
-- 이제 주문은 그대로야
SAVEPOINT로 "유연성" 추가하기
항상 전체 트랜잭션을 롤백할 필요는 없어. 여러 단계로 이루어진 작업이라면, 일부만 롤백하고 싶을 수도 있지. 이럴 때 SAVEPOINT가 등장해.
이제 시나리오는 이렇게 돼: 여러 단계를 처리하면서, 그 중 한 단계만 롤백할 수 있게 하는 거야.
예를 들어, 주문 처리할 때 여러 단계가 있어: 창고에서 상품 빼기, 주문 상태 업데이트, 고객에게 알림 보내기. 만약 알림 보내기가 실패하면, 그 단계만 롤백하고 나머지 변경사항은 유지하고 싶을 수 있지.
-- 트랜잭션 시작
BEGIN;
-- 1단계. 창고에서 상품 빼기
UPDATE products
SET stock = stock - 1
WHERE product_id = 101;
-- 롤백 포인트 저장
SAVEPOINT step1;
-- 2단계. 주문 상태 업데이트
UPDATE orders
SET status = 'shipped'
WHERE order_id = 202;
-- 고객 알림 보내기 시도
SAVEPOINT step2;
-- 앗, 알림 과정에서 에러!
ROLLBACK TO SAVEPOINT step2;
-- 이제 트랜잭션을 안전하게 끝내기로 결정
COMMIT;
마무리
트랜잭션은 단순한 기술 도구가 아니라, 데이터의 무결성을 지켜주는 보증수표야. "도미노 효과"처럼 한 번의 실수로 전체 시스템이 무너지는 걸 막아주지. 여러 관련 작업을 할 때마다 스스로에게 물어봐: "이 중 하나라도 실패하면 어떻게 되지?" 만약 답이 "재앙"이라면, 트랜잭션을 쓸 때야. 기억해: 트랜잭션 코드 몇 분 더 쓰는 게, 장애 후 데이터 복구에 몇 시간 쓰는 것보다 훨씬 나아. 네 사용자(그리고 네 멘탈)가 고마워할 거야!
GO TO FULL VERSION