동시성 제어
Db 동시성 제어방법
동시성 문제란 ? : 다수의 사용자가 db 에 동시 접근할때 트렌잭션 간섭 문제
Lock based concurrency control
- 디비 접근시 락 걸어서 동시성 제어
- 읽기작업은 공유잠금, 쓰기작업은 배타잠금을 검
- 다수 트렌젝션 동시접근시 성능저하 가능성 있음
- 교착상태 발생 가능성 있음
낙관적 락 : 같은데이터 동시수정은 없다고 가정. 읽는 시점이 아니라 수정 시점에 값 변경 검사
- 버전으로 쓰기작업을 관리하는 것으로 알고있음.
- 트랜잭션1의 version필드가 1인데 특정 튜플에 접근해서 쓰기작업 완수하면 version필드를 2로 업데이트. 근데 트랜잭션2의 version값이 1이면 업데이트 안시켜줌
- 이럴때 해결법은 그냥 복구로직 잘 설계하는 것.
비관적 락: 데이터 동시수정 가정
- 공유락 : 읽기잠금을 1번 트랜잭션이 가진 경우 2가 읽는 경우는 데이터 일관 또다른 공유락 가져옴,
- 쓰는 경우는 기다려야
- 배타락 : 쓰기잠금을 1번 트랜잭션이 가진 경우 2번째 트랜잭션은 아무것도 못하고 1번잠금이 쓰기잠금을 풀때까지 기다려야 함.
⇒ 획득한 락 해제하는 법은 커밋과 롤백 뿐이다
공유락 같은 경우 읽기와 쓰기가 서로 충돌 하는 문제나 데이터 일관성에 문제가 있을 수 있다. 배타락은 충돌하지는 않지만, 다른 트렌젝션에서 락 걸릭 행에 접근이 불가하기 때문에 해당 행에 병목 현상이 일어날 수 있다. 이러한 문제를 해결하기 위해 MVCC 방식이 등장했다.
MVCC ( multi-version concurrency control )
원본 데이터와 변경중인 데이터를 동시에 유지하는 방식. 원본 데이터에 대한 스냅샷 찍어서 보관한다. 새로운 접근 오면 배타락처럼 읽기를 막는것이 아닌, 원본데이터 스넵샷을 읽고 지금 작업중인 트렉젝션이 완료되면 저장 , 아니면 스넵샷을 바탕으로 복구작업을 진행한다. 즉, 배타락이 걸려 있어도 이를 무시하고 읽기 작업 진행이 가능하게 된다.
→즉 하나의 데이터에 대해 여러 버전이 존재하게 되고 사용자는 마지막 버전의 데이터를 읽게 된다
- 읽기 작업시 잠금 사용하지 않아 높은 동시성 유지
- But 여러버전 데이터 저장해야 함으로 저장공간 많이 필요
- 트렌젝션 시작 시점 데이터로 읽음
- 겝락과 넥스트 키 락을 통해 팬텀 리드 방지 ( 레코드와 , 레코드 주변 공간까지 잠궈 범위 조회시 다른 트렌젝션에서 INSERT 를 막음 )
NNO DB 의 버퍼 풀
디스크의 데이터를 메모리에 미리 올려두는 거대한 캐시(Cache)이자 버퍼(Buffer) 공간이다.
이렇게 mysql 은 undo 로그 를 이용해서 스넵샷 형태로 데이터를 관리 한다고 하는데 이때 commit 실행 여부와 무관하게 버퍼 풀은 갱신되고 (버퍼풀에는 한계가 있다 새로운 데이터를 계속 여기다가만 기록할 수 없으니 디스크에 잠시 기록 해 놓고 커밋되면 갱신한다. ) undo 로그에 변경 전의 값이 복사된다고 한다. 아직 커밋 전임으로 이때 외부 사용자가 접근하면 undo 로그의 값을 읽을 것 !
즉
- 가장 최신의 커밋된 데이터가 버퍼 풀에 반영되어 있다면 버퍼 풀에서 읽음
- 현재 수정 중인 데이터가 버퍼 풀에 있다면, 그걸 피해서 Undo 로그에서 과거의 커밋된 버전을 찾아 읽음 ( 나의 격리 수준에 따라 )
이렇게 Mysql 은 읽기에는 mvcc 쓰기는 락베이스로 작동한다.
Mysql 인덱스
- db 레코드를 빠르게 select 하기위한 색인이다.
- db 크기의10%를 차지한다.
- 테이블 조회를 위해 항상 정렬 돼 있어야 한다.
- Create update delete 가 많은 속성에 인덱스 걸면 인덱스 크기가 비대해져 오히려 성능 저하될 수 있다 ( 인덱스는 기존걸 삭제하는게 아니라 안씀 처리하고 추가만 하니까 )
인덱스의 자료구조
- 해시 테이블 : 빠른 데이터 검색 필요시 사용. key 로 해시값 생성 → (데이터값,인덱스) 구조를 가진다. But 해시함수는 MIN이 들어가는 모든 연산 (부등호 연산, 최대/최소) 을 해결할 수 없다. 값이 하나라도 다르면 다른 값을 주기 때문이다. 동등 비교(==), 존재 여부, key 조회에는 강하다. 때문에 보통 B-Tree 구조를 많이 쓴다.
- B-Tree 구조
- 일반적인 B-Tree. 바이너리트리와 유사하지만 자식이 2개 이상 가능하다. 균일성을 가지기 때문에 같은 노드에 있는 자료는 동일한 탐색 시간을 가진다는 장점이 있다.
mysql 은 B-Tree 를 확장한 B+Tree 를 사용한다. B-Tree 의 경우 중간 노드에도 실제 데이터 값이 들어간다.
때문에 중간 노드 전체를 탐색해야 한다는 단점이 있다. 이를 보완하여 B+Tree 는 리프 노드에만 실제 값을 두고
중간 노드에는 key 만 저장한다. 즉 따라가면서 마지막 높이에 있는 노드들만 탐색 해 보면 된다.
(오는길에 가지치기됨)
- 리프노드에만 값을 담아두기 때문에 하나의 노드에 더 많은 Key 를 가질 수 있다. 때문에 key 높이가 낮아진다. (cash hit 을 높힐 수 있음)
- Inno db 는 데이터 파일 저장 장식 자체를 pk 인덱스로 정렬하여 관리해서 pk 가 모든 테이블에 반드시 존재 해야 한다고 한다 ( 없으면 내부적으로 만들어줌 ) 그래서 key value 에서 value 는 다른 노드 주소라 고정값이니 key 값을 줄여주는게 하나 노드에 딤길 수 있는 key value 늘릴 수 있는 방법이라고함.
- 무조건 인덱스로 읽는게 효율적인건 아니다.
- 만약 테이블의 레코드 수가 100만 개 인데 절반인 50만개 또는 100만개 전부 다 읽어야 한다고 가정해보자. 그러면 굳이 인덱스를 읽지 않고 바로 레코드를 읽을 수 있다면 읽는 게 이득일 수도 있다. 그래서 통계적인 수치로 전체 레코드 대비 20% 미만의 레코드를 읽을 때 효율적으로 동작한다고 한다 물론 저장된 레코드 수와 읽어야 할 레코드 수를 예상해서 옵티마이저가 최적화해준다.
Mysql 스토리지 엔진 수준의 락
Mysql 에서 사용되는 락은 스토리지 엔진 수준의 락과 Mysql 엔진 수준의 락 두개이다.
Mysql 엔진 수준 락 : 테이블 , db 위한 락
스토리지 엔진 수준 락 : 테이블 데이터를 다루기 위한 락
이중 스토리지 엔진 수준의 락에 대해 알아보자.
1. 레코드락
테이블 레코드 자체을 잠그는 락
Mysql 에서 레코드락은 테이블 레코드가 아닌 인덱스 레코드를 잠근다
- 만약 테이블에 인덱스가 하나도 없다면? InnoDB는 내부적으로 생성한 자동 증가 클러스터 인덱스를 이용해 잠금을 수행
- 중요: UPDATE나 DELETE를 할 때 적절한 인덱스가 없으면, 엉뚱한 레코드들까지 다 잠겨버리는 불상사가 생길 수 있음
2. 갭 락 (Gap Lock)
레코드 자체가 아니라 레코드와 레코드 사이의 간격(Gap)을 잠그는 락
- 목적: 레코드 사이의 간격에 새로운 레코드가 INSERT되는 것을 막음
- 팬텀 리드(Phantom Read) 방지 위함
3. 넥스트 키 락 (Next-Key Lock)
레코드 락과 갭 락을 합친 형태
- InnoDB의 기본 잠금 방식입니다.
- 보통 REPEATABLE READ 격리 수준에서 사용되며, 특정 레코드와 그 앞의 갭을 세트로 묶어 잠금으로써 데이터의 일관성을 지킴
여기서 데드락이 발생하기 쉽다.
겝락 획득은 서로 충돌하지 않기 때문. 두개의 트렌젝션이 같은 공간에 대해 겝락을 획득하면 아무도 데이터 삽입이 불가한 상황이 발생한다.
4. 자동 증가 락 (Auto-Increment Lock)
AUTO_INCREMENT 속성이 붙은 칼럼에 값을 넣을 때 사용하는 테이블 수준의 락
- 여러 트랜잭션이 동시에 INSERT를 할 때, ID 값이 중복되지 않고 순차적으로 증가하도록 보장
- 아주 짧은 순간만 걸렸다가 해제되는 특수한 락
'밍개발일지' 카테고리의 다른 글
| 센츄리 + discord 알림 연동 (1) | 2026.03.24 |
|---|---|
| 서준수의 글쓰기 클럽 (0) | 2026.03.24 |
| 통신 : Polling, Long Polling, Streaming , Websocket , SSE (3) | 2025.07.27 |
| 웹 기본 + nginx (1) | 2025.07.27 |
| MediaType (2) | 2025.04.06 |