[MariaDB] 트랜잭션, DB 동시성 이슈

심재혁·2023년 11월 21일
0
post-thumbnail

트랜잭션

정의

  • 트랜잭션이란 하나의 논리적인 작업 단위로 처리되어야 하는 하나 이상의 SQL 문의 집합
    • 예시1) 은행 계좌 간에 이체를 할 때, 금액을 한 계좌에서 빼고 다른 계좌에 더하는 두 가지 연산은 한 작업의 단위로 처리되어야 하는 트랜잭션
    • 예시2) 주문을 하고 order테이블에 주문을 생성하고, item테이블에서 재고까지 빼주는 작업이 한 단위로 처리되어야 하는 트랜잭션

COMMIT

  • COMMIT 명령은 한 트랜잭션의 모든 변경사항을 데이터베이스에 영구적으로 저장

ROLLBACK

  • ROLLBACK 명령은 트랜잭션의 변경사항을 모두 취소하고, 데이터베이스를 트랜잭션 시작 이전의 상태로 되돌리는 것

COMMIT, ROLLBACK 예제

  • sql툴에서 auto_commit 모드 해제

    예시)
    INSERT INTO board_test.author(id, name, email) VALUES(2, 'test', 'test@naver,com');
    INSERT INTO board_test.post(title, contents, author_id) VALUES('hello', 'hello is', 10);

    • 위 코드 실행후 commit하면 첫번째 쿼리만 확정
    • 위 코드 실행후 rollback하면 모두 취소

    예시)
    INSERT INTO board_test.author(id, name, email) VALUES(1, 'test', 'test@naver,com');
    COMMIT;
    INSERT INTO board_test.author(id, name, email) VALUES(2, 'test2', 'test2@naver,com');
    INTSERT INTO board_test.post(title, contents, author_id) VALUES('hello', 'hello is', 10);

    • 첫번째 쿼리는 commit되어 확정
    • 두번째 쿼리는 실행은 되나 세번째 쿼리 실행이후 rollback을 하게 되면 함께 rollback되므로, 하나의 논리적인 transaction으로 볼 수 있음
    • 일반적으로는 사용자가 위와 같은 쿼리를 실행시킬수는 없으니, 프로그램에서 논리적인 트랜잭션을 지정하고, 전체 commit 또는 전체 rollback하는 명령을 실행

DB 동시성 이슈

  • 트랜잭션이 동시에 실행됐을때 발생할 수 있는 문제 관련한 상황을 DB 동시성 문제라함

dirty read

  • 한 트랜잭션이 다른 트랜잭션이 수정 중인 데이터를 읽을 수 있는 문제
  • 아직 commit되지 않은 데이터가 읽힘으로서, 추후 rollback 될 가능성이 있는 데이터 read
  • 해결책 : Read Committed 격리성

Non-Repeatable Read

  • mariaDB의 기본 설정은 Repeatable Read0

  • 한 트랜잭션에서 동일한 조회 쿼리를 두 번 이상 실행할때에, 그 중간에 다른 트랜잭션에서 데이터를 수정하여 한 트랜잭션의 결과가 다르게 나타나는 문제

  • 예시)

    재고 업데이트 전 현재 재고 조회
    → 수량업데이트
    → 변경된 재고 수량을 다시 조회(한트랜잭션에서).
    그러나 만일 이를 중간에 누가 수정을 가하면 재조회 시 오차 발생하여 에러.

  • 해결책
    Repeatable Read 격리성

    • 한 트랜잭션이 조회하는 동안 다른 트랜잭션이 update를 하더라도 현재 트랜잭션의 read값이 변경되지 않도록 하는 격리성.
    • 그러나, 만약 다른 트랜잭션에서 update를 통해 값을 변경해버렸다면, read한 값 자체에 문제 발생.
    • 타 트랜잭션의 update를 막기 위한 select for update 쿼리 필요

Phantom Read

  • 한 트랜잭션이 같은 조회쿼리를 여러 번 실행했을 때, 그 중간에 다른 트랜잭션에서 새로운 데이터를 추가/삭제하여 다르게 나타나는 문제

  • 해결책
    Repeatable Read 격리성

    • 팬텀(유령) 읽기 또한 repeatable Read 격리성으로 해결이 가능하나,
    • 이 부분은 DB마다 차이가 있어 phantom read를 해결하기 위해 Serializable 격리수준이 필요할수도 있다.
    • 다만, DB에서 주로 발생하는 문제는 동시에 수정하는 상황이므로, 수정에 초점을 두고 해결 전략을 살펴봐도 좋을것.
  • 사실 동시성 문제는 read만이 문제는 아니고, read이후 DB에 어떠한 수정사항을 가할때도 read의 오차로 인한 또다른 오차가 발생하여 DB 전체에 영향이 발생하므로 DB 전체에 대한 동시성이라 보면 될것.


DB 격리수준

  • DB 동시성 문제를 해결하기 위한 격리수준

Read Uncommitted

  • 즉, 데이터가 변경되었다면, 커밋되지 않았다 하더라도 읽을 수 있도록 하는 격리수준
  • dirty read 발생 가능

Read Committed

  • 다른 트랜잭션이 커밋된 데이터만 읽을 수 있는 격리수준.
  • 다만, 나의 트랜잭션에 여러 select 문이 있을 경우에, 그 사이에 다른 트랜잭션에서 update 또는 insert 등을 발생시키고 commit하게 될시 phantom read 또는 non-repeatable-read 발생가능

Repeatable Read

  • 한 번 읽은 데이터는 같은 트랜잭션 내에서는 항상 같은 값을 갖도록 하는 격리수준
    나의 트랜잭션에서 먼저 read하는 동안 다른 트랜잭션에서는 변경,추가 하더라도 같은 read값을 보장하는 것. → Non-Repeatable Read과 Phantom read를 해결
  • repeatable read를 하더라도 문제가 발생할 가능성 존재
  • 나의 트랜잭션이 read하는 동안 타 트랜잭션에서 update하게 되면 read해온 값이 달라지는 문제 발생
  • 대안은 locking
  • 공유락(shared lock - select for share)
    • 두 다른 트랜잭션이 동시에 read하는 것은 가능하여 lost update 문제 가능성 존재

      ex) 상품주문의 최종 수량이 1개
      → transaction에 read && update가 있을때
      → 내 tran에서 1 read
      → 타 트랜잭션이 1 read
      → 내 tran에서 0으로 update
      → 타 tran에서 0으로 update
      → 최종 수량에 오류 발생

  • 배타락(exclusive lock - select for update)
    • read부터 lock을 걸어 lost update 문제 해결

Serializable

  • 동시에 실행되는 여러 트랜잭션들을 순차적으로 실행한 것과 같은 결과를 보장 -> 즉 데이터베이스 차원에서 동시에 특정 데이터에 접근하는 것을 차단

DB 동시성 관련 실무 해결책

  • 위와 같은 동시성 이슈는 일반적이지는 않은 상황이지만, 쇼핑몰이벤트 또는 예매 시스템에서는 빈번하게 발생할수 있는 가능성 존재
  • Spring에서의 전략
  • optimistic lock
    • 버전정보 활용하여 update시에 정합성 체크
    • update item set stock_count = new_count where id=1 and version = 1;
    • 만일, version이 맞지 않다면 0rows affected
  • pessimistic lock
    - 공유락
    - lock - PESSIMISTIC_READ
    - 배타락
    - lock - PESSIMISTIC_WRITE
    - 특정행에 대해 lock을 걸어 read조차 막음으로서 update시에 발생하는 이슈 원천 차단.
    - Serializable수준의 격리를 특정테이블과 특정데이터에 적용가능

    구분Optimistic LockPessimistic Lock
    정의충돌이 없을 것으로 가정하여 락을 걸지 않음충돌을 예상하고 미리 락을 검
    사용법버전을 사용해 관리모드 설정 및 쿼리에 직접 사용, DB단에서 설정 가능
    별칭낙관적인 락비관적인 락
    장점데드락 가능성이 적으며 성능의 이점충돌에 대한 오버헤드가 줄어들며 무결성을 지키기 용이
    단점충돌이 발생하면 오버헤드 발생충돌이 없으면 오버헤드가 발생
  • queue사용
    • 이벤트, 예매 상황에서 고려될수 있는 아키텍처
    • 동시성에 대한 효율을 감수해야 하지만, 시스템의 정확도와 사용자경험의 향상을 꾀할수 있음
  • Redis 사용
    - 싱글스레드 기반 key-value 시스템

0개의 댓글