MySQL transaction 가이드: 어디까지 묶어야 하고 왜 길면 위험할까
DB
마지막 업데이트

MySQL transaction 가이드: 어디까지 묶어야 하고 왜 길면 위험할까


MySQL을 다루다 보면 transaction이라는 단어를 정말 자주 보게 됩니다. 그런데 입문 단계에서는 이를 “여러 SQL을 한 번에 실행하는 것” 정도로만 받아들이는 경우가 많습니다. 실무에서는 그 이해만으로는 부족합니다. 예전에 결제 서비스에서 주문 생성 → 재고 차감 → 결제 상태 갱신을 하나의 트랜잭션으로 묶고 있었는데, 그 안에서 외부 PG사 API 호출까지 같이 하고 있었습니다. PG 응답이 2초만 느려지면 트랜잭션이 그만큼 길어지고, 다른 주문들이 줄줄이 lock wait에 걸렸습니다. API 호출을 트랜잭션 밖으로 빼고 commit 이후에 비동기로 처리하니 deadlock이 일주일에 10건에서 0건으로 줄었습니다. 트랜잭션은 단순한 묶음 기능이 아니라, 데이터 일관성을 지키는 경계이면서 동시에 잠금 시간과 동시성 비용을 결정하는 설계 단위이기 때문입니다.

즉, 트랜잭션을 잘못 잡으면 두 가지 문제가 같이 생깁니다.

  • 데이터가 반쯤 반영되거나 예상과 다른 상태가 남을 수 있고
  • 잠금을 너무 오래 잡아서 lock wait timeout, deadlock, 응답 지연이 커질 수 있습니다

이 글은 아래 질문에 답하기 위해 정리했습니다.

  • transaction이 정확히 무엇인지
  • commitrollback이 실제로 무엇을 보장하는지
  • transaction boundary를 어디까지 잡아야 하는지
  • 긴 transaction이 왜 위험한지
  • isolation level과 lock 문제는 어떻게 연결되는지

짧게 말하면 transaction은 여러 쿼리를 그냥 묶는 기능이 아니라, “이 작업들은 함께 성공하거나 함께 실패해야 한다”는 경계를 선언하는 도구입니다.


Quick answer

MySQL transaction을 실무적으로 이해하려면 아래 순서로 생각하는 것이 가장 좋습니다.

  1. transaction은 여러 변경을 일관되게 완료하거나 되돌리기 위한 경계다
  2. commit 전까지는 변경이 최종 확정되지 않고, 실패 시 rollback으로 되돌릴 수 있다
  3. transaction은 “가장 넓게”가 아니라 “함께 성공하거나 실패해야 하는 최소 단위”로 잡아야 한다
  4. transaction이 오래 열려 있으면 lock도 오래 유지되어 concurrency가 떨어진다
  5. 긴 transaction은 lock wait timeout, deadlock, connection 점유 증가로 이어질 수 있다
  6. isolation level은 읽기 일관성과 동시성 사이의 균형을 바꾸므로 transaction 설계와 함께 봐야 한다

즉, transaction 설계의 핵심은 정합성을 지키되 lock과 대기 비용이 폭발하지 않게 경계를 작고 명확하게 유지하는 것입니다.


1. transaction은 정확히 무엇인가

transaction은 여러 작업을 하나의 논리적 단위로 취급하는 방식입니다. 중요한 점은 “여러 작업을 한 번에 보낸다”가 아니라 **“이 작업들은 중간 상태 없이 함께 적용되어야 한다”**는 데 있습니다.

예를 들어 계좌 이체를 생각해 보면:

  1. 계좌 A에서 10만 원 차감
  2. 계좌 B에 10만 원 증가

이 두 작업은 하나만 성공하면 안 됩니다. 차감만 되고 입금이 안 되면 데이터가 깨지기 때문입니다. 그래서 둘을 하나의 transaction 안에 넣습니다.

즉, transaction의 핵심 목적은:

  • 중간 상태가 밖에 노출되지 않게 하고
  • 일부만 성공하는 반쪽 상태를 막고
  • 실패 시 이전 상태로 안전하게 되돌릴 수 있게 하는 것입니다

그래서 transaction은 성능 기능이라기보다 정합성을 위한 안전 경계에 가깝습니다.


2. 언제 transaction이 필요한가

가장 쉬운 판단 기준은 이 질문입니다.

“이 변경들은 반드시 함께 성공하거나 함께 실패해야 하는가?”

그렇다면 transaction이 필요합니다.

대표적인 예시는 아래와 같습니다.

  • 주문 생성과 재고 차감
  • 결제 상태 반영과 주문 상태 갱신
  • 계좌 이체처럼 두 테이블 이상을 동시에 변경하는 작업
  • 한 테이블 insert 뒤 그 결과를 다른 테이블에도 반드시 반영해야 하는 작업

반대로 단순 조회 하나, 독립적인 단건 기록 하나처럼 “함께 묶일 이유”가 약한 경우에는 transaction을 불필요하게 길게 열 이유가 없습니다.

즉, transaction은 “중요한 작업이니까 무조건”이 아니라 서로 운명이 연결된 변경들에만 붙이는 것이 기본입니다.


3. commitrollback은 실제로 무엇을 의미하나

transaction 안에서 일어난 변경은 commit되기 전까지 최종 확정 상태가 아닙니다. 중간에 오류가 나거나 애플리케이션이 판단을 바꾸면 rollback으로 되돌릴 수 있습니다.

아래처럼 생각하면 직관적입니다.

START TRANSACTION;

UPDATE accounts
SET balance = balance - 100000
WHERE id = 1;

UPDATE accounts
SET balance = balance + 100000
WHERE id = 2;

COMMIT;

이 흐름에서 둘 다 정상이라면 COMMIT으로 확정합니다. 하지만 중간에 오류가 생기면:

ROLLBACK;

으로 되돌릴 수 있습니다.

핵심은 commit이 있기 전까지는 “작업이 끝난 것처럼 보여도 아직 최종 승인 전”이라는 점입니다.

이 차이를 이해하지 못하면:

  • 오류 처리 없이 중간 상태를 남기거나
  • rollback해야 할 상황에서 일부 변경을 믿고 다음 로직으로 넘어가거나
  • transaction이 아직 열려 있는데 이미 끝난 것처럼 착각할 수 있습니다

4. autocommit과 explicit transaction을 구분해서 보기

입문자가 자주 헷갈리는 부분이 바로 autocommit입니다. MySQL은 보통 기본적으로 각 statement를 하나의 개별 transaction처럼 처리합니다.

즉, 단일 UPDATE 하나를 실행하면:

  • 실행
  • 자동 commit

처럼 끝날 수 있습니다.

하지만 여러 작업이 함께 성공해야 한다면 explicit transaction이 필요합니다.

START TRANSACTION;
-- 여러 변경 수행
COMMIT;

이 차이가 중요한 이유는, autocommit 상태에서는 각 쿼리가 따로 확정되므로 “주문 생성은 성공했는데 재고 차감은 실패” 같은 반쪽 상태가 생길 수 있기 때문입니다.

즉:

  • 독립적인 단건 작업: autocommit으로 충분할 수 있음
  • 함께 성공하거나 실패해야 하는 작업: explicit transaction 필요

라고 생각하면 정리가 쉽습니다.


5. transaction boundary는 어디까지 잡아야 하나

이 질문이 실무에서는 가장 중요합니다. transaction boundary는 가능한 넓게 잡는 것이 아니라, 함께 성공하거나 실패해야 하는 최소 단위로 잡아야 합니다.

좋은 boundary의 특징은 보통 이렇습니다.

  • 비즈니스적으로 같이 묶여야 하는 작업만 포함한다
  • 외부 API 호출, 파일 처리, 긴 계산 같은 느린 작업은 가능하면 밖으로 뺀다
  • 사용자의 입력 대기 시간을 transaction 안에 넣지 않는다

예를 들어 주문 처리에서:

  • 주문 row 생성
  • 결제 상태 row 생성
  • 재고 차감

은 transaction 안에 있을 수 있습니다.

하지만 그 다음:

  • 이메일 발송
  • 외부 메시징 서비스 호출
  • 분석 로그 전송

까지 같은 transaction 안에 넣는 것은 대개 좋지 않습니다.

즉, boundary는 “관련 작업을 다 집어넣는 상자”가 아니라 데이터 일관성에 꼭 필요한 변경만 담는 최소 경계여야 합니다.


6. 왜 긴 transaction이 위험한가

transaction이 길면 데이터 변경이 늦게 확정되는 것만 문제가 아닙니다. 실제 운영에서는 lock이 오래 유지된다는 점이 훨씬 더 치명적일 때가 많습니다.

긴 transaction이 위험한 이유는 아래와 같습니다.

  • row lock이 오래 유지됨
  • 다른 세션이 같은 자원을 기다리게 됨
  • waiting session이 쌓이면 connection이 오래 점유됨
  • 결국 lock wait timeout이나 deadlock 가능성이 커짐

특히 아래 패턴은 매우 자주 문제를 만듭니다.

  • transaction 안에서 외부 API 호출
  • transaction 안에서 복잡한 비즈니스 로직 수행
  • transaction 안에서 수천 건을 한 번에 순회 업데이트
  • transaction을 열어 둔 채 사용자 입력이나 다음 요청을 기다림

즉, 긴 transaction의 위험은 “조금 느리다” 수준이 아니라 다른 요청까지 연쇄적으로 막히게 만든다는 데 있습니다.

잠금 대기 자체를 더 자세히 보고 싶다면 MySQL lock wait timeout 가이드가 자연스럽게 이어집니다.


7. transaction과 lock은 왜 같이 봐야 하나

입문 단계에서는 transaction과 lock을 별개로 배우기 쉽지만, 운영에서는 거의 항상 같이 움직입니다.

transaction이 열려 있는 동안:

  • 변경한 row에 대한 lock이 유지될 수 있고
  • 다른 세션은 그 자원에 접근하려다 기다릴 수 있으며
  • 접근 순서가 엇갈리면 deadlock도 생길 수 있습니다

예를 들어 두 코드 경로가 같은 두 테이블을 다른 순서로 건드리면:

  • 경로 A는 users 먼저, orders 나중
  • 경로 B는 orders 먼저, users 나중

처럼 되어 순환 대기가 생길 수 있습니다.

그래서 transaction 설계는 단순히 “rollback 가능” 여부만이 아니라:

  • 어떤 자원을 잠그는가
  • 얼마나 오래 잠그는가
  • 다른 흐름과 접근 순서가 일관된가

까지 함께 봐야 합니다.

이 부분은 MySQL deadlock 가이드와도 직접 연결됩니다.


8. isolation level은 왜 transaction 이해에 필수인가

transaction을 이해할 때 commitrollback만 알면 끝나는 것처럼 보이지만, 실제로는 isolation level도 매우 중요합니다. 이유는 간단합니다. transaction은 “어떻게 쓰는가”만이 아니라 동시에 실행되는 다른 transaction의 변경을 언제 보고 어떻게 일관되게 읽을 것인가도 함께 결정하기 때문입니다.

예를 들어 같은 transaction 안에서:

  • 같은 SELECT를 두 번 했을 때 결과가 달라질 수 있는가
  • 다른 transaction이 commit한 결과를 언제 볼 수 있는가

는 isolation level과 연결됩니다.

실무에서는 보통 아래 감각이 중요합니다.

  • 더 강한 일관성: 읽기 결과를 더 안정적으로 유지
  • 더 높은 동시성: 더 많은 변화를 허용하는 대신 읽기 결과가 달라질 수 있음

즉, transaction은 “쓰기 묶음”만이 아니라 읽기 일관성과 concurrency의 균형 선택이기도 합니다.

격리 수준 자체는 MySQL isolation level 가이드에서 더 자세히 이어집니다.


9. 실무에서 좋은 transaction 설계 습관

transaction을 안전하게 운영하려면 아래 습관이 특히 중요합니다.

1. 최소 단위만 묶기

정말 같이 성공하거나 실패해야 하는 변경만 transaction 안에 넣습니다.

2. 느린 외부 작업은 transaction 밖으로 빼기

API 호출, 파일 업로드, 이메일 발송, 긴 계산은 가능하면 commit 이후로 미룹니다.

3. 접근 순서를 일관되게 맞추기

같은 자원을 여러 코드 경로가 다룬다면 테이블/row 접근 순서를 통일하는 편이 deadlock을 줄이는 데 도움이 됩니다.

4. batch 작업도 transaction 길이를 통제하기

대량 insert나 대량 update를 할 때도 “한 번에 제일 크게”보다 적절한 batch size와 commit 주기를 찾는 편이 안전합니다.

5. 장애 시 rollback과 retry 전략을 함께 설계하기

transaction만 있다고 모든 문제가 자동 해결되지는 않습니다. 어떤 에러는 rollback 후 재시도가 필요할 수 있습니다.

즉, 좋은 transaction 설계는 DB 기능만의 문제가 아니라 애플리케이션 흐름까지 포함한 운영 설계입니다.


10. transaction 관련 문제가 생기면 무엇부터 확인할까

실무에서는 보통 아래 순서로 보면 흐름이 잘 잡힙니다.

  1. transaction이 너무 오래 열려 있지 않은가
  2. 어떤 쿼리나 코드 경로가 lock을 오래 쥐고 있는가
  3. transaction 안에 불필요한 외부 작업이 들어가 있지 않은가
  4. 같은 자원을 다른 순서로 갱신하는 흐름이 있는가
  5. isolation level과 쿼리 패턴이 현재 요구에 맞는가

중요한 점은 “실패한 마지막 쿼리”만 보면 안 된다는 것입니다. transaction 문제는 대부분 앞단에서 잠금을 오래 잡고 있는 흐름, 혹은 잘못 잡힌 boundary에서 시작됩니다.


자주 하는 오해

1. transaction은 길수록 안전하다

오히려 길수록 lock 유지 시간이 길어지고 contention이 커집니다.

2. 중요한 로직은 전부 한 transaction에 몰아넣어야 한다

정합성에 필요한 최소 단위만 묶는 편이 더 안전합니다.

3. transaction이 있으면 lock 문제는 DB가 알아서 해결한다

DB는 경계를 지켜 주지만, 긴 transaction이나 잘못된 접근 순서까지 대신 고쳐 주지는 않습니다.

4. read only 흐름은 transaction과 무관하다

읽기 일관성과 isolation level 관점에서는 read path도 충분히 영향을 받을 수 있습니다.


FAQ

Q. transaction은 어디까지 묶어야 하나요?

함께 성공하거나 함께 실패해야 하는 최소 변경 집합까지만 묶는 것이 가장 좋습니다.

Q. transaction 안에서 오래 걸리는 작업을 하면 왜 안 좋나요?

commit이 늦어지고 lock 유지 시간이 길어져 다른 세션이 기다리게 될 수 있기 때문입니다.

Q. deadlock과도 직접 관련이 있나요?

네. transaction이 같은 자원을 다른 순서로 오래 잡고 있으면 deadlock 가능성이 커집니다.

Q. isolation level도 transaction 설계 때 같이 봐야 하나요?

네. transaction은 쓰기 경계일 뿐 아니라 읽기 일관성과 concurrency에도 영향을 주므로 isolation level을 함께 이해하는 편이 좋습니다.


먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.

광고