この記事では、mysql に関する関連知識を提供します。主に、MVCC 原理、ファントム読み取りを生成する RR、ファントム読み取りを解決する RR など、RR とファントム読み取りに関する関連コンテンツを紹介します。以下の内容が皆様のお役に立てれば幸いです。
推奨学習: mysql ビデオ チュートリアル
この記事では、これら 3 つのトピックに焦点を当てます。 RR ファントム・リーディングを解決するにはどうすればよいですか?
2. MVCC 原則
MVCC の最大の利点は、読み取りにロックがなく、読み取りと書き込みの間に競合がないことです。
OLTP (オンライン トランザクション処理) アプリケーションでは、読み取りと書き込みの間に競合がないことが重要であり、ほとんどすべての RDBMS が MVCC をサポートしています。
注: MVCC は、読み取りコミット RC と反復読み取り RR の 2 つの分離レベルでのみ機能します。
注: MVCC は、読み取りコミット RC と反復読み取り RR の 2 つの分離レベルでのみ機能します。
注: MVCC は、読み取りコミット RC と反復読み取り RR の 2 つの分離レベルでのみ機能します。
(1) MVCC マルチバージョンの実装MySQL が MVCC メカニズムを実装する場合、それは Undo ログのマルチバージョン チェーンに基づいています。 ReadView メカニズム。
UNDO ログのマルチバージョン チェーンの例を次に示します。
RC 読み取り送信: 各読み取り操作ステートメントは ReadView を取得します。各更新後、データベース内の最新のトランザクション送信ステータスが取得され、最新に送信されたトランザクション、つまり各ステートメントを確認できます。実行すると可視性ビューが更新されます。
RR 反復読み取り: トランザクションの開始時に ReadView は取得されず、最初のスナップショット読み取りが開始されたときにのみ ReadView が取得されます。
現在の読み取りを使用すると、新しい ReadView が取得され、更新されたデータも表示されます。
#(2) スナップショット読み取りと現在の読み取り
MVCC 同時実行制御では、読み取り操作は 2 つのカテゴリに分類できます:スナップショット読み取り: 読み取られるのは、ロックされていない、レコードの表示されているバージョン (おそらく履歴バージョン) です。 操作: 単純な SELECT 操作。 現在の読み取り: レコードの最新バージョンが読み取られ、現在の読み取りによって返されたレコードは、他のトランザクションがこのレコードを同時に変更しないようにロックされます。 操作: 特殊な読み取り操作、追加/更新/削除操作。-- 对应 SQL 如下: -- 1. 特殊读操作 SELECT ... FOR UPDATE SELECT ... LOCK IN SHARE MODE -- 共享锁 -- 2. 新增:INSERT -- 3. 更新:UPDATE -- 4. 删除:DELETEReadView メカニズムと組み合わせて、スナップショット読み取りと現在の読み取りを区別します。 スナップショット読み取り: トランザクションでは、最初のスナップショット読み取りが開始されたときにのみ ReadView が取得され、その後の読み取りが開始されます。操作は再度取得されません。 現在の読み取り値: ReadView は読み取り操作ごとに取得されます。 3. 実験: RR とファントム読み取りインタビューの質問: RR トランザクション分離レベルでは、トランザクション A はデータの一部をクエリし、トランザクション B はデータの一部を追加し、トランザクション A はトランザクション B を参照してください。 データは? この質問は比較的曖昧ですが、一般的な検査ポイントは RR とファントム リーディングであることがわかっています。質問は 2 つのカテゴリに分類できます: RR はどのような状況でファントム リーディングを生成しますか? (データを見ることができます) 答え: 現在の読み取り (SELECT..FOR UDPDATE、SELECT ... LOCK IN SHARE MODE)RR はどのような状況でファントム読み取りを解決しますか? (データは表示されません)回答: ロック、スナップショット読み取り注: 反復不可能な読み取りは UPDATA と DELETE に重点を置き、ファントム読み取りは INSERT に重点を置きます。
両者の最大の違いは、ロック機構を通じて引き起こされる問題をどのように解決するかです。 ここで説明するロックは、悲観的ロック機構のみを使用します。
もう一度確認してみましょう: ファントム読み取り
-- 举个栗子:有这样一个查询 SQL SELECT * FROM user WHERE id < 10;同じトランザクションで、T1 時点で 4 個のデータがクエリされ、8 個のデータがクエリされますT2時。これによりファントムリーディングが発生します。 同じトランザクションで、時刻 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 IN SHARE MODE; を使用すると、ファントム読み取りが発生します。
#実験 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 はファントム読み取りを解決します 実験は次のとおりです。
-- 事务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 は新しいデータを挿入できません。これにより、UPDATE の前後でトランザクション A によって読み取られたデータの一貫性が保たれ、ファントム読み取りが回避されます。
-- 事务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 中国語 Web サイトの他の関連記事を参照してください。