Loading...
MySQL 9.5 Reference Manual 9.5의 17.7.4 Phantom Rows의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
소위 phantom 문제는 하나의 트랜잭션 내에서 동일한 쿼리가 서로 다른 시점에 서로 다른 행 집합을 반환할 때 발생합니다. 예를 들어, SELECT를 두 번 실행했을 때, 두 번째 실행에서 첫 번째 실행에서는 반환되지 않았던 행이 반환된다면, 그 행은 “phantom” 행입니다.
child 테이블의 id 열에 인덱스가 있고, id 값이 100보다 큰 모든 행을 읽고 잠금을 건 다음, 나중에 선택된 행들의 어떤 열을 업데이트하려 한다고 가정합니다:
1SELECT * FROM child WHERE id > 100 FOR UPDATE;
이 쿼리는 id가 100보다 큰 첫 번째 레코드부터 인덱스를 스캔합니다. 테이블에 id 값이 90과 102인 행이 있다고 합시다. 스캔된 범위 내 인덱스 레코드에 설정된 잠금이 그 사이 갭(이 경우 90과 102 사이의 갭)에 대한 삽입을 막지 못한다면, 다른 세션이 id가 101인 새로운 행을 테이블에 삽입할 수 있습니다.
동일한 SELECT를 같은 트랜잭션 안에서 다시 실행하면, 쿼리가 반환하는 결과 집합에 id가 101인 새로운 행(“phantom”)이 보이게 됩니다. 행 집합을 하나의 데이터 항목으로 본다면, 이러한 새로운 phantom child는 트랜잭션이 읽은 데이터가 트랜잭션 동안 변경되지 않아야 한다는 트랜잭션의 격리 원칙을 위반하게 됩니다.
phantom을 방지하기 위해 InnoDB는 next-key locking이라 불리는 알고리즘을 사용하는데, 이는 인덱스-행 잠금과 갭 잠금을 결합한 것입니다. InnoDB는 테이블 인덱스를 검색하거나 스캔할 때, 만나게 되는 인덱스 레코드에 대해 공유 잠금 또는 배타 잠금을 설정하는 방식으로 행 단위 잠금을 수행합니다. 따라서 행 단위 잠금은 실제로 인덱스-레코드 잠금입니다.
추가로, 인덱스 레코드에 대한 next-key 잠금은 그 인덱스 레코드 앞의 “갭”에도 영향을 줍니다. 즉, next-key 잠금은 인덱스 레코드 잠금에 그 인덱스 레코드 바로 앞의 갭에 대한 갭 잠금이 더해진 것입니다. 한 세션이 인덱스 내 레코드 R에 대해 공유 잠금이나 배타 잠금을 가지고 있다면, 다른 세션은 인덱스 순서에서 R 바로 앞의 갭에 새로운 인덱스 레코드를 삽입할 수 없습니다.
InnoDB가 인덱스를 스캔할 때는 인덱스의 마지막 레코드 뒤의 갭을 잠글 수도 있습니다. 바로 앞선 예제에서 그런 일이 발생합니다. id가 100보다 큰 어떤 값에 대해서도 테이블에 삽입을 못 하게 하려면, InnoDB가 설정하는 잠금에는 id 값 102 뒤의 갭에 대한 잠금도 포함됩니다.
애플리케이션에서 유니크성 검사를 구현하기 위해 next-key locking을 사용할 수 있습니다. 데이터를 공유 모드로 읽을 때, 삽입하려는 행에 대한 중복을 보지 못했다면, 그 행을 안전하게 삽입할 수 있으며, 읽기 동안 해당 행의 후속 항목에 설정된 next-key 잠금이 그 사이에 누군가가 그 행에 대한 중복을 삽입하는 것을 막아 준다는 것을 알 수 있습니다. 따라서 next-key locking을 사용하면 테이블에서 어떤 것이 “존재하지 않는다”는 사실을 “잠그는” 것이 가능해집니다.
갭 잠금은 Section 17.7.1, “InnoDB Locking”에서 설명한 대로 비활성화할 수 있습니다. 이렇게 하면 갭 잠금이 비활성화된 상태에서 다른 세션이 갭에 새로운 행을 삽입할 수 있으므로 phantom 문제가 발생할 수 있습니다.
17.7.3 Locks Set by Different SQL Statements in InnoDB
17.7.5 Deadlocks in InnoDB