백앤드(Back-End)/MySQL

[MySQL] - 트랜잭션

RyanSin 2024. 2. 7. 00:35
반응형

개요

안녕하세요. 이번 시간에는 트랜잭션(Transaction)에 대해 알아보겠습니다.

 

트랜잭션은 관계형 데이터에비스에서 작업의 완정성을 보장하기 위한 단위입니다. (여기서 작업은 SQL 구문(Select, Insert, Update, Delete)을 말합니다.)

 

여러 작업을 하나의 트랜잭션 단위로 만들 수 있고, 반대로 하나의 작업을 트랜잭션 단위로 만들 수 있습니다.

 

위 설명이 이해가 가지 않을 것 같아요! 바로 예시를 통해 알아보도록 하겠습니다.

 

준비

기본적으로 MySQL 설치 및 데이버테이스와 테이블 생성하는 개념은 어느 정도 있다고 생각하고 진행하도록 하겠습니다.

 

- 예시 테이블

-- InnoDB 테이블 생성 SQL
CREATE TABLE user_innodb
(
    `user_id`     BIGINT         NOT NULL    AUTO_INCREMENT COMMENT '고유 키', 
    `phone`       VARCHAR(30)    NOT NULL    COMMENT '휴대폰 번호', 
    `nick_name`   VARCHAR(50)    NOT NULL    COMMENT '닉네임', 
    `created_at`  DATETIME       NOT NULL    DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', 
     PRIMARY KEY (user_id)
) ENGINE=INNODB;
ALTER TABLE user_innodb COMMENT '유저';

-- MyISAM 테이블 생성 SQL
CREATE TABLE user_myisam
(
    `user_id`     BIGINT         NOT NULL    AUTO_INCREMENT COMMENT '고유 키', 
    `phone`       VARCHAR(30)    NOT NULL    COMMENT '휴대폰 번호', 
    `nick_name`   VARCHAR(50)    NOT NULL    COMMENT '닉네임', 
    `created_at`  DATETIME       NOT NULL    DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', 
     PRIMARY KEY (user_id)
)  ENGINE=MyISAM;
ALTER TABLE user_myisam COMMENT '유저';

 

위 테이블을 만들었다면 본격적으로 진행해보겠습니다.

 

예시

트랜잭션을 사용하기 위해서는 다음과 같은 SQL 구문이 존재합니다.

  • start transaction - 트랜잭션을 시작
  • commit - 모든 작업을 적용
  • rollback - 모든 작업을 전 상태로 되돌림

우리가 보통 기본적으로 insert와 update, delete를 하면 다음과 같이 작업을 바로 반영합니다. 그 이유는 기본적으로 MySQL은 autocommit이라는 옵션이 활성화되어있기 때문입니다.

 

autocommit 활성상태 여부를 확인하는 방법은 아래 명령어를 통해 확인이 가능합니다.

# 결과가 on/off 상태를 보여준다.
SHOW VARIABLES LIKE 'autocommit%';

# 결과가 0 or 1 상태로 보여준다.
SELECT @@AUTOCOMMIT;

# autocommit 설정 변경 방법 (on/off)
SET autocommit = on;

 

만약 autocommit 상태를 off로 변경하고 insert 구문을 시작하면 어떻게 될까요? 아래 쿼리 구문을 순차적으로 실행해 보세요.

# autocommit 비활성화
SET autocommit = off;

# autocommit 상태 확인 OFF
SHOW VARIABLES LIKE 'autocommit%';

# 데이터 추가
INSERT INTO user_innodb VALUE (3,'01011111111', 'ryan1', now());

# 데이터 추가 로우 하나 생성된 것 처럼 보여짐
SELECT * FROM user_innodb;

# 작업 트랜잭션 롤백!!
ROLLBACK ;

# 결과 다시 확인... 데이터가 없음...
SELECT * FROM user_innodb;

 

위 쿼리 구문을 실행하면 마지막 SELECT 쿼리 실행 시 데이터가 없는 것을 확인할 수 있습니다. 즉, MySQL은 기본적으로 autocommit상태가 활성화 상태입니다.

 

그렇다는 건 우리는 트랜잭션을 생각하지 않고 쿼리를 실행해도 insert, update, delete 구문은 자동적으로 반영(commit)되게 됩니다.

 

또한 MySQL에는 엔진에 따라 트랜잭션 여부가 달라집니다. 기본적으로 innodb 엔진은 트랜잭션을 지원합니다. memory엔진과 myisam엔진은 지원하지 않습니다. 아래 예시를 통해 확인해 보겠습니다.

 

먼저 테이블 데이터를 모두 초기화를 합니다.

truncate user_innodb;
truncate user_myisam;

 

그 후 다음과 같이 데이터를 하나씩 저장합니다.

INSERT INTO user_innodb VALUE (3, '01011111111', 'ryan1', now());
INSERT INTO user_myisam VALUE (3, '01011111111', 'ryan1', now());

 

 

그런 다음 예시를 위해 user_id 값이 중복해서 데이터를 저장합니다.

INSERT INTO user_innodb
VALUES (1, '01011111111', 'ryan1', now()),
       (2, '01011111111', 'ryan1', now()),
       (3, '01011111111', 'ryan1', now());
       
INSERT INTO user_myisam
VALUES (1, '01011111111', 'ryan1', now()),
       (2, '01011111111', 'ryan1', now()),
       (3, '01011111111', 'ryan1', now());

 

위 두 쿼리를 실행하면 다음과 같이 에러가 발생합니다.

 

그럼 트랜잭션을 지원하는 엔진과 아닌 엔진과 결과를 확인해 볼까요?

 

생각하신 결과가 나왔나요? 아니면 생각하신 결과가 안 나왔나요? innodb 엔진은 트랜잭션을 지원하기 때문에 하나의 쿼리가 실패했다면 모든 데이터를 롤백합니다.

 

하나의 INSERT 쿼리 구문에 3개의 데이터를 한 번의 저장하려고 했기 때문에 1번과 2번 데이터까지 포함돼서 저장을 실패했습니다.

 

만약 특정 비즈니스 로직에서 INSERT구문을 VALUES가 아니라 VALUE를 사용해서 하나씩 저장하는 상황이라면 어떻게 될까요?

1번 데이터와 2번 데이터가 저장되게 됩니다. 그렇다면 우리는 부분 적용 현상(Partial Updte)을 마주하게 됩니다.

 

하지만 트랜잭션을 사용해서 여러 쿼리 구문을 한 단위로 묶는다면 위와 같은 상황에서 부분 적용 현상을 피할 수 있습니다.

# 초기 데이터 확인
SELECT * FROM user_innodb;

# 트랜잭션 시작
start transaction;

# 데이터 저장 성공
INSERT INTO user_innodb VALUE (1, '01011111111', 'ryan1', now());

# 데이터 저장 성공
INSERT INTO user_innodb VALUE (2, '01011111111', 'ryan1', now());

# 데이터 저장 실패
INSERT INTO user_innodb VALUE (3, '01011111111', 'ryan1', now());

# 현재 상태 확인
SELECT * FROM user_innodb;

# 원 상태로 되돌리기
rollback;

# 현재 상태 확인
SELECT * FROM user_innodb;

 

다음과 같이 쿼리 구문을 순차적으로 실행하면서 결과를 확인하면 이전 상태와 동일한 상태라는 걸 확인할 수 있습니다.

 

만약 rollback이 아닌 commit을 실행하면 어떻게 될까요? 아래와 같은 결과가 나오는 걸 확인하실 수 있습니다.

# 초기 데이터 확인
SELECT * FROM user_innodb;

# 트랜잭션 시작
start transaction;

# 데이터 저장 성공
INSERT INTO user_innodb VALUE (1, '01011111111', 'ryan1', now());

# 데이터 저장 성공
INSERT INTO user_innodb VALUE (2, '01011111111', 'ryan1', now());

# 데이터 저장 실패
INSERT INTO user_innodb VALUE (3, '01011111111', 'ryan1', now());

# 현재 상태 확인
SELECT * FROM user_innodb;

# 원 상태로 되돌리기
commit;

# 현재 상태 확인
SELECT * FROM user_innodb;

 

결국 에러가 발생한 쿼리 구문은 무시하고 데이터가 저장되는 걸 확인할 수 있습니다.

 

이번 시간에는 트랜잭션(Transaction)의 대해 알아봤습니다. 꼭 실습을 통해 하나하나 분석해보시는 걸 추천드리겠습니다.

 

주의 사항

트랜잭션을 사용할 때는 DB 커넥션과 동일하게 꼭 필요한 최소의 코드에만 적용을 하는 게 좋습니다. 즉, 프로그램 코드에서 트랜잭션의 범위를 취소화하라는 의미를 뜻 합니다. (실제로 저도 트랜잭션 범위를 최소한으로 설정하지 않아서 문제가 발생한 경우가 있었습니다.)

 

실제 비즈니스 로직에서 트랜잭션을 설정해야 하는 부분을 많이 고려해서 설정하는 걸 추천드리겠습니다.