From 82a09bde64925f8a796169f4fc37bb2729f5108d Mon Sep 17 00:00:00 2001 From: Jimin Date: Mon, 16 Dec 2024 18:16:34 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20Real=20MySQL=208.0=205.=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- ...70\353\236\234\354\236\255\354\205\230.md" | 290 ++++++++++++++++++ 2 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 "\353\217\204\354\204\234/Real MySQL 8.0/5. \355\212\270\353\236\234\354\236\255\354\205\230.md" diff --git a/README.md b/README.md index 4ca6288..a7de5ad 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ ## 도서 -[자바 스프링 개발자를 위한 실용주의 프로그래밍](https://github.com/jmxx219/TIL/tree/main/%EB%8F%84%EC%84%9C/%EC%9E%90%EB%B0%94%20%EC%8A%A4%ED%94%84%EB%A7%81%20%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC%20%EC%9C%84%ED%95%9C%20%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D) +[자바 스프링 개발자를 위한 실용주의 프로그래밍](https://github.com/jmxx219/TIL/tree/main/%EB%8F%84%EC%84%9C/%EC%9E%90%EB%B0%94%20%EC%8A%A4%ED%94%84%EB%A7%81%20%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC%20%EC%9C%84%ED%95%9C%20%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D) + [Real MySQL 8.0](https://github.com/jmxx219/TIL/tree/main/%EB%8F%84%EC%84%9C/Real%20MySQL%208.0)
diff --git "a/\353\217\204\354\204\234/Real MySQL 8.0/5. \355\212\270\353\236\234\354\236\255\354\205\230.md" "b/\353\217\204\354\204\234/Real MySQL 8.0/5. \355\212\270\353\236\234\354\236\255\354\205\230.md" new file mode 100644 index 0000000..d2cacb7 --- /dev/null +++ "b/\353\217\204\354\204\234/Real MySQL 8.0/5. \355\212\270\353\236\234\354\236\255\354\205\230.md" @@ -0,0 +1,290 @@ +# 5. 트린잭션 + + +### 목차 + +- [5.1 트랜잭션](#51-트랜잭션) +- [5.2 MySQL 엔진의 잠금](#52-mysql-엔진의-잠금) +- [5.3 InnoDB 스토리지 엔진 잠금](#53-innodb-스토리지-엔진-잠금) +- [5.4 MySQL의 격리 수준](#54-mysql의-격리-수준) + +
+ +> - `트랜잭션`: 데이터의 정합성을 보장하기 위한 기능 +> - 작업의 완정성을 보장해주는 것. 즉, 논리적인 작업 셋을 모두 완벽하게 처리하거나, 원 상태로 복구해서 일부만 적용되는 현상이 발생해지 않게 만들어주는 기능 +> - `잠금(Lock)`: 동시성을 제어하기 위한 기능 +> - 여러 커넥션에서 동시에 동일한 자원(레코드나 테이블)을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할 +> - `격리 수준`: 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지를 결정하는 레벨을 의미 + + + +## [5.1 트랜잭션](#목차) + +> `MyISAM`과 `MEMORY`는 트랜잭션을 지원하지 않고, `InnoDB`는 트랜잭션을 지원한다. + +### 5.1.1 MySQL 에서의 트랜잭션 + +> 하나의 논리적인 작업 셋에 몇 개의 쿼리가 있든 관계없이, 논리적인 작업 셋 자체가 100% 적용되거나(COMMIT을 실행했을 때) 아무것도 적용되지 않아야(ROLLBACK 또는 트랜잭션을 ROLLBACK 시키는 오류가 발생했을 때)함을 보장해 주는 것 + +트랜잭션 관점에서 InnoDB 테이블과 MyISAM 테이블의 차이 +- MyISAM과 MEMORY 스토리지 엔진을 사용하는 테이블의 경우, 한 쿼리에서 오류가 발생해도 일부 쿼리는 성공시킨다. + - 이러한 현상을 부분 업데이트트(`Partial Update`)라고 표현하며, 이 현상은 테이블 데이터의 정합성을 맞추는데 상당히 어려운 문제를 만들어낸다. +- 반면에 InnoDB는 쿼리 중 일부라도 오류가 발생하면 전체를 원 상태로 만든다. + +트랜잭션을 상당히 골치 아픈 기능쯤으로 생각할 수 있지만, 트랜잭션이란 그만큼 애플리케이션 개발에서 고민해야 할 문제를 줄여주는 아주 필수적인 DBMS의 기능이라는 점을 기억해야 한다. +- 부분 업데이트 현상이 발생하면 실패한 쿼리로 인해 남은 레코드를 다시 삭제하는 재처리 작업이 필요할 수 있다. +- MyISAM의 경우, 비즈니스 로직 처리로 이미 `IF.. ELSE..` 로 가득한 프로그램 코드에 이런 데이터 클렌징 코드까지 넣어야 한다. + ```sql + INSERT INTO tab_a ...; + IF(_is_ insert1_succeed) { + INSERT INTO tab_b ...; + IF(_is_ insert2_succeed){ + // 처리 완료 + } ELSE { + DELETE FROM tab_a WHERE ...; + IF(_is_delete_succeed) { + // 처리 실패 및 tab_a, tab_b 모두 원상 복구 완료 + } ELSE { + // 해결 불가능한 심각한 상황 발생 + // 이제 어떻게 해야 하나? + // tab_b 에 INSERT는 안 되고, 하지만 tab_a 에는 INSERT 돼 버렸는데, 삭제는 안 되고... + } + } + } + ``` +- 하지만 InnoDB의 경우, 간단한 코드로 완벽한 구현이 가능하다. + ```sql + try { + START TRANSACTION; + INSERT INTO tab_a ...; + INSERT INTO tab_b ...; + COMMIT; + } catch(exception) { + ROLLBACK; + } + ``` + +
+ +### 5.1.2 주의사항 + +트랜잭션 또한 DBMS 의 커넥션과 동일하게 꼭 필요한 최소의 코드에만 적용하는 것이 좋다. +- 프로그램의 코드가 데이터베이스 커넥션을 가지고 있는 범위와 트랜잭션이 활성화돼 있는 프로그램의 범위를 최소화해야 한다는 것이다. +- 프로그램의 코드에서 라인 수는 한두 줄이라고 하더라도 네트워크 작업이 있는 경우에는 반드시 트랜잭션에서 배제해야 한다. + - 프로그램이 실행되는 동안 메일 서버와 통신할 수 없는 상황이 발생한다면, 웹 서버뿐 아니라 DBMS 서버까지 위험해지는 상황이 발생할 것이다. + +
+
+ +## [5.2 MySQL 엔진의 잠금](#목차) + +> MySQL 에서 사용되는 잠금은 크게 `스토리지 엔진 레벨`과 `MySQL 엔진 레벨`로 나눌 수 있다. +> - `MySQL 엔진 레벨`의 잠금은 모든 스토리지 엔진에 영향을 미치지만, `스토리지 엔진 레벨`의 잠금은 스토리지 엔진 간 상호 영향을 미치지는 않는다. + +### 5.2.1 글로벌 락 + +> `FLUSH TABLES WITH READ LOCK` 명령으로 획득할 수 있다. 여러 데이터베이스에 존재하는 MyISAM이나 MEMORY 테이블에 대해 `mysqldump`로 일관된 백업을 받아야 할 때 사용해야 한다. + +글로벌 락은 MySQL 서버의 모든 테이블에 큰 영향을 미치기 때문에 웹 서비스용으로 사용되는 MySQL 서버에서는 가급적 사용하지 않는 것이 좋다. +- MySQL에서 제공하는 잠금 가운데 가장 범위가 크다. + - 글로벌 락이 영향을 미치는 범위는 **MySQL 서버 전체**이며, 작업 대상 테이블이나 데이터베이스가 다르더라도 동일하게 영향을 미친다. +- 글로벌 락을 획득하지 않은 다른 세션에서 SELECT를 제외한 대부분의 DDL 문장이나 DMIL 문장을 실행하는 경우, 글로벌 락이 해제될 때까지 대기 상태로 남는다. + +MySQL 8.0부터 InnoDB가 스토리지 엔진이 되면서 조금 더 가벼운 글로벌 락의 필요성이 생겼다. +- InnoDB 스토리지 엔진은 트랜잭션을 지원하기 때문에 일관된 데이터 상태를 위해 모든 데이터 변경 작업을 멈출 필요는 없다. +- 그래서 백업 툴들의 안정적인 실행을 위해 `백업 락`이 도입됐다. + - 백업 락을 획득하면 모든 세션에서 테이블의 스키마나 사용자의 인증 관련 정보를 변경할 수 없게 되지만, 일반적인 테이블의 데이터 변경은 허용된다. + +#### 백업 락의 필요성 +> MySQL 서버의 구성은 소스 서버(Source server)와 레플리카 서버 (Replica server)로 구성되며, 주로 백업은 레플리카 서버에서 실행된다. MySQL에서 복제(replication)는 소스 서버에서 발생한 변경 사항을 레플리카 서버로 전달하고 실행한다. +- 백업 락은 백업이 진행되는 동안 데이터의 일관성을 유지하면서 백업 실패를 방지하는 것이 목적이다. +- 백업 중에 백업 중에 스키마 변경(DDL)이 발생하면, 복제를 통해 레플리카 서버의 데이터가 변경되고 이로 인해 백업의 일관성이 깨지거나 백업이 실패할 수 있다. +- 백업 락은 이를 방지하기 위해 DDL 명령 실행 시, 복제를 일시적으로 중지한다. + + +
+ +### 5.2.2 테이블 락 + +> 개별 테이블 단위로 설정되는 잠금이며, 명시적 또는 묵시적으로 특정 테이 블의 락을 획득할 수 있다. + +명시적인 테이블 락 +- 명시적인 테이블 락도 특별한 상황이 아니면 애플리케이션에서 사용할 필요가 거의 없다. +- 명시적으로 테이블을 잠그는 작업은 글로벌 락과 동일하게 온라인 작업에 상당한 영향을 미치기 때문이다. + +묵시적인 테이블 락 +- 묵시적인 테이블 락은 MyISAM 이나 MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생한다. + - 쿼리가 실행되는 동안 자동으로 획득됐다가 쿼리가 완료된 후 자동 해제된다. +- InnoDB 테이블의 경우, 스토리지 엔진 차원에서 레코드 기반의 잠금을 제공하기 때문에 DML로 인해 묵시적인 테이블 락이 설정되지는 않는다. DDL의 경우에만 영향을 미친다. + +
+ +### 5.2.3 네임드 락 + +`GET_LOCK()` 함수를 이용해 임의의 문자열에 대해 잠금을 설정할 수 있다. +- 단순히 사용자가 지정한 `문자열(String)`에 대해 획득하고 반납하는 잠금이다. + - 대상이 테이블이나 레코드 또는 AUTO_INCREMENT와 같은 데이터베이스 객체가 아니라는 것이 특징이다. +- 자주 사용되지는 않지만, 여러 클라이언트가 상호 동기화를 처리해야 할 때 네임드 락을 이용하면 쉽게 해결할 수 있다. +- 동일 데이터를 변경하거나 참조하는 프로그램끼리 분류해서 네임드 락을 걸고 쿼리를 실행하면 데드락을 간단히 해결할 수 있다. + - 배치 프로그램처럼 한꺼번에 많은 레코드를 변경하는 쿼리는 자주 데드락의 원인이 된다. + +
+ +### 5.2.4 메타데이터 락 + +> 명시적으로 획득/해제할 수 있는 것이 아닌, 데이터베이스 객체(테이블이나 뷰 등)의 테이블의 이름이나 구조를 변경하는 경우 자동으로 획득하는 잠금 + +- RENAME TABLE 명령의 경우, 원본 이름과 변경될 이름 두 개 모두 한꺼번에 잠금을 설정한다. +- 테이블 또는 스키마에 대한 DDL 연산이 안전하게 수행될 수 있도록 한다. + +
+
+ +## [5.3 InnoDB 스토리지 엔진 잠금](#목차) + +> InnoDB 스토리지 엔진은 MySQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 `레코드 기반의 잠금 방식`을 탑재하고 있다. 이러한 방식 덕분에 MyISAM 보다 뛰어난 동시성 처리를 제공한다. + +### 5.3.1 InnoDB 스토리지 엔진의 잠금 + +> InnoDB는 레코드 기반의 잠금 기능을 제공하며, 잠금 정보가 상당히 작은 공간으로 관리되기 때문에 레코드 락이 페이지락 또는 테이블 락으로 레벨업되는 경우가 없다. + +- `레코드 락(Record lock, Record only lock)` + - 레코드 자체만을 잠그는 락이며, 다른 사용 DBMS의 레코드 락과 동일한 역할을 한다. + - 한 가지 중요한 차이는 일반적인 DBMS의 경우 레코드 락은 레코드 자체를 락하지만, InnoDB의 경우 인덱스나 자동 생성된 클러스터 인덱스를 이용해 잠금을 설정한다. + - InnoDB에서는 대부분 **보조 인덱스**를 이용한 변경 작업은 넥스트 키 락 또는 갭 락을 사용하지만, **PK** 또는 **유니크 인덱스**에 의한 변경 작업에서는 갭에 대해서는 잠그지 않고 레코드 자체에 대해서만 락을 건다. +- `갭 락(Gap lock)` + - 일반 DBMS와 또 다른 차이로, 레코드 자체가 아닌 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 락이다. + - 레코드와 레코드 사이의 간격에 새로운 레코드가 생성(INSERT)되는 것을 제어하는 역할이다. + - 갭 락은 그 자체보다는 넥스트 키 락의 일부로 사용된다. +- `넥스트 키 락(Next key lock)` + - 레코드 락과 갭 락을 합쳐 놓은 형태의 잠금이다. + - InnoDB의 갭 락이나 넥스트 키 락은 바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때, 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 목적이다. + - 그런데 넥스트 키 락과 갭 락으로 인해 데드락이 발생하거나 다른 트랜잭션을 기다리게 만드는 일이 자주 발생한다. + - 가능하다면 바이너리 로그 포맷을 ROW 형태로 바꿔서 넥스트 키 락이나 갭 락을 줄이는 것이 좋다. +- `자동 증가 락(Auto Increment lock)` + - MySQL에서는 자동 증가하는 숫자 값을 추출하기 위해 `AUTO_INCREMENT`라는 칼럼 속성을 제공한다. + - `AUTO_INCREMENT` 칼럼이 사용된 테이블에 동시에 여러 `INSERT`가 되면, 각 레코드는 중복되지 않고 저장된 순서대로 증가하는 일련번호 값을 가져야한다. + - 이를 위해서 InnoDB에서는 내부적으로 `AUTO_INCREMENT 락` 이라고 하는 테이블 수준의 잠금을 사용한다. + - AUTO_INCREMENT 락은 INSERT와 REPLACE 쿼리 같이 새로운 레코드를 저장하는 쿼리에서만 필요하다. + - 레코드 락이나 넥스트 키 락과는 달리, 트랜잭션과 관계없이 INSERTL나 REPLACE 문장에서 AUTO_INCREMENT 값을 가져오는 순간만 락이 걸렸다가 즉시 해제된다. + - AUTO_INCREMENT 락은 테이블에 단 하나만 존재하기 때문에, 다른 쿼리는 기다려야 한다. + - AUTO_INCREMENT 락을 명시적으로 획득하고 해제하는 방법은 없다. + +
+ +### 5.3.2 인덱스와 잠금 + +InnoDB의 잠금은 레코드를 잠그는 것이 아니라 인덱스를 잠그는 방식으로 처리된다. +- 즉, 변경해야 할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 락을 걸어야 한다. + - employees 테이블에 first_name 컬럼이 인덱스가 걸려있고, first_name을 포함한 조건으로 레코드를 찾는다면 first_name 레코드가 모두 잠기게 된다. + - InnoDB의 이런 방식은 동시에 많은 레코드에 락을 거는 경우 발생할 수 있고, 이런 경우는 쿼리에 대해 적절한 인덱스가 준비돼 있지 않을 때 발생한다. +- 만약 테이블에 인덱스가 하나도 없다면 테이블이 풀 스캔하면서 작업을 처리하는데, 테이블에 있는 모든 레코드를 잠그게 된다. + - 따라서 MySQL의 InnoDB에서는 인덱스 설계가 중요하다. + +
+ +### 5.3.3 레코드 수준의 잠금 확인 및 해제 + +InnoDB를 사용하는 레코드 수준 잠금은 테이블 수준의 잠금보다는 조금 더 복잡하다. +- 테이블 잠금에서는 잠금의 대상이 테이블 자체이므로 쉽게 문제의 원인이 발견되고 해결될 수 있다. +- 하지만 레코드 수준의 잠금은 테이블의 레코드 각각에 잠금이 걸리므로 그 레코드가 자주 사용되지 않는다면 오랜 시간 동안 잠겨진 상태로 남아 있어도 잘 발견되지 않는다. + +MySQL 5.1 부터는 `레코드 잠금`과 `잠금 대기`에 대한 조회가 가능하므로 쿼리 하나만 실행해 보면 잠금과 잠금 대기를 바로 확인할 수 있다. + +
+
+ +## [5.4 MySQL의 격리 수준](#목차) + +> 트랜잭션의 격리 수준(isolation level)이란 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지 결정하는 것이다. + +격리 수준은 아래와 같이 4가지 나뉜다. 또한 아래로 내려갈수록 각 트랜잭션 간의 데이터 격리 정도가 높아지며, 동시성 처리 성능은 떨어지지만 데이터의 정합성은 높아진다. + +| 격리 수준 | DIRTY READ | NON-REPEATABLE READ | PHANTOM READ | +|:------------------:|:------------:|:---------------------:|:---------------:| +| READ UNCOMMITTED | O | O | O | +| READ COMMITTED | X | O | O | +| REPEATABLE READ | X | X | O (InnoDB는 X) | +| SERIALIZABLE | X | X | X | + +- InnoDB에서는 독특한 특성 때문에 `REPEATABLE READ` 격리 수준에서도 `PHANTOM READ`가 발생하지 않는다. +- 오라클 같은 DBMS에서는 주로 `READ COMMITTED` 수준을 많이 사용하며, MySOL에서는 `REPEATABLE READ`를 주로 사용한다. + +
+ +### 5.4.1 READ UNCOMMITTED + + + +- 트랜잭션에서 처리한 데이터를 COMMIT 이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 데이터를 조회할 수 있게 허용하는 격리 수준이다. + - `더티 리드(Dirty read)`: 어떤 트랜잭션에서 처리한 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상 +- DBMS 표준에서는 트랜잭션의 격리 수준으로 인정하지 않을 정도로 정합성에 문제가 많은 격리 수준이다. + +
+ +### 5.4.2 READ COMMITTED + + + +- 어떤 트랜잭션에서 변경한 내용을 COMMIT 한 이후에만 다른 트랜잭션이 해당 데이터를 조회할 수 있게 허용하는 격리 수준이다. + - 커밋되기 전에 다른 트랜잭션이 변경 내역을 조회하면, 언두 영역에 백업된 레코드에서 조회한다. +- 오라클 DBMS에서 기본으로 사용되는 격리 수준으로, 이 격리 수준에서는 `DIRTY READ`가 발생하지 않는다. + +하지만 `NON-REPEATABLE READ` 라는 부정합 문제가 발생할 수 있다. + + + +- 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 한다는 `REPEATABLE READ` 정합성에 어긋나는 문제가 발생한다. + +트랜잭션 내에서 실행되는 SELECT 문장과 트랜잭션 없이 실행되는 SELECT 문장의 차이 +- READ `COMMITTED` 격리 수준에서는 트랜잭션 내에서 실행되는 SELECT 문장과 트랜잭션 외부에서 실행되는 SELECT 문장의 차이가 별로 없다.\ +- 하지만 `REPEATABLE READ` 격리 수준에서는 기본적으로 SELECT 쿼리 문장도 트랜잭션 범위 내에서만 작동한다. + - `START TRANSACTION`(또는 `BEGIN`) 명령으로 트랜잭션을 시작한 상태에서 온종일 동일한 쿼리를 반복해서 실행해 봐도 동일한 결과만 보게 된다. + - 다른 트랜잭션에서 그 데이터를 변경하고 COMMIT을 실행해도 동일하게 나온다. + + +
+ +### 5.4.3 REPEATABLE READ + +> InnoDB에서 기본적으로 사용하는 격리 수준이다. 바이너리 로그를 가진 MySQL 서버에서는 최소 `REPEATABLE READ` 격리 수준 이상을 사용해야 한다. + + + +- 한 트랜잭션에서 항상 같은 값을 반환받는 것을 보장하는 격리 수준이다. + - `REPEATABLE READ`는 `MVCC`를 위해 언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장한다. + - `READ COMMITTED`도 `MVCC`를 이용해 COMMIT 되기 전의 데이터를 보여준다. + - 둘의 차이는 언두 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 이전 버전까지 찾아 들어야 하느냐에 있다. +- 이 격리 수준에서는 `NON-REPEATABLE READ`가 발생하지 않는다. + +InnoDB 트랜잭션 번호와 언두 데이터 +- 모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적으로 증가하는 값)를 가지며, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함돼 있다. +- InnoDB는 불필요하다고 판단한 언두 데이터를 주기적으로 삭제하지만, `REPEATABLE READ` 격리 수준에서는 `MVCC`를 유지하기 위해 가장 오래된 트랜잭션보다 번호가 앞선 언두 데이터는 삭제할 수 없다. +- 정확히는 특정 트랜잭션 번호의 구간 내에서 백업된 언두 데이터가 보존되어야 한다. + +작동 방식 +- `BEGIN`으로 트랜잭션을 시작하면서 트랜잭션 번호를 부여받고, 그 트랜잭션 안에서 실행되는 모든 SELECT 쿼리는 트랜잭션 번호가 자신보다 작은 트랜잭션 번호에서 변경한 것만 보게 된다. +- 한 사용자가 트랜잭션을 시작하고 장시간 트랜잭션을 종료하지 않으면 언두 영역이 백업된 데이터로 무한정 커질 수도 있고, 이렇게 언두 영역이 커지면 MySQL 서버의 처리 성능이 떨어질 수 있다. + + +하지만 `PHANTOM READ` 라는 부정합 문제가 발생할 수 있다. + + + +- `SELECT ... FOR UPDATE` 쿼리는 레코드에 쓰기 잠금을 걸지만, 언두 레코드에는 잠금을 걸 수 없어 현재 레코드 값을 반환한다. +- 따라서 `PHANTOM READ`가 발생하게 된다. + - `PHANTOM READ`: 한 트랜잭션에서 같은 SELECT 쿼리문을 두 번 이상 실행하면 그 사이에 다른 트랜잭션이 레코드를 추가되거나 삭제되면서 처음에 읽은 데이터와 다른 데이터를 읽게 되는 현상이다. + - 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 한다. + + +
+ +### 5.4.4 SERIALIZABLE + +- 가장 단순한 격리 수준이면서 가장 엄격한 격리 수준이다. +- 동시 처리 성능이 떨어지며 읽기 작업도 공유 잠금을 요구한다. +- 이 격리 수준에서는 `PHANTOM READ` 문제가 발생하지 않으며, 트랜잭션 간 레코드 접근이 완전히 차단된다. +- InnoDB의 `REPEATABLE READ` 격리 수준에서도 갭 락과 넥스트 키 락으로`PHANTOM READ`가 방지되므로 `SERIALIZABLE` 사용 필요성이 낮다. + +
+