-
[TIL] 스파르타) Node.js 숙련주차 강의 수강 (Transaction, Prisma), 개인 과제 진행 조금 (D-2)TIL-sparta 2024. 5. 27. 21:00
> Node 숙련주차 강의를 수강한 뒤 개인 과제의 도전 요구 사항을 완성하고, 새롭게 배운 내용을 정리해보았다.
학습 키워드: JavaScript, Node.js, express, database, MySQL, transaction, ACID
Transaction
1) What is it?:
데이터베이스에서 transaction이란 여러 개의 작업을 그룹화하여 한 그룹의 작업이 온전하게 성공하지 못하면 일부분만 수정되는 일 없이 해당 그룹의 모든 쿼리가 전부 실패하도록 하여 데이터의 일관성을 보장해주는 기능을 가리킨다. Transaction의 특징인 ACID에 대해서는 이전 TIL(링크, 1-6 Transaction)을 참고하자.
2) How does it work (MySQL)?:
-- 트랜잭션을 시작합니다. START TRANSACTION; -- 성공시 작업 내역을 DB에 반영합니다. COMMIT; -- 실패시 START TRANSACTION이 실행되기 전 상태로 작업 내역을 취소합니다. ROLLBACK;
BEGIN TRY BEGIN TRANSACTION -- Start the transaction update ar set doc_no = gltrans.reference from plxx.dbo.ar inner join plxx.dbo.gltrans on gltrans.LNKUNIQUE = ar.UNIQUE_CD where gltrans.LNKUNIQUE = ar.UNIQUE_CD and ar.DOC_NO <> gltrans.REFERENCE -- If we reach here, success! COMMIT END TRY BEGIN CATCH -- Whoops, there was an error IF @@TRANCOUNT > 0 ROLLBACK -- Raise an error with the details of the exception DECLARE @ErrMsg nvarchar(4000), @ErrSeverity int SELECT @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY() RAISERROR(@ErrMsg, @ErrSeverity, 1) END CATCH
위에서 부터 순서대로 각각 강의노트와 stackoverflow의 답변에서 발췌한 사용법인데, 강의 노트만 봐서는 ROLLBACK을 어느 순간에 사용하라는 건지 모르겠어서 추가로 찾아본 내용이다. 두 번째 블록을 통해 몇 가지 새로운 사실을 알 수 있었다. SQL Query에서 try catch 문을 사용할 수가 있는데, BEGIN 키워드로 TRY를 먼저 열어주고, 아래에서 END 키워드로 다시 TRY를 닫아준 뒤, 다시 BEGIN 키워드로 CATCH를 열어주고, 마지막으로 END 키워드로 CATCH를 닫아준다. BEGIN과 END가 중괄호 역할을 한다고 생각하면 된다.
일반적인 try catch문에서 처럼 try 구문이 끝나기 전까지 아무런 이상이 없었다면 COMMIT을 진행하여 DB에 작업 내역을 저장하게 된다. CATCH 구문의 @@TRANCOUNT는 SQL의 시스템 변수 중 하나로, BEGIN TRANSACTION (혹은 BEGIN TRAN)이 한 번 실행될 때 마다 카운트가 올라가고, COMMIT이 한 번 실행될 때 마다 카운트가 하나 줄어드는 값이다. Query가 COMMIT까지 도달하지 못하고 에러가 발생하면 @@TRANCOUNT 값이 1인 상태로 CATCH 구문으로 넘어가게 되고, IF문의 조건(@@TRANCOUNT > 0)에 걸려서 ROLLBACK이 진행되는 방식이다. ROLLBACK의 아래에 있는 구문들에 사용된 @가 붙은 이름들 또한 SQL의 System Variable 이라고 한다. 이에 대한 자세한 내용은 추후에 SQL에 대해 깊이 공부하게 되면 추가로 알아보도록 해야겠다. @@TRANCOUNT와 시스템 변수 링크를 참고하자.
3) How does it work (Prisma):
Transaction은 Sequential과 Interactive 방식으로 나뉜다. Prisma에서도 이 두 가지 방식을 지원한다.
import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); // Sequential 트랜잭션은 순차적으로 실행됩니다. // 결과값은 각 쿼리의 순서대로 배열에 담겨 반환됩니다. const [posts, comments] = await prisma.$transaction([ prisma.posts.findMany(), prisma.comments.findMany(), ]);
강의 노트에서 발췌한 Sequential Transaction 스크립트다. Sequential transaction은 prisma.$transaction() method의 인자로 array를 받고, 배열 내 prisma 쿼리 method들을 순차적으로 실행한다.
import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); // Prisma의 Interactive 트랜잭션을 실행합니다. const result = await prisma.$transaction(async (tx) => { // 트랜잭션 내에서 사용자를 생성합니다. const user = await tx.users.create({ data: { email: 'testuser@gmail.com', password: 'aaaa4321', }, }); // 에러가 발생하여, 트랜잭션 내에서 실행된 모든 쿼리가 롤백됩니다. throw new Error('트랜잭션 실패!'); return user; });
마찬가지로 강의 노트에서 발췌한 Interactive Transaction의 예시 스크립트다. Sequential transaction과 다르게 $transaction method의 인자로 함수를 사용하는데, 위 코드에서 tx를 대상으로 prisma에 등록된 테이블에 접근할 수 있다. 또한, Interactive Transaction의 경우 error등의 발생으로 중간에 실패하는 경우 ROLLBACK이 수행된다는 차이점이 있다. 함수로 구현되기 때문에 Transaction의 중간 마다 비즈니스 로직을 처리할 수 있다는 장점도 있다.
4) Why use it?:
그냥 query method만 사용하지 복잡하게 왜 transaction을 사용하느냐 생각할 수 있지만, 여러가지 작업을 수행하는 쿼리가 실행될 때, 중간에 하나가 실패하게 되는 경우 앞의 쿼리가 수정한 정보들 때문에 데이터의 무결성에 문제가 생길 수가 있고, 또한 여러 사용자가 동시에 쿼리를 진행하여 서로가 같은 데이터를 수정하게 되는 concurrency 문제가 발생하기도 한다. 이러한 문제들이 ACID 성질을 통해 해결되기 때문에 다수의 쿼리를 사용할 때는 transaction 내에서 쿼리를 진행하는 것이 좋다.
추가로 알아볼 것: XA Transaction
--
REFERENCES:
> 강의 노트, 4.5~4.6
> 개인 과제 repo
> stackoverflow, "SQL Transactions with Commit and Rollback", 답변 참고
> microsoft, "@@TRANSCOUNT(Transact-SQL)..."
728x90'TIL-sparta' 카테고리의 다른 글
[TIL] 강의 과제) Subnet, Public IP, Private IP, Static IP, Dynamic IP, Router, Routing (0) 2024.05.29 [TIL] 프로그래머스) 42583 - 다리를 지나는 트럭 풀이 (Java), 개인 과제 제출 (0) 2024.05.28 [TIL] IP, ARP, Routing (0) 2024.05.26 [TIL] 프로그래머스 - 피로도 문제 풀이 (Java, DFS) (0) 2024.05.25 [TIL] 스파르타) 아이템 시뮬레이터 2 과제 진행 - 3일 차 (bcrypt) (0) 2024.05.24