hlab-books / real-mysql-8.0-1

Real MySQL 8.0 1권
0 stars 2 forks source link

Chapter 5. 트랜잭션과 잠금 #3

Open hubtwork opened 4 months ago

hubtwork commented 4 months ago

Chapter Ownership @KangHun-Lee

CHOICORE commented 4 months ago

05. 트랜잭션과 잠금

Lock

락의 종류


Auto Increment Lock

MySQL에서는 Auto Increment 값을 증가시키는 동안 Auto Increment Lock이 발생할 수 있다. 이는 해당 테이블의 레코드 삽입 작업이 이루어질 때 발생하는 테이블 레벨의 락이다. 이로 인해 해당 테이블에 대한 모든 작업이 일시 중단된다.

Auto Increment Lock은 다음과 같은 상황에서 발생한다.

트랜잭션의 여부와 상관없이 AUTO_INCREMENT 값을 가져오는 시점에만 잠긴다.

(Auto Increment 이 적용된 열에 명시적으로 값을 설정하더라도 해당 락이 적용됨)

5.1 버전부터는 innodb_autoinc_lock_mode라는 설정을 통해 Auto Increment Lock 모드를 조절할 수 있습니다.

InnoDB의 래치(mutex)는 동시성 제어를 위한 도구로, 여러 스레드가 동시에 데이터에 접근하는 것을 방지한다. 이는 데이터의 일관성을 유지하고 동시에 여러 작업을 안전하게 수행할 수 있게 하는데, 래치는 락과 비슷하지만, 래치는 일반적으로 더 낮은 수준에서 작동하고, 짧은 시간 동안 유지된다.

InnoDB의 두 가지 주요한 래치 유형, 공유 래치와 배타적 래치

공유 래치는 여러 스레드가 동시에 데이터를 읽을 수 있게 해주지만, 데이터를 변경하는 것은 허용하지 않는다. 반면에 배타적 래치는 한 스레드만이 데이터에 접근하고 변경할 수 있게 한다.

흥미로웠던 점

자동 증가 락이 당연히 있어야 순서 보장을 할 것이라 생각했는데, 버전 별로 설정을 통해 조절할 수 있다는 것이 신기했다. 8.0 버전 부터는 innodb_autoinc_lock_mode의 기본값이 2로 변경되어서 래치로 처리하게 되어 더 이상 자동 증가 락이 발생하지 않고, 순서대로 채번되는 것이 아닌 유니크한 값을 생성한다는 사실을 알게 되었다.


Index Lock

InnoDB의 잠금은 레코드(데이터 로우) 를 잠그는 것이 아니라 인덱스를 잠그는 방식으로 처리된다. 변경행랴할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 잠그는 방식이다. 그렇기 때문에 MySQL의 인덱스 잠금을 알지 못한다면, 데이터베이스의 성능을 떨어뜨리는 원인이 될 수 있다. 질의 성능을 높이기 위해서는 인덱스 잠금을 이해하고 적절히 사용하는 것이 중요하다.

흥미로웠던 점

조회 성능을 위해 복합 인덱스, 인덱스를 가지고 질의 조건을 만들어내야하는 방식은 알고 있었지만, 데이터를 변경해야할 때 질의 조건에 따라 성능 차이가 발생한다는 것을 처음 알게 되었다.


MySQL의 격리 수준 (isolation level)

트랜잭션의 격리 수준은 트랜잭션 간의 데이터 접근을 어떻게 제어할 것인지를 결정하는 것이다.

격리 수준 Dirty Read Non-Repeatable Read Phantom Read
READ UNCOMMITTED O O O
READ COMMITTED X O O
REPEATABLE READ X X O (InnoDB 제외)
SERIALIZABLE X X X

REPEATABLE READ

MySQL에서는 기본적으로 REPEATABLE READ 격리 수준을 사용한다. (Non-Repeatable Read, Phantom Read 방지)를 위한 최소 수준의 격리 수준이다.

모든 InnoDB의 트랜잭션은 고유한 트랜잭션 ID를 가지며, 이를 통해 트랜잭션의 격리 수준을 관리한다. 언두 영역에 백업 된 레코드는 트랜잭션 ID도 함께 관리되고 있어 트랜잭션 ID를 통해 이전의 데이터를 참조할 수 있다.

하지만 SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE와 같은 명령을 사용하면 SELECT 해야하는 레코드에 쓰기 잠금을 걸게 되는데 언두 영역의 변경 전 데이터를 가져오는게 아니라 현재 최종 커밋된 레코드의 값을 가져오게 된다.

흥미로웠던 점

A 사용자와 B 사용자가 동시에 같은 데이터를 읽고 쓰는 상황에서 겪을 수 있는 부정합 상황을 크게 의식하지 못했었는데, A 사용자가 트랜잭션을 먼저 열고 트랜잭션을 종료하지 않은 상황에서 B 사용자가 트랜잭션을 열고 데이터를 변경하고 커밋하면 A 사용자는 변경 된 데이터를 읽으면서 부정합 문제가 발생할 수 있고, 이를 방지하기 위해 고유의 트랜잭션 ID를 부여하여 A 사용자가 바라볼 수있는 트랜잭션 ID를 통해 이전 데이터를 참조할 수 있게 한다는 것을 알게 되었다. 그리고, 쓰기 잠금을 걸어야하는 경우는 언두 영역의 변경 전 데이터를 가져오는게 아니라 현재 최종 커밋된 레코드의 값을 가져오게 된다는 것도 알게 되었다.

KangHun-Lee commented 3 months ago

5. 트랜잭션과 잠금

요약 및 정리

트랜잭션

하나의 논리적인 작업 셋에 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장해 주는 것

 MyISAM

 InnoDB

 주의사항

 네트워크 작업 예시
  1. 외부 API 호출
  2. 파일 시스템 접근
  3. 3.메일 서버를 통한 이메일 전송

MySQL 엔진의 잠금

크게 스토리지 엔진MySQL 엔진 레벨로 나뉨

  • MySQL 엔진 레벨의 잠금은 스토리지 엔진에 여향을 미침
  • 스토리지 엔진 레벨의 잠금스토리지 엔진 간 영향을 미치지 않음
  • 테이블 데이터 동기화를 위한 테이블 락
  • 테이블의 구조를 잠그는 메타데이터 락
  • 사용자의 필요에 맞게 사용할 수 있는 네임드 락

 글로벌 락

 네임드 락

 메타데이터 락

InnoDB 스토리지 엔진 잠금

레코드 기반의 잠금 방식을 탑재하고 있음

  • MyISAM 보다 훨씬 뛰어난 동시성 처리를 제공함
  • 트랜잭션과 잠금, 잠금 대기중인 목록 조회 및 종료 가능
    • infomation_schema의 INNODB_TRX, INNODB_LOCKS, INNODB_LOCK_WAITS 테이블
    • 조금씩 Deprecated 되고 있음(performance_schema의 data_locks, data_lock_waits)
 레코드 락
 갭 락
 넥스트 키 락
 자동 증가 락

인덱스와 잠금

적절한 인덱스가 준비되어 있지 않다면 동시성이 상당히 떨어짐 레코드를 변경해야 할 경우 검색한 인덱스의 레코드를 모두 락을 걸게 됨

레코드 수준의 잠금 확인 및 해제

MySQL의 격리 수준

크게 4가지로 나뉘며, 하위로 갈수록 데이터 격리(고립) 정도가 높아짐

  1. READ UNCOMMITTED (거의 사용하지 않음)
  2. READ COMMITTED
  3. REPEATABLE READ
  4. SERIALIZABLE

SERIALIZABLE 격리 수준이 아니면 크게 성능의 개선이나 저하는 발생하지 않음

DIRTY READ NON-REPEATABLE READ PHANTOM READ
READ UNCOMMITTED 발생 발생 발생
READ COMMITTED x 발생 발생
REPEATABLE READ x x 발생(InnoDB 없음)
SERIALIZABLE x x x

 READ UNCOMMITTED

 READ COMMITTED

 REPEATABLE READ

 SERIALIZABLE

hubtwork commented 3 months ago

우선 전체적으로 MyISAM 시절에는 존재하지 않았던 트랜잭션은 이제 RDBMS 전반적으로 중요한 개념이 되었고, InnoDB 는 이 부분을 훌륭히 지원한다는 점에 감사하다. 다음과 같은 특성은 특히나 InnoDB 스토리지 엔진을 다루고 있는 우리에게 중요하다고 생각한다.

또한 내가 수 차례 말하던 트랜잭션의 범위 축소 및 격리 ( 비즈니스 로직에서 부가로직의 분리 등과 함께 달성 ) 이 더 중요해지는 이유는 다른 RDBMS 와는 다른 특징인 Index 기반의 Record Lock 때문에 영향범위 또한 더 커지는 점이 있다. 이 부분도 절대적으로 기억하고, 사실 비즈니스 로직 레벨에서는 이런 조회 및 수정 자체가 없을 경우가 99% 지만 배치 애플리케이션 을 구성하여 Job 을 구성할 때, 이를 유념하여 작성할 것.

뭐 격리 수준이야.. 이번에 면접 때 대답 까먹었던 터라 ㅋㅋㅋ 다시 한번 보고 아 이런거였지.. 싶었다.. ( 여러분은 면접 때 까먹지 마시길 ㅋㅋ ) 그리고 SERIALIZABLE 은 진짜 속도가 너무 안나오는 수준의 격리레벨이라 안써봤다... 써본 사람은 후기좀요;;

zinokim commented 3 months ago

Chapter 05. 트랜잭션과 잠금

Transaction and Lock

  • Transaction: 데이터의 정합성을 보장하기 위한 기능
  • Lock: 동시성을 제어하기 위한 기능

Transaction

MySQL Isolation Level

후기(?)

hubtwork commented 3 months ago

e.g. API 요청 분리 및 사이드 이펙트 처리보장 로직

business {
  Transaction 처리() // @Transactional
  business_success_event 발행()
}

@TransactionalEventListener(BEFORE_COMMIT)
successHandler {
  push_request_history DB저장() // INIT
}

@Async
@TransactionalEventListener(AFTER_COMMIT)
pushSender {  
  API 통한 Push 요청()
  push_request_history DB 상태 변경() // FAIL or SUCCESS
}

@Scheduled
pushChecker {
  push_request_history bulk 조회 및 재시도 처리() // INIT or FAIL
}