이 기사에서는 MVCC 원리, RR 생성 팬텀 읽기, RR 해결 팬텀 읽기 등을 포함하여 RR 및 팬텀 읽기에 대한 관련 내용을 주로 소개하는 mysql에 대한 관련 지식을 제공합니다. 다음은 살펴보겠습니다. 그것이 모두에게 도움이 되기를 바랍니다.
추천 학습: mysql 비디오 튜토리얼
이 기사는 이 세 가지 주제에 중점을 두고 있습니다. RR은 팬텀 리딩을 어떻게 해결합니까?
MVCC 원리
실험: RR 및 팬텀 읽기
사례: 교착 상태
먼저 MySQL에서 InnoDB가 지원하는 4가지 유형의 트랜잭션 격리 및 동시 트랜잭션을 검토해 보겠습니다. 몇 가지 문제 :
Read uncommitted: ACID 특성에 위배되는 트랜잭션의 중간 프로세스를 읽을 수 있으며 기본적으로 사용되지 않는 더티 읽기 문제가 있습니다.
커밋 읽기: 다른 트랜잭션이 제출된 경우 이를 볼 수 있음을 나타냅니다. 프로덕션 환경에서는 많이 사용되지 않습니다.
반복 읽기: 기본 수준, 가장 많이 사용되는 수준입니다. 갭 잠금 기능이 있습니다.
직렬화 가능: 모든 구현은 잠금을 통해 구현됩니다.
동시 트랜잭션 처리는 더티 읽기, 반복 불가능한 읽기, 팬텀 읽기 등 몇 가지 문제도 발생합니다.
더티 읽기: 트랜잭션이 완료되어 제출되기 전에 이 레코드는 다음과 같습니다. 일관성이 없는 상태입니다.
비반복 읽기: 동일한 쿼리 조건에 따라 트랜잭션을 두 번 읽어 읽은 데이터가 일치하지 않습니다(수정, 삭제).
팬텀 리딩(Phantom reading): 트랜잭션 내에서 동일한 쿼리 조건에 따라 데이터를 다시 쿼리하지만 다른 트랜잭션에서 해당 쿼리 조건을 만족하는 새로운 데이터가 삽입되었음을 발견합니다.
이 글의 맥락을 요약하자면: RR은 더 빠른 동시성을 위해 MVCC를 도입하지만 팬텀 읽기를 해결하기 위해 Gap 잠금이 도입되며 Gap으로 인해 교착 상태가 발생할 수 있습니다.
MVCC(다중 버전 제어): 높은 동시 데이터 액세스, 데이터의 다중 버전 처리 및 트랜잭션 가시성을 통해 트랜잭션이 무엇을 해야 하는지 확인할 수 있도록 하기 위해 데이터베이스를 참조합니다. 데이터 버전을 참조하세요.
MVCC의 가장 큰 장점은 읽기에 대한 잠금이 없고 읽기와 쓰기에 충돌이 없다는 것입니다.
OLTP(온라인 트랜잭션 처리) 애플리케이션에서는 읽기와 쓰기 사이에 충돌이 없는 것이 중요합니다. 거의 모든 RDBMS가 MVCC를 지원합니다.
참고: MVCC는 읽기-커밋 RC와 반복 가능한 읽기 RR의 두 가지 격리 수준에서만 작동합니다.
참고: MVCC는 읽기-커밋 RC와 반복 가능한 읽기 RR의 두 가지 격리 수준에서만 작동합니다.
참고: MVCC는 읽기-커밋 RC와 반복 가능한 읽기 RR의 두 가지 격리 수준에서만 작동합니다.
(1) MVCC 다중 버전 구현
MySQL은 MVCC 메커니즘을 구현할 때 undo 로그 다중 버전 체인 + ReadView 메커니즘을 기반으로 합니다.
Undo 로그 다중 버전 체인: 데이터베이스가 수정될 때마다 현재 수정 레코드의 트랜잭션 번호와 수정 전 데이터 상태의 저장 주소(예: ROLL_PTR)가 Undo 로그에 기록되므로 필요한 경우 이전 데이터 버전으로 복원할 수 있습니다.
ReadView 메커니즘: 다중 버전 체인을 기반으로 트랜잭션 읽기의 가시성을 제어합니다. (주요 차이점은 RC와 RR입니다.)
여기서 원칙을 탐구하는 데 중점을 두지는 않지만 일반적인 개념인 실행 취소 로그 다중 버전 체인 및 ReadView 메커니즘이 필요합니다.
실행 취소 로그 다중 버전 체인의 경우 예는 다음과 같습니다.
읽기 트랜잭션이 현재 레코드를 쿼리하지만 최신 트랜잭션이 아직 제출되지 않았습니다.
원자성에 따라 읽기 트랜잭션은 최신 데이터를 볼 수 없지만 롤백 세그먼트에서 이전 버전의 데이터를 찾을 수 있으므로 여러 버전이 생성됩니다.
ReadView 메커니즘의 경우: 실행 취소 로그 다중 버전 체인 구현을 기반으로 하며 다양한 트랜잭션 격리에는 다양한 처리가 있습니다.
RC 수준 트랜잭션: 가시성이 비교적 높으며 제출된 트랜잭션의 모든 수정 사항을 볼 수 있습니다.
RR 수준 트랜잭션: 읽기 트랜잭션에서는 다른 트랜잭션이 데이터를 어떻게 수정하고 제출했는지 여부에 관계없이 제출하지 않는 한 쿼리 데이터 결과는 변경되지 않습니다.
이 작업은 어떻게 수행되나요?
RC 읽기 제출: 각 읽기 작업 문은 각 업데이트 후 데이터베이스의 최신 트랜잭션 제출 상태를 가져오고 최근 제출된 트랜잭션을 볼 수 있습니다. 즉, 각 문 실행이 가시성을 업데이트합니다. 보다.
RR 반복 읽기: 트랜잭션을 시작할 때 ReadView를 얻지 못합니다. ReadView는 첫 번째 스냅샷 읽기가 시작될 때만 획득됩니다.
현재 읽기를 사용하면 새로운 ReadView가 제공되며 업데이트된 데이터도 볼 수 있습니다.
(2) 스냅샷 읽기 및 현재 읽기
MVCC 동시성 제어에서 읽기 작업은 두 가지 범주로 나눌 수 있습니다.
스냅샷 읽기: 레코드의 표시 가능한 버전(아마도 기록 버전)을 읽습니다. 잠글 필요가 있습니다.
작업: 간단한 SELECT 작업입니다.
현재 읽기: 최신 버전의 레코드를 읽고, 현재 읽기에서 반환된 레코드는 다른 트랜잭션이 이 레코드를 동시에 수정하지 못하도록 잠깁니다.
작업: 특수 읽기 작업, 추가/업데이트/삭제 작업.
-- 对应 SQL 如下: -- 1. 特殊读操作 SELECT ... FOR UPDATE SELECT ... LOCK IN SHARE MODE -- 共享锁 -- 2. 新增:INSERT -- 3. 更新:UPDATE -- 4. 删除:DELETE
ReadView 메커니즘과 결합하여 스냅샷 읽기와 현재 읽기를 구별합니다.
스냅샷 읽기: 트랜잭션에서 ReadView는 첫 번째 스냅샷 읽기가 시작될 때만 획득되며 후속 읽기 작업에서는 다시 획득되지 않습니다.
현재 읽기: 각 읽기 작업마다 ReadView를 얻습니다.
인터뷰 질문: RR 트랜잭션 격리 수준에서 트랜잭션 A는 데이터 조각을 쿼리하고 트랜잭션 B는 데이터 조각을 추가합니다.
이 질문은 비교적 모호하지만 일반적인 검사 지점은 RR과 팬텀 리딩이라는 것을 알고 있습니다. 질문은 두 가지 범주로 나눌 수 있습니다.
RR은 어떤 상황에서 팬텀 리딩을 생성합니까? (데이터를 볼 수 있습니다)
답변: 현재 읽기(SELECT..FOR UDPDATE, SELECT... LOCK IN SHARE MODE)
RR은 어떤 상황에서 팬텀 읽기를 해결합니까? (데이터를 볼 수 없음)
답변: 잠금, 스냅샷 읽기
참고: 반복 불가능 읽기는 UPDATA 및 DELETE에 초점을 맞추고 팬텀 읽기는 INSERT에 중점을 둡니다.
이들 사이의 가장 큰 차이점은 잠금 메커니즘을 통해 발생하는 문제를 해결하는 방법입니다.
여기서 언급된 잠금 장치는 비관적 잠금 메커니즘만 사용합니다.
다시 검토해 보겠습니다. Phantom reading
-- 举个栗子:有这样一个查询 SQL SELECT * FROM user WHERE id < 10;
동일한 트랜잭션에서 T1에서는 4개의 데이터가 쿼리되고 T2에서는 8개의 데이터가 쿼리됩니다. 이것은 팬텀 판독을 생성합니다.
동일한 트랜잭션에서 T1 시간에 8개의 데이터가 쿼리되고 T2 시간에 4개의 데이터가 쿼리됩니다. 이것은 팬텀 판독을 생성합니다.
실험 준비는 다음과 같습니다. 실습
show variables like 'transaction_isolation'; -- 事务隔离级别 RR select version(); -- 版本 8.0.16 show variables like '%storage_engine%'; -- 引擎 InnoDB -- 1. 手动开启事务提交 begin; -- 开始事务 commit; -- 提交事务 -- 2. 创建表 CREATE TABLE IF NOT EXISTS `student` ( `id` INT NOT NULL COMMENT '主键 id', `name` VARCHAR(50) NOT NULL COMMENT '名字', `age` TINYINT NOT NULL COMMENT '年龄', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT '学生表'; -- 3. 新增数据用于实验 INSERT INTO student (id, name, age) VALUES (5, 'kunkun', 14); INSERT INTO student (id, name, age) VALUES (30, 'ikun', 18);
(1) RR은 가상 판독값을 생성합니다
실험은 다음과 같습니다. 현재 판독값을 테스트합니다.
실험 1: 먼저 SELECT를 수행한 다음 SELECT합니다. .. FOR UPDATE
실험 2 : 먼저 SELECT를 한 다음 UPDATE(팬텀 읽기가 발생하지 않음)
실험 1: 먼저 SELECT를 한 다음 SELECT... FOR UPDATE
-- 事务A: BEGIN; SELECT * FROM student WHERE id < 30; SELECT * FROM student WHERE id < 30 FOR UPDATE; -- 等待事务B commit 后再执行 -- SELECT * FROM student WHERE id < 30 LOCK IN SHARE MODE; COMMIT; -- 事务B: BEGIN; INSERT INTO student (id, name, age) VALUES (20, 'wulikun', 16); COMMIT;
현상은 아래 그림과 같습니다.
실험 기록은 아래 사진과 같습니다.
현상의 결론: 현재 읽기(SELECT ... FOR UPDATE)를 사용하면 팬텀 읽기가 발생합니다.
공유 모드에서 SELECT ... LOCK을 사용하면 유령 읽기가 생성됩니다.
실험 2: SELECT를 먼저 한 후 UPDATE
-- 事务A: BEGIN; SELECT * FROM student WHERE id < 30; UPDATE student SET name = 'zhiyin' WHERE id = 5; -- 等待事务B commit 后再执行 SELECT * FROM student WHERE id < 30; COMMIT; -- 事务B: BEGIN; INSERT INTO student (id, name, age) VALUES (20, 'wulikun', 16); COMMIT;
현상은 아래 그림과 같습니다.
실험 기록은 아래 그림과 같습니다.
현상의 결론: 현재 읽기( UPDATE)는 가상 읽기를 생성하지 않습니다. INSERT / DELETE 모두 동일한 작업을 수행하지 않습니다.
(2) RR은 팬텀 읽기를 해결합니다.
실험은 다음과 같습니다.
실험 1: 스냅샷 읽기
잠금(존재하지 않는 항목 업데이트) 기록)
실험 3: 잠금 (SELECT... FOR UPDATE)
실험 1: 스냅샷 읽기, 일반 SELECT
-- 事务A: BEGIN; SELECT * FROM student; SELECT * FROM student; -- 等待事务B commit 后再执行 COMMIT; -- 事务B: BEGIN; INSERT INTO student (id, name, age) VALUES (20, 'wulikun', 16); COMMIT;
상황은 아래 그림과 같습니다.
실험 기록이 나와 있습니다. 아래 그림에서:
현상의 결론: RR 트랜잭션 격리 수준에서는 스냅샷 읽기(SELECT)만 팬텀 읽기를 발생시키지 않습니다. 현재 읽은 내용이 없습니다.
실험 2: 잠금(존재하지 않는 레코드 업데이트)
RR 격리 수준에서 트랜잭션 A는 UPDATE를 사용하여 잠그고 트랜잭션 B는 그 사이에 새 데이터를 삽입할 수 없으므로 트랜잭션 A는 UPDATE 전후에 데이터를 읽습니다. 일관성을 유지하고 팬텀 읽기가 방지됩니다.
-- 事务A: BEGIN; SELECT * FROM student; UPDATE student SET name = 'wulikunkun' WHERE id = 18; -- 记录不存在,产生间隙锁 (5, 30)。 COMMIT; -- 事务B: BEGIN; INSERT INTO student (id, name, age) VALUES (10, 'zhiyin', 16); -- 需要等待事务A结束。 COMMIT; -- 事务C: BEGIN; INSERT INTO student (id, name, age) VALUES (40, 'zhiyin你太美', 32); COMMIT; -- 查询数据库中当前有哪些锁 SELECT INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA FROM performance_schema.data_locks;
상황은 아래 사진과 같습니다
实验记录如下图所示:
现象结论:
一开始先加 临键锁Next-key lock,锁范围为 (5,30]。
因为是唯一索引,且更新的记录不存在,临键锁退化成 间隙锁Gap,最终锁范围为 (5,30)。其余的记录不受影响。
实验三:加锁(SELECT ... FOR UPDATE)
-- 事务A: BEGIN; SELECT * FROM student; SELECT * FROM student WHERE id < 5 FOR UPDATE; COMMIT; -- 事务B: BEGIN; INSERT INTO student (id, name, age) VALUES (4, 'zhiyin', 4); -- 需要等待事务A结束。 COMMIT; -- 事务C: BEGIN; INSERT INTO student (id, name, age) VALUES (5, 'zhiyin你太美', 32); -- 插入成功 COMMIT; -- 查询数据库中当前有哪些锁 SELECT INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA FROM performance_schema.data_locks;
发生情况如下图所示:
实验记录如下图所示:
现象结论:
先加 临键锁Next-key lock,锁范围为 (-∞,5]。
所以,id
拓展:Gap 锁(间隙锁)
根据 官方文档 可知:
锁是加在索引上的。
记录锁: 行锁,只会锁定一条记录。
间隙锁 :是在索引记录之间的间隙上的锁,区间为前开后开 (,)。
临键锁(Next-Key Lock): 由 记录锁 和 间隙锁Gap 组合起来。
加锁的基本单位是 临键锁,其加锁区间为前开后闭 (,]。
索引上的等值查询,给唯一索引加锁的时候,如果满足条件,临键锁 退化为 行锁。
索引上的等值查询,给唯一索引加锁的时候,如果不满足条件,临键锁 退化为 间隙锁。注意,非等值查询是不会优化的。
推荐学习:mysql视频教程
위 내용은 mysql의 RR 및 팬텀 읽기 관련 문제의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!