RealMysql8.0 - 5장 트랜잭션과 잠금

eden·2022년 2월 2일
0
  • 잠금과 트랜잭션
  • 트랜잭션의 격리수준

1) 트랜잭션이란 작업의 완전성을 보장해주는 것 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능이다.

2)잠금이란 트랜잭션과 비슷한 개념 같지만 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 데이터의 정합성을 보장하기 위한 기능이다.

하나의 회원정보 레코드를 여러 커넥션에서 동시에 변경하려고 하는데 잠금이 없다면 여러 커넥션에서 동시에 변경할 수 있게 된다. 결과적으로 해당 레코드의 값은 예측할 수 없는 상태가 된다. 잠금은 여러 커넥션에서 동시에 동일한 자원(레코드나 테이블) 을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할을 한다.

  • MyISAM이나 MEMORY 스토리지 엔진은 트랜잭션을 지원하지 않는다.
  • InnoDB는 쿼리 중 일부라도 오류가 발생하면 전체를 원 상태로 만든다.(트랜잭션)

트랜잭션 처리시 주의사항

1)일반적으로 데이터베이스의 커넥션의 개수가 제한적이어서 각 단위 프로그램이 커넥션을 소유하는 시간이 길어질 수록 사용 가능한 여유 커넥션의 갯수가 줄어들 것이다.
필자의 경우 서비스 레이어에 메소드 단위로 트랜잭션을 걸고 실제 데이터베이스에 커넥션 하는 코드외에 비즈니스 코드도 혼합되어 있는데 아무리 빠르게 비즈니스 로직이 실행된다 하더라도 데이터베이스 커넥션을 물고 있기에 분리하는게 좋다.

2)트랜잭션 안에 네트워크 작업이 있는 경우(외부 메일전송등)에는 반드시 트랜잭션에서 배제 해야한다. 이런 실수로 DBMS서버가 높은 부하 상태로 빠지거나 위험한 상태에 빠지는 경우가 빈번히 발생한다.

MySQL엔진의 잠금

1)글로벌 락
잠금 가운데 가장 범위가 크다. 한 세션에서 글로벌 락을 획득하면 다른 세션에서는 SELECT를 제외한 대부분의 DDL문장이나 DML문장을 실행할 경우 글로벌 락이 해제될때까지 해당 문장이 대기 상태로 남는다.

2)테이블락
테이블락은 개별 테이블 단위로 설정되는 잠금이며, 명시적 또는 묵시적으로 특정 테이블의 락을 획들 할 수있다.
명시적 테이블락은 특별한 상황이 아니라면 애플리케이션에서 용할 필요가 거의 없다.

묵시적 테이블락은 MyISAM이나 MEMORY 테서 테이블에 데이터를 변경하는 쿼리를 실행하면 발생한다. MySQL 서버가 데이터가 변경되는 테이블에 잠금을 설정하고 데이터를 변경한 후 즉시 잠금을 해제하는 형태로 사용된다.
하지만 InnoDB의 테이블의 경우 스토리지 엔진 차원에서 레코드 기반의 잠금을 제공하기 때문에 단순 데이터 변경 쿼리로 인해 묵시적인 테이블락이 설정되지 않는다.

3)네임드락
네임드락은 임의의 문자열에대해 잠금을 설정할 수 있다 이 잠금의 특징은 대상이 테이블이나 레코드 또는 데이터베이스 객체가 아니다 라는것 이다.
네임드 락은 단순히 사용자가 지정한 문자열에 대해 획득하고 반납하는 잠금이다.
네임드 락의 경우 많은 레코드에 대해서 복잡한 요건으로 레코드를 변경하는 트랜잭션에 유용하게 사용할 수 있다. 배치 프로그램처럼 한꺼번에 많은 레코드를 변경하는 쿼리는 자주 데드락의 원인이 되곤하는데 이러한 경우에 동일 데이터를 변경하거나 참조하는 프로그램끼리 분류해서 네임드락을 걸고 쿼리를 실행하면 해결할 수 있다.

InnoDB 스토리지 엔진잠금
1)이노디비 스토리지 엔진은 mysql에서 제공하는 잠금과 별개로 스토리지 엔진 내부에서 레코드기반의 잠금 방식을 탑재하고 있다.
레코드 기반의 잠금 방식때문에 MyISAM보다는 훨씬 뛰어난 동시성 처리를 제공할 수 있다.
2)이노디비 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠근다는 점이다. 인덱스가 하나도 없는 테이블이더라도 내부적으로 자동생성된 클러스터 인덱스를 이용해 잠금을 설정한다.
3)인덱스를 기반으로 락을 걸기때문에 인덱스가 적절하게 설정되어 있지 않은경우 상관없는 레코드까지 모두 잠김 수 있다. 이는 클라이언트 간의 동시성이 상당히 떨어져서 한 세션에서 업데이트 작업을 하는 중에는 다른 클라이언트는 그 테이블을 업데이트 하지 못하고 기다려야 하는상황이 발생할 것이다.
최악의 상황은 테이블에 인덱스가 하나도 없다면 테이블을 풀 스캔하면서 업데이트 작업을 하는데 이과정에서 테이블에 있는 모든 레코드를 잠그게 된다. 이것이 MySQL InnoDB에서 인덱스 설계가 중요한 이유 이다.

MySQL의 격리수준 (isolation level)
1)트랜잭션의 격리 수준이란 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것이다.
2)격리수준은 크게 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE 총 4가지로 나뉜다.
3)DIRT READ라고도 하는 READ UNCOMMITTTED는 일반적인 데이터베이스에서는 거의 사용하지 않고 SERIALIZABLE또한 동시성이 중요한 데이터베이스에서는 거의 사용되지 않는다.
4)4개의 격리수준은 순서대로 뒤로 갈수록 각 트랜잭션간의 데이터베이스 고립정도가 높아지며 동시처리 성능도 떨어지는 것이 일반적이라고 볼 수 있다. 격리 수준이 높아질수록 MySQL서버의 처리 성능이 많이 떨어질 것으로 생각하는 사용자가 많은데 사실 SERIALIZABLE 격리수준만 아니라면 크게 성능의 개선이나 저하는 발생하지 않는다.

** 참고로 JPA에서는 Mysql, Maria 데이터베이스는 기본적으로 REPEATABLE READ이고, Oracle, MSSql은 READ COMMITTED이다. Aurora의 경우는 Writer 인스턴스는 REPEATABLE READ Reader 인스턴스는 READ COMMITTED이다

격리수준에 따른 세가지 부정합 문제

비고DIRTY READNON-REPEATABLE READPHANTOM READ
READ UNCOMMITTED발생발생발생
READ COMMITTED없음발생발생
REPEATABLE READ없음없음발생 (InnoDB는 없음)
SERIALIZABLE없음없음없음

**)
dirty read-> 어떤 트랜잭션에서 처리한 작업이 커밋되지 않았는데도 다른 트랜잭션에서 볼 수 있는 문제
non-reperatable read -> 하나의 트랜잭션안에 2개의 쿼리가 발생하는 경우 다른 트랜잭션에서 업데이트시 데이터가 상이한 문제
phantom read -> 하나의 트랜잭션안에서 일정 범위의 레코드를 두번이상 읽을때 첫번째의 쿼리에 없던 레코드가 두번째 쿼리에서 발생하는 데이터 부정합의 문제

일반적인 온라인 서비스용도의 데이터베이스는 READ COMMITTED와 REPEATABLE READ중 하나를 사용한다.

1)READ UNCOMMITTED
사용하지마라. 더티리드 문제가 발생하기때문에 RDBMS표준에서 해당 격리수준을 인정하지 않을정도로 정합성에 문제가 많은 격리 수준이다. 최소한 READ COMMITTED이상의 격리수준을 사용할 것을 권장한다.
2)READ COMMITTED
온라인 서비스에서 가장 많이 선택되는 격리수준이다.
더티리드 같은 현상은 발생하지 않는다. 어떤 트랜잭션에서 데이터를 변경했더라도 commit이 완료된 데이터만 다른 트랜잭션에서 조회 할 수 있기 때문에다.
커밋되지 않은 데이터를 다른 트랜잭션에서 조회할경우 변경트랜잭션에서 변경 전 데이터를 언두로그로 복사해놓고 커밋전에 조회트랜잭션에서 조회시 언두로그데이터를 반환한다.
해당 격리수준에서도 문제가 있는데 NON-REPEATABLE READ 문제가 발생한다는 것이다.
하나의 트랜잭션 내에서 똑같은 셀렉트 쿼리를 실행했을때 항상 같은 결과를 가져와야 한다는 NON-REPEATABLE READ 정합성에 어긋난다.
이러한 부정합 현상은 일반적인 웹 프로그램에서는 크게 문제되지 않을 수 있지만 하나의 트랜잭션에서 동일 데이터를 여러번 읽고 변경하는 작업이 금전적인 처리와 연결되면 문제가 될 수 있다. 예를들어 다른 트랜잭션에서 입금과 출금 처리가 계속 진행될때 다른 트랜잭션에서 오늘 입금된 금액의 총합을 조회한다고 가정해보자. 그런데 repeatable read가 보장되지 않기때문에 셀렉트 쿼리는 실행될 때 마다 다른 결과를 가져올 것이다.
3)REPEATABLE READ
MySQL의 InnoDB스토리지 엔진에서 기본으로 사용되는 격리수준이다.
InnoDB스토리지 엔진은 트랜잭션이 롤백될 가능성을 대비해 변경되기전 레코드를 언두(Undo)공간에 백업해놓고 실제 레코드를 변경한다. 이러한 변경 방식을 MVCC(Multi Version Concurrency Control)이라고 한다.
해당 격리레벨은 이 MVCC를 위해 언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장된다.
모든 InnoDB의 트랜잭션은 고유한 트랜잭션번호(순차적으로 증가하는 값)을 가지며 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어있다. 언두 영역에 백업된 데이터는 이노디비 스토리지 엔진이 불필요하다고 판단하는 시저메 주기적으로 삭제한다. REPEATABLE READ격리 수준에서는 MVCC를 보장하기 위해 실행중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역의 데이터는 삭제할 수가 없다.
하지만 새로운 데이터가 읽기트랜잭션과 쓰기 트랜잭션 두개의 트랜잭션이 동시에 진행하며 쓰기 트랜잭션이 읽기트랜잭션 사이에 존재할때 PHANTOM READ가 발생한다.
4)SERIALIZABLE
한 트랜잭션 안에서 읽고 쓰는 레코드를 다른 트랜잭션에서 절대 접근 할 수 없는 것이다.
일반적으로 사용할 필요성은 없다.

profile
다양한 것에 관심있는 개발자입니다.

0개의 댓글