저번 글([JPA] @Transactional과 synchronized를 동시에 사용할 때의 DB 동기화 이슈)에 이어 이번엔 왜 멀티 쓰레드 환경에서 하나의 트랜잭션에서 하나의 row 데이터에 접근하여 데이터를 변경하는 쿼리를 날리고(flush), 다른 트랜잭션에서 같은 데이터 row에 접근했을 때 변경되지 않은 데이터 값을 가져오는지 알아보기 위해 MVCC에 대해 알아보고 MySQL이 어떻게 동시성 제어를 하는지 보도록 한다.
이제 MySQL을 DBMS로 사용하고, 다음과 같은 상황에 놓여있다고 생각해보자.
먼저 다음과 같은 쿼리가 DB에 전달되고, 트랜잭션 종료 및 commit을 한다.
insert into member (m_id, m_name, m_value) values (12, '홍길동', '서울');
이제 하나의 쓰레드에서 트랜잭션을 시작해 다음처럼 update 쿼리를 날린(flush)다.
update member m set m_area = '경기' where m_id = 12;
그리고 나서 다른 쓰레드의 트랜잭션이 다음과 같이 select를 실행한다.
select * from member where m_id = 12;
결과가 어떻게 될까??
m_id | m_name | m_area |
---|---|---|
12 | 홍길동 | 경기 |
위처럼 생각했다면 이 글을 읽기 잘했다!
결과는 다음과 같다.
m_id | m_name | m_area |
---|---|---|
12 | 홍길동 | 서울 |
이유를 보기에 앞서 사용자 요청 및 쿼리 처리 과정을 간단하게 보자.
사용자에 대한 요청(쿼리)가 처리되는 과정을 간략히 보면
다음으로 InnoDB의 쿼리 처리 과정을 간략히 보자.
InnoDB는 이와 같이 버퍼 풀, Undo 로그를 통해서 MVCC를 지원한다.
다시 위 시나리오로 돌아가 정리해보면
update member m set m_area = '경기' where m_id = 12;
는 InnoDB 버퍼 풀에 기록된다.select * from member where m_id = 12;
쿼리를 날려 조회를 하면 업데이트 이전의 데이터인 ‘서울’이 조회가 된다.그 이유는 다음과 같다.
만약 Isolation Level을 Read Uncommitted(Level 0)으로 설정한다면(하면 안되지만) 트랜잭션은 InnoDB 버퍼 풀에 있는 값을 읽을 것이다.
보통의 DBMS는 Default가 Read Committed(Level 1) 이상이다. 말 그대로 커밋된 데이터를 읽는데 MySQL은 Repeatable Read(Level 2)이기 때문에 컨텍스트 스위칭 되었던 다른 쓰레드에서는 버퍼 풀이 아닌 Undo Log에 있는 데이터를 읽어온다.
InnoDB의 특징을 간략히 보자면 다음과 같다.