MySQL 트랜잭션 / LOCK / 격리수준

summerlee·2023년 2월 10일
0

TIL

목록 보기
39/39

1. Transaction 트랜잭션?

  • 작업의 완전성을 보장해주는 것
  • 논리적인 작업 셋을 모두 완벽하게 처리하거나, 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상(Partial update)이 발생하지 않게 만들어주는 기능

잠금(Lock)트랜잭션은 서로 비슷한 개념 같지만, 사실 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 *데이터의 정합성을 보장하기 위한 기능이다.

*데이터 정합성 : 데이터가 서로 모순 없이 일관되게 일치해야 함을 의미한다.

하나의 회원 정보 레코드를 여러 커넥션에서 동시에 변경하려고 하는데 잠금이 없다면 하나의 데이터를 여러 커넥션에서 동시에 변경할 수 있게 된다. 이는 결과적으로 해당 레코드의 값을 예측할 수 없는 상태가 된다.


1-1. MySQL에서의 트랜잭션

  • 트랜잭션은 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합됐을 때만 의미가 있는 개념은 아니다.
  • 트랜잭션은 하나의 논리적인 작업 셋에 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나(COMMIT을 실행했을 때),
    아무것도 적용되지 않아야(ROLLBACK 또는 트랜잭션을 ROLLBACK 시키는 오류가 발생했을 때) 을 보장해 주는 것이다.

1-2. 트랜잭션 관점에서의 InnoDB & MyISAM 엔진의 차이

트랜잭션 관점에서의 InnoDB & MyISAM 엔진의 차이를 살펴보기 위해 예제를 만들었다.

1) MyISAM 엔진의 경우

CREATE TABLE tab_myisam (test INT NOT NULL, PRIMARY KEY (test)) ENGINE = MyISAM;
INSERT INTO tab_myisam (test) VALUES (3);

2) InnoDB 엔진의 경우

CREATE TABLE tab_innodb (test INT NOT NULL, PRIMARY KEY (test)) ENGINE = INNODB;
INSERT INTO tab_innodb (test) VALUES (3);

위와 같이 테스트용 테이블에 각각 레코드를 1개씩 저장한 후, AUTO-COMMIT 모드에서 다음 쿼리 문장을 InnoDB 테이블과 MyISAM 테이블에서 각각 실행해 보았다.

이때, 아래와 같이 AUTO-COMMIT 모드는 디폴트가 ON 상태이다.

SHOW VARIABLES LIKE 'autocommit'; 을 사용하여 확인할수도 있다.

두 개의 스토리지 엔진에서의 결과이다.

처음부터 살펴보면, 두 INSERT 문장 모두 프라이머리 키 중복 오류로 쿼리가 실패했다.
그런데, 두 테이블의 레코드를 조회해보면 차이점이 있다.

두 테이블의 차이점

1) MyISAM 테이블

  • 오류가 발생했음에도 불구하고 '1''2'INSERT 된 상태로 남아 있음.
  • INSERT 문장이 실행되면서 차례대로 '1''2'저장하고 그 다음 '3' 을 저장하려고 하는 순간 중복 키 오류가 발생한 것.

2) InnoDB 테이블

  • 이미 INSERT 된 '1''2' 를 그대로 두고 쿼리 실행을 종료함.

결론을 정리하면 아래와 같다.

InnoDB는 쿼리 중 일부라도 오류가 발생하면 전체를 원 상태로 만든다는 트랜잭션의 원칙대로 INSERT 문장을 실행하기 전 상태로 그대로 복구한다.

MyISAM 테이블에서 발생하는 이러한 현상을 부분 업데이트 (Partial Update) 라고 표현하며 이러한 부분 업데이트 현상은 테이블 데이터의 정합성을 맞추는데 어려운 문제를 만들어 낸다.


1-3. 주의사항

트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋다.
(프로그램 코드에서 트랜잭션의 범위를 최소화 하라는 의미)



2. Lock 잠금?

  • 여러 커넥션에서 동시에 동일한 자원(레코드나 테이블)을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할
  •  크게 스토리지 엔진 레벨 & MySQL 엔진 레벨로 나눌 수 있음

2-1. MySQL 엔진

MySQL 서버에서 스토리지 엔진을 제외한 나머지 부분.
MySQL 엔진 레벨의 잠금은 모든 스토리지 엔진에 영향을 미치지만, 스토리지 엔진 레벨의 잠금은 스토리지 엔진 간 상호 영향을 미치지 않음


2-1-1. MySQL 엔진이 제공하는 잠금 기능

1) 테이블 락

  • 테이블 데이터 동기화를 위함
  • 개별 테이블 단위로 설정되는 잠금
  • 명시적 | 묵시적 으로 특정 테이블의 락을 획득할 수 있음

명시적 특정 테이블 락

LOCK TABLES table_name [ READ | WRITE ]
// 해당 명령으로 특정 테이블의 락을 획득할 수 있음

UNLOCK TABLES
// 해당 명령으로 잠금을 해제 할 수 있음

하지만 온라인 작업에 상당한 영향을 미치기 때문에 특별한 상황이 아니면 애플리케이션에서 사용할 필요가 거의 없다.

묵시적 테이블 락

  • MyISAM | MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생.
  • MySQL 서버가 데이터가 변경되는 테이블에 잠금을 설정하고 데이터를 변경한 후, 즉시 잠금을 해제하는 형태로 사용됨.
  • 쿼리가 실행되는 동안 자동으로 획득됐다가 쿼리가 완료된 후 자동 해제


2) 네임드 락

  • 사용자의 필요에 맞게 사용할 수 있음
  • GET_LOCK() 함수를 이용해 임의의 문자열에 대해 잠금을 설정할 수 있다.
mysql> SELECT GET_LOCK('string', time);

// mylock 이라는 문자열에 대해 잠금을 획득한다.
// 이미 잠금을 사용 중이면 2초 동안만 대기한다. (2초 이후 자동 잠금 해제)
mysql> SELECT GET_LOCK('mylock', 2);

// mylock 이라는 문자열에 대해 잠금이 설정돼 있는지 확인한다
mysql> SELECT IS_FREE_LOCK('mylock');

// mylock 이라는 문자열에 대해 획득했던 잠금은 해제한다.
mysql> SELECT RELEASE_LOCK('mylock');
mysql> SELECT RELEASE_ALL_LOCKS();  // 2개 이상을 동시에 해제 

//  위의 3개 함수 모두 정상적으로 락을 획득하거나 해제한 경우에는 1, 그렇지않으면 NULL 이나 0을 반환함
  • 특징
    - 대상이 테이블이나 레코드 또는 AUTO_INCREMENT 와 같은 데이터베이스 객체가 아님
    - 단순히 사용자가 지정한 문자열(String)에 대해 획득하고 해제하는 잠금.

  • 자주 사용되지는 않음



3) 메타데이터 락

  • 테이블의 구조를 잠금
  • 데이터베이스의 이름이나 구조를 변경하는 경우에 획득하는 잠금
  • 테이블의 이름을 변경하는 경우 자동으로 획득하는 잠금
    -- RENAME TABLE A TO B 명령의 경우 원본 이름 & 변경될 이름 모두 한꺼번에 잠금


3. Isolation Level 격리 수준?

  • 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지를 결정하는 레벨을 의미
  • 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것

3-1. 격리 수준 종류

3-1-1. READ UNCOMMITTED

  • 정합성에 문제가 많은 격리 수준이라 일반적인 데이터베이스에서 거의 사용 안함
  • 각 트랜잭션에서의 변경 내용이 COMMIT 이나 ROLLBACK 여부에 상관 없이 다른 트랜잭션에서 보임
  • DIRTY READ : 어떤 트랜잭션에서 처리한 작업이 완료되지 않았음에도 다른 트랜잭션에서 볼 수 있는 현상

3-1-2. READ COMMITTED

  • 오라클에서 기본으로 사용되는 격리 수준
  • 더티 리드 현상이 발생하지 않음
  • 어떤 트랜잭션에서 데이터를 변경했더라도 COMMIT완료된 데이터만 다른 트랜잭션에서 조회 가능
  • 커밋되기 전까지는 다른 트랜잭션에서 변경 내역을 조회할 수 없음
  • REPEATABLE READ 가 불가능하다는 NON-REPEATABLE READ 부정합의 문제 존재

3-1-3. REPEATABLE READ

  • 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 함
  • MySQL의 InnoDB 스토리지 엔진에서 기본으로 사용되는 격리 수준
  • NON-REPEATABLE READ 부정합이 발생하지 않음
  • InnoDB 스토리지 엔진은 트랜잭션이 ROLLBACK 될 가능성에 대비해 변경되기 전 레코드를 언두(Undo) 공간백업해두고 실제 레코드 값을 변경함 (MVCC방식)
  • 언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서 동일한 결과를 보여줄 수 있음
  • MVCC 를 보장하기 위해, 실행 중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역의 데이터를 삭제할 수 없음
  • 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상인 PHANTOM READ 현상 발생하는 단점 존재 -> 쓰기 잠금을 걸어 방지해야 함.

READ COMMITTED & REPEATABLE READ 의 공통점과 차이점

공통점

  • MVCC를 이용해 COMMIT되기 이전의 데이터를 보여줌

차이점

  • 언두 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 이전 버전까지 찾아 들어가느냐?

모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호를 가진다. (순차적으로 증가하는 값)
언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함된다.
언두 영역의 백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제한다.

3-1-4. SERIALIZABLE

  • 동시성이 중요한 데이터베이스에서는 거의 사용되지 않음
  • 가장 단순하면서 동시에 가장 엄격한 격리 수준
  • 동시 처리 성능이 다른 격리 수준보다 떨어짐
  • 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서 절대 접근할 수 없음

위의 4가지 격리 수준 은 순서대로, 뒤로 갈수록 각 트랜잭션 간의 데이터 격리 정도가 높아지고 동시 처리 성능도 떨어지는 것이 일반적이다.


⬇️ 세가지 부정합의 문제점



참고 - real Mysql 8.0
profile
완벽하지 않아도 기록하려고 노력하기 😅

0개의 댓글