DB, SQL

트랜잭션

index.ys 2023. 7. 8. 21:26

트랜잭션이란?

  • 작업의 완전성을 보장해주기 위해 사용되는 개념 특정한 작업을 전부 처리하거나 전부 실패하게 만들어 데이터의 일관성을 보장해주는 기능
  • sql로 작성되는 여러 쿼리들을 1개의 단위로 묶어 하나의 작업으로 그룹화하여 처리함
  • ex) 결제 시스템, 예매 시스템등에 적용

트랜잭션 사용이유

  • 트랜잭션을 사용하면 항상 프로그램 실행을 완료하도록 구성함
  • 만약 트랜잭션이 실패할 경우 오류가 발생하더라도 DB에 영향이 가지 않아 데이터를 안전하게 다룰 수 있음

트랜잭션 특징 4가지 (ACID)

원자성  Atomicity

  • 트랜잭션 내에서 실행되는 명령어들을 하나의 묶음으로 처리하여, 내부에서 실행된 명령들이 전부 성공 하거나, 모두 실패해야 한다는 특징
  • 여러개읨 작업들을 묶어 하나의 작업단위로 처리
  • 하나의 함수처럼 트랜잭션 사용
  • ex) 계좌이체 : a의 계좌에서 2000원 인출 a계좌 + 2000 + b의 계좌에 2000원 추가

일관성  Consistency

  • 트랜잭션 내부에서 처리되는 데이터의 일관성을 유지해야하는 특징
  • 작업이 성공할 경우 아무런 문제가 발생하지 않음, 실패하더라도 작업을 진행하던 도중 실패한 상태로 데이터를 방치하지 않는 특징
  • 에러가 발생하더라도 DB에 저장된 데이터에 영향이 가지 않아야함
  • 에러 발생시 ROLLBACK

격리성  Isolation

  • 트랜잭션 수행전,후의 데이터를 외부에서 참조할 수 있지만 트랜잭션 실행중에는 데이터를 READ하거나 WRITE할 수 없도록 구성하는 특징
  • MySQL에서 ㅅ용중인 DB오브젝트에 LOCK을 걸어 격리성 구현
  • LOCK을 건 상태는 DB에 접속한 또다른 클라이언트가 해당하는 오브젝트를 읽거나, 사용할 수 없도록 만듬

동시성

  • 여러명의 사용자가 하나의 데이터를 동시에 사용(READ, WRITE) 및 공유하는 것
  • 동시성제어?

격리수준

READ UNCOMMITTED

  • **커밋 되지 않은 읽기(Uncommitted Read)**를 허용하는 격리 수준입니다.
  • 가장 낮은 수준의 격리수준이며, 락을 걸지 않아 동시성이 높지만 일관성이 쉽게 깨질 수 있습니다.

READ COMMITTED

  • 커밋 된 읽기(Committed Read)만을 허용하고, SELECT 문을 실행할 때 공유락을 겁니다.
  • 다른 트랜잭션이 데이터를 수정하고 있는 중에는 데이터를 읽을 수 없어 커밋되지 않은 읽기현상이 발생하지 않습니다.

REPEATABLE READ

  • 읽기를 마치더라도 공유락풀지 않으며, 트랜잭션이 완전히 종료될 때 까지 락을 유지합니다.
  • 공유락이 걸린 상태에서 데이터를 수정하는 것은 불가능하지만, 데이터를 삽입하는 것이 가능해집니다. 그로인해 팬텀 읽기가 발생할 수 있는 문제점이 있습니다.

SERIALIZABLE

  • 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 읽거나 삽입할 수 없고, 새로운 데이터를 추가하는 것 또한 불가능합니다.
  • 가장 높은 수준의 격리 수준이므로, 동시성이 떨어지는 문제점이 존재합니다.
  • 커밋되지 않은 읽기(Uncommitted Read)는 무엇일까요?
  • 커밋되지 않은 읽기(Uncommitted Read)는 다른 트랜잭션에 의해 작업중인 데이터를 읽게 되는 것을 나타냅니다. 만약 커밋되지 않은 읽기가 발생할 경우, 의도치 않은 데이터를 참조하게 되어 데이터의 일관성이 깨지게 되는 상황이 발생하게됩니다.
  • 팬텀 읽기(Phantom Read)란 무엇일까요?
  • 트랜잭션을 수행하던 중 다른 트랜잭션에 의해 삭제된 데이터를 팬텀행(Phantom Rows)이라고 합니다. 여기서, 팬텀행에 해당하는 데이터를 읽는 것을 팬텀 읽기(Phantom Read)라고 부릅니다.

지속성  Durability

  • 트랜잭션을 성공적을 수행하면 수정된 데이터를 DB에 영구적으로 적용하는 특징
  • 트랜잭션의 중간결과가 아닌 완성된 결과만 저장하여 DB에 이상이 생기더라도 자동 복구할 수 있는 특성을 가짐
  • 트랜잭션 완료이후 DB에 COMMIT 요청, 변경하상이 반영되기 전에 DB가 비정상적으로 종료될 경우 DB가 재시작 될때 트랜잭션의 변경사항을 다시 반영하게됨 => 지속성

SQL 트랜잭션 예시코드

-- SPARTA 테이블을 생성합니다.
CREATE TABLE IF NOT EXISTS SPARTA
(
    spartaId      INT(11)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    spartaName    VARCHAR(255) NOT NULL,
    spartaAddress VARCHAR(255) NOT NULL
);

-- 1번째 트랜잭션을 실행합니다.
START TRANSACTION;

-- SPARTA 테이블에 더미 데이터 3개를 삽입합니다.
INSERT INTO SPARTA (spartaName, spartaAddress)
VALUES ('SPARTA1', 'SEOUL'),
       ('SPARTA2', 'BUSAN'),
       ('SPARTA3', 'DAEGU');

-- 1번째 트랜잭션을 DB에 적용합니다.
COMMIT;


-- 2번째 트랜잭션을 실행합니다.
START TRANSACTION;

-- SPARTA 테이블에 더미 데이터 3개를 삽입합니다.
INSERT INTO SPARTA (spartaName, spartaAddress)
VALUES ('SPARTA4', 'SEOUL'),
       ('SPARTA5', 'BUSAN'),
       ('SPARTA6', 'DAEGU');

-- 2번째 트랜잭션을 롤백합니다.
ROLLBACK;

Sequelize 트랜잭션 예시코드

const { sequelize } = require("../models/index.js");

// 콜백으로 함수를 할당하여 비즈니스로직을 처리합니다.
const result = await sequelize.transaction( async(t) => {
  const user = await User.create({
    firstName: '용우',
    lastName: '이',
  }, { transaction: t }); // 해당 쿼리에 트랜잭션을 적용합니다.

  return user;
});

Sequelize 격리수준 설정

const { Transaction } = require("sequelize");

const t = await sequelize.transaction({
  isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED, // 트랜잭션 격리 수준을 설정합니다.
});