개발머해니

[은행IT] 트랜잭션과 DB LOCK 실무에서 언제 고려해야할까? 본문

은행IT

[은행IT] 트랜잭션과 DB LOCK 실무에서 언제 고려해야할까?

왕행님 2023. 8. 28. 22:08
728x90
반응형

안녕하세요!
제가 실무를 하면서 헷갈렸던 트랜젝션과 DB LOCK 개념에 대해서 설명해 드리겠습니다.
 


트랜잭션이란?

 
각 사용자가 요청을 보냈을 때, 처리되는 거래입니다. 예를 들어, '계좌이체' 거래가 있다고 가정해 봅시다. 이때 계좌 이체 내에는 대략 아래와 같은 비즈니스 로직으로 작업이 진행됩니다. 

① 출금 계좌 목록 SELECT
② 출금 계좌 잔액 UPDATE
③ 출금 거래 기록 INSERT
④ 입금 거래 처리(당행이체/타행이체)
⑤ 입금 거래 기록 INSERT

 
일반적으로 A고객이 계좌이체를 진행하면, 위의 트랜젝션이 순차적으로 작업이 진행되게 됩니다. 또 B고객이 계좌이체를 진행하여도 동일한 순서의 거래가 흐르게 됩니다. 이 고객들의 각각 거래가 바로 '트랜잭션'이라고 볼 수 있습니다. 
 
각 '트랜잭션'은 지켜져야 하는 규칙 4가지가 있습니다. 

- 원자성 (Atomicity)
- 일관성 (Consistency)
- 독립성 (Isolation)
- 지속성 (Durability)

 
※ 각각의 내용은 아래 링크를 참고하세요~!

 

[데이터베이스] 트랜잭션의 ACID 성질 - 하나몬

트랜잭션이란 여러 개의 작업을 하나로 묶은 실행 유닛을 말한다. 데이터베이스 트랜잭션은 ACID라는 특성을 가지고 있다. ACID는 데이터베이스 내에서 일어나는 하나의 트랜잭션(transaction)의 안

hanamon.kr

꼭 알아야 하는 트랜잭션의 특징 하나만 기억하자면, 한 거래(트랜잭션) 내에 작업 전체가 실패하거나 성공하는 하나의 작업으로 묶는다는 점입니다. 
 
실패한 트랜잭션 : A고객의 계좌이체

① 출금 계좌 목록 SELECT                        → 성공
② 출금 계좌 잔액 UPDATE                      → 성공
③ 출금 거래 기록 INSERT                        → 성공
④ 입금 거래 처리(당행이체/타행이체)  → 실패 : 작업 전체 ROLLBACK
⑤ 입금 거래 기록 INSERT

 
성공한 트랜잭션 : B고객의 계좌이체

① 출금 계좌 목록 SELECT                       → 성공
② 출금 계좌 잔액 UPDATE                      → 성공
③ 출금 거래 기록 INSERT                        → 성공
④ 입금 거래 처리(당행이체/타행이체)  → 성공
⑤ 입금 거래 기록 INSERT                       → 성공 : 작업 전체 COMMIT

만약 출금 거래까지만 INSERT가 되고 입금 거래내역이 INSERT 되지 않는다면, 거래 내역의 부정확한 데이터들이 생겨나겠죠? 그래서 한 거래 내에서는 1개의 작업이라도 실패하면 전체 DB를 다 Rollback 처리하고, 모든 작업이 성공해야만 DB Commit을 진행해야 합니다.
 
 

만약 계좌이체 진행 중에 다른 스레드 요청이 들어오면 어떻게 될까? 

A고객의 거래이체 트랜잭션이 끝나기 전에 B고객의 계좌이체 요청이 들어온 경우를 가정해 봅시다. A고객의 요청이 먼저 들어왔으니, B의 요청은 A가 끝날 때까지 대기해야 할까요? 아닙니다! 보통 동시에 요청이 들어오면, 각 트랜젝션은 동시에 처리됩니다.
 
하지만 트랜잭션이 동시에 처리 가능한 개수를 지정해두지 않으면 서버가 과부하 걸려 죽을 수밖에 없겠죠? 그래서 스프링을 생각해 보면, 톰캣 WAS 내에 구현된 스레드풀에 의해서 요청의 갯수가 제어됩니다. Default로 200개의 쓰레드 요청을 허용한다고 하니 아래 내용을 참고해 보세요!

 

[Spring] 동시 요청 - 멀티 쓰레드

동시 요청 - 멀티 쓰레드를 잘 알고 있어야 트래픽 많은 서버를 잘 다룰 수 있다클라이언트가 서버에 요청을 하면,이런 흐름으로 진행된다.클라이언트가 서버로 요청하면 TCP/IP 연결 후, 서블릿

velog.io

 
 

A고객과 B고객이 같은 계좌로 입금을 한다면, 거래 후 잔액은?

아래와 같이 동시에 계좌이체 거래가 발생했다고 가정해 봅시다!

계좌 잔액
- 1번 계좌 : 500원
- 2번 계좌 : 1000원

동시에 발생한 거래
- A고객 : 2번 계좌에서 1번 계좌로 100원 입금 (1번 : 500원 → 600원 / 2번 : 1000원 → 900원 )
- B고객 : 1번 계좌에서 2번 계좌로 800원 입금 (1번 : 500원 → -300원 / 2번 : 1000원 → 1800원 )

 
과연 두 거래가 동시에 처리된다면, DB 내에 거래 잔액이 어떻게 될까요?

  1번계좌 2번계좌
최근 거래 후 잔액 500원 1000원
A고객, B고객 잔액조회 (동시) 500원 1000원
A고객 출금 (2번 → 1번 : 100원) 500 + 100 = 600원 1000 - 100 = 900원
B고객 출금 (1번 → 2번 : 800원) 500 - 800 = -300원 1000 + 800 = 1800원
C고객 잔액조회  -300원 1800원

 
C고객이 잔액을 조회했을 때, B고객이 출금한 이후에 잔액만 볼 수 있겠죠? A고객이 계좌이체한 내역은 DB에 남아있지 않겠죠? 그래서 필요한 것이 바로 DB Lock입니다! 

 

 

DB Lock이 뭘까?

DB Lock이란 데이터베이스는 여러 사용자들이 같은 데이터를 동시에 접근하는 상황에서, 데이터가 어긋나지 않도록 DB를 보호하는 방법입니다!
 
위의 상황에서 A와 B고객이 똑같은 데이터를 읽어가는 일이 발생하지 않도록 계좌 잔액이 기록된 테이블에 Lock을 걸어둔다면, 두 트랜젝션이 동시에 들어와도 0.00001초라도 먼저 요청한 고객이 테이블을 읽고 있다면, 다른 요청이 들어와도 테이블 SELECT가 되지 않아 잔액이 틀어지지 않게 되는 거죠!
 
 

DB Lock을 설정할 수 있는 범위는?

 
일반적으로는 Table Lock과 Row Lock을 주로 접하게 됩니다. 데이터베이스 전체, 파일, 블록, 컬럼 단위로도 Lock을 걸 수 있지만 실무에서 본 기억은 없습니다. 
 
테이블 락(Table Lock)

  • 테이블 수준의 Lock은 테이블을 기준으로 Lock을 설정합니다.
  • 이는 테이블의 모든 행을 업데이트 하는 등의 전체 테이블에 영향을 주는 변경을 수행할 때 유용합니다.
  • 주로 DDL(create, alter, drop 등) 구문과 함께 사용되며 DDL Lock이라고도 합니다.

 
로우 락(Row Lock)

  • 행 수준의 Lock은 1개의 행(Row)를 기준으로 Lock 설정을 합니다.
  • DML에 대한 Lock으로 가장 일반적으로 사용하는 Lock입니다.

 
※ 참고 블로그 : https://sewonzzang.tistory.com/76

 

[database] Lock

Lock 데이터베이스는 여러 사용자들이 같은 데이터를 동시에 접근하는 상황에서, 데이터의 무결성과 일관성을 지키기 위해 Lock을 사용합니다. Lock 이란 트랜잭션 처리의 순차성을 보장하기 위한

sewonzzang.tistory.com

 
 

DB Lock의 종류는?

DB Lock은 크게 공유(Shared) Lock과 베타(Exclusive), 업데이트(Update) Lock있습니다. 

공유락(Shared Lock) : 읽기 O , 쓰기 X 
① 공유 Lock은 데이터를 읽을 때 사용되며, Read Lock이라고도 불립니다.
② 공유 Lock은 공유 Lock 끼리는 동시에 접근이 가능합니다. 즉, 하나의 데이터를 읽는 것은 여러 사용자가 동시에 할 수 있다라는 것입니다. 
③ 공유 Lock이 설정된 데이터에 베타 Lock을 사용할 수는 없습니다.

베타락(Exclusive Lock) : 읽기 X , 쓰기 X 
① 베타 Lock은 데이터를 변경하고자 할 때 사용되며 Write Lock이라고도 불립니다.
② 베타 Lock은 트랜잭션이 완료될 때까지 유지됩니다. 베타락은 Lock이 해제될 때까지 다른 트랜잭션(읽기 포함)은 해당 리소스에 접근할 수 없습니다. 
③ 베타 Lock이 걸려있다면 다른 트랜잭션은 공유 락, 배타 락 둘 다 획득 할 수 없습니다.

업데이트락(Update Lock)
U-Lock 잠금이 걸려 있어도 S-Lock만은 걸 수 있다 정도로 알면 될것 같습니다.

https://resisa.tistory.com/m/184

 

SQL Server DeadLock 1편

개발자가 할줄은 알지만 은근히 모르는 DB개발과 관련된 글을 쓰려고 합니다. 저도 SP를 실제 프로젝트에 사용한 것이 얼마 안되었고 프로그램 개발에 더 많은 시간을 투자하였습니다. 그래서인

resisa.tistory.com


 

 

DB Lock이 완전한 해결책이 될 수 있을까?

아래 블로그에는 DDD Start라는 책의 내용이 요약되어 있는데 이 문제에 대한 답이 될 것 같아 참조해 보았습니다. 
 

 

DDD - 트랜잭션과 잠금을 관리하는 다양한 방법!

해당 포스팅은 "도메인 주도 개발 시작하기" 라는  내용을 정리한 글입니다. 해당 도서는 아래 Link에서 확인할 수 있습니다. - http://www.yes24.com/Product/Goods/108431347 애그리거트와 트랜잭션 주문 애

jaehoney.tistory.com

 
이 책에서는 선점잠금과 비선점잠금에 대해서 설명하고 있습니다.
 
선점잠금
 
선점 잠금은 DBMS가 제공하는 로우락(Row Lock)을 사용해서 구현한다. 일반적으로 FOR UPDATE 문을 사용해서 특정 레코드에 한 커넥션만 접근할 수 있도록 처리한다.
 
선점 잠금 기능을 사용할 때는 교착 상태(dead lock)를 조심해야 한다. 예를 들어 스레드 A와 스레드 B가 서로 사용 중인 자원을 필요로 하는 경우이다. QueryHint를 사용해서 잠금 최대 대기 시간을 지정하는 것이 좋다.
 
지정한 시간 이내 잠금을 구하지 못하면 익셉션을 발생시킨다.
 
 
비선점잠금
 
선점잠금만으로는 해결할 수 없는 동시성 문제를 해결하는 방식입니다. 비선점 잠금은 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방법이다.
 

 
비선점 잠금을 구현하려면 애그리거트에 버전으로 사용할 숫자 타입 프로퍼티를 추가해야 한다. 애그리거트를 수정할 때마다 다음과 같은 쿼리를 사용한다.
 
JPA는 버전을 이용한 비선점 잠금을 지원한다. @Version 애노테이션을 붙이고 버전을 저장할 칼럼을 추가하기만 하면 된다.
 

@Entity
@Table(name = "purchage_order")
public class Order {
    @EmbeddedId
    private OrderNo number;

    @Version
    private long version;
    
    ...
}

 
 
오프라인 선점잠금
 
만약 엄격하게 동시에 수정하는 것을 막고 싶었다면 누군가 수정 화면을 보고 있으면 수정 자체를 실행하지 못하게 해야 한다. 이때 필요한 것이 오프라인 선점 잠금(Offline Pessimistic Lock) 방식이다.
 
오프라인 선점 잠금을 관리하는 DB를 새로 구축하고 아래 4가지 로직을 모두 구현해야 내야 하기 때문에 가장 구현도가 복잡한 방법이라 생각 됩니다.
 

  • 잠금 선점 시도 (try lock)
  • 잠금 확인 (check lock)
  • 잠금 해제 (release lock)
  • 락 유효시간 연장 (extend lock expiration)

 
여기까지 동시성 제어에 대해 알아본 내용을 정리해 보았습니다.

728x90
반응형