4.4 InnoDB 스토리지 엔진의 잠금

InnoDb 스토리지 엔진은 MYSQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반의 잠금 방식을 탑재하고 있다. InnoDB는 레코드 기반의 잠금 방식 때문에 MyISAM보다는 훨씬 뛰어난 동시성 처리를 제공할 수 있다.

4.4.1 InnoDB의 잠금 방식

  • 비관적 잠금
    현재 트랜잭션에서 변경하고자 하는 레코드에 대해 잠금을 획득하고 변경 작업을 처리하는 방식

  • 낙관적 잠금

낙관적 잠금에서는 기본적으로 각 트랜잭션이 같은 레코드를 변경할 가능성은 상당히 희박할 것이라고 가정한다. 그래서 우선 변경 작업을 수행하고 마지막에 잠금 충돌이 있었는지 확인해 문제가 있었다면 ROLLBACK처리 하는 방식을 의미한다.

4.4.2 InnoDB의 잠금의 종류

  • 레코드 락(Record lock, Record only lock)

레코드 자체만을 잠그는 것을 레코드 락 이라고 하며, 다른 상용 DBMS의 레코드 락과 동일한 역할을 한다. 한가지 중요한 차이는 InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠근다는 점이다. 만약 인덱스가 하나도 없는 테이블이라 하더라도 내부적으로 자동 생성된 클러스터 인덱스를 이용해 잠금을 설정한다.

  • 갭 락(Gap lock)

다른 DBMS와의 또 다른 차이는 바로 갭 락이다. 갭 락은 레코드 그 자체가 아니라 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것을 의미한다. 갭 락의 역할은 레코드와 레코드 사이에 새로운 레코드가 생성되는 것을 제어하는 것이다.

  • 넥스트 키 락(Next key lock)

레코드 락과 갭 락을 합쳐 놓은 형태의 잠금을 넥스트 키 락이라고 한다.

  • 자동 증가 락(Auto increment lock)

Auto_increment 칼럼이 사용된 테이블에 동시에 여러 레코드가 insert 되는 경우, 저장되는 각 레고드는 중복되지 않고 저장된 순서대로 증가한 일련번호 값을 가져야 한다. InnoDB 스토리지 엔진에서는 이를 위해 내부적으로 Auto_increment 락이라고 하는 테이블의 수준의 잠금을 사용한다.

4.4.3 인덱스와 잠금

InnoDB의 잠금과 인덱스는 상당히 중요한 연관 관계가 있다. 레코드 락은 레코드의 인덱스를 잠그는 방식으로 처리된다. 즉, 변경해야 할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 잠가야 한다.

하나의 테이블에 update 문장이 실행되면 Mysql의 InnoDB는 인덱스로 사용된 컬럼의 값과 동일한 레코드 들에 락을 건다. 만약 인덱스가 없다면 해당 테이블의 레코드에 모두 락을 건다.

4.4.4 트랜잭션 격리 수준과 잠금

이러한 불필요한 레코드의 잠금 현상은 InnoDB의 넥스트 키 락 때문에 발생하는 것이다. 하지만 InnoDB에서 넥스트 키 락을 필요하게 만드는 주 원인은 바로 복제를 위한 바이너리 로그 때문이다. 레코드 기반의 바이너리 로그를 사용하거나 바이너리 로그를 사용하지 않는 경우에는 InnoDB의 갭 락이나 넥스트 키 락의 사용을 대폭 줄일 수 있다.

  • Mysql 5.0
    innodb_locks_unsafe_for_binlog = 1

    트랜잭션 격리수준을 READ-COMMIT 로 설정

  • Mysql 5.1 이상

  1. 바이너리 로그를 비활성화
  2. 레코드 기반의 바이너리 로그 사용 : innodb_locks_unsafe_for_binlog = 1

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

InnoDB 스토리지 엔진을 사용하는 테이블의 레코드 수준 잠금은 테이블 수준의 잠금보다는 복잡하다.
(복잡해서 요약이 안됨)

4.5 Mysql의 격리 수준

트랜잭션의 격리 수준이란 동시에 여러 트랜잭션이 처리될 때, 득정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다. 격리 수준은 크게 READ UNCOMMITED, READ COMMITED, REPEATABLE READ, SERIALIZABLE 의 4가지로 나뉜다.

데이터베이스의 격리수준을 이야기 하면 항상 언급되는 것은 부정합이다. 3가지 부정합 문제가 있으며 각각의 격리수준에 따른 부정합 문제는 다음과 같다.

격리수준 DIRTY READ NON-REPEATABLE READ PHANTOM READ
READ UNCOMMITTED 발생 발생 발생
READ COMMITTED 발생하지 않음 발생 발생
REPEATABLE READ 발생하지 않음 발생하지 않음 발생(InnoDB는 발생하지 않음)
SERIALIZABLE 발생하지 않음 발생하지 않음 발생하지 않음

4.5.1 READ UNCOMMITTED

READ UNCOMMITTED 격리 수준에서는 각 트랜잭션에서의 변경 내용이 COMMIT이나 ROLLBACK 여부에 상관 없이 다른 트랜잭션에서 보여진다. 이때 다른 트랜잭션에는 변경된 레코드가 반영되지 않는 현상을 Dirty read 라고 한다. Dirty read는 사용자와 개발자를 혼란스럽게 한다.

4.5.2 READ COMMITTED

READ COMMITTED는 오라클 DBMS에서 기본적으로 사용되는 격리 수준이며, 온라인 서비스에서 가장 많이 선택되는 격리 수준이다. Dirty read 같은 현상은 발생하지 않는다. 어떤 트랜잭션에서 데이터를 변경했더라도 Commit이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있기 때문이다.

READ COMMITTED는 Non-repeatable read 부정합의 문제가 있다. 데이터 변경 후 커밋 이전에 조회문을 날렸을 때와 커밋 이후에 조회문을 날렸을 때 가져오는 데이터가 다른 경우, 하나의 트랜잭션에서 똑같은 select 쿼리를 실행했을 때 항상 같은 결과를 가져와야 하는데 위의 경우는 다른 결과를 가져오게 된다.

이러한 부정합 현상은 일반적인 웹에서는 문제는 없지만 하나의 트랜잭션에서 데이터를 여러번 읽고 변경하는 작업이 금전적인 처리와 연결되면 문제가 될 수 있다.

4.5.3 REPEATABLE READ

REPEATABLE READ는 Mysql의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준이다. 바이너리 로그를 가진 Mysql의 장비에서는 최소 Repeatable read 격리 수준 이상을 사용해야 한다. InnoDB에서는 MVCC를 위해 Undo 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있도록 보장한다.

4.5.4. SERIALIZABLE

가장 단순한 격리 수준이지만 가장 엄격한 격리 수준이다. 또한 그만큼 동시 처리 성능도 다른 트랜잭션 격리 수준보다 떨어진다. 트랜잭션의 격리수준이 SERIALIZABLE 로 설정되면 일기 작업도 공유 잠금을 획득해야만 하며, 동시에 다른 트랜잭션은 그러한 레코드를 변경하지 못하게 된다. 즉, 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없는 것이다.