web-dev-qa-db-ja.com

Mysqlのコミット読み取り分離レベルでのギャップロック

MYSQL VERSION : 5.7.X
STORAGE ENGINE : Innodb

私は、Read Committed Isolationが主に共有および排他レコードロックを使用するという一般的な考えを持っています。しかし、mysql docs のように、コミットされた読み取りであってもギャップロックを使用する必要がある場合があります。

READ COMMITTED .....ロック読み取り(FOR UPDATEまたはLOCK IN SHARE MODEを使用したSELECT)、UPDATEステートメント、およびDELETEステートメントの場合、InnoDBはインデックスレコードのみをロックし、その前のギャップはロックしないため、新しいレコードを自由に挿入できます。ロックされたレコードの横。 ギャップロックは、外部キー制約チェックと重複キーチェックにのみ使用されます。

私見、レコードロックだけで十分です。誰かがギャップロックのシナリオを説明できますか、なぜmysqlはそれをするのですか?

5
Dinesh Kumar

並行挿入による潜在的な整合性違反を防止するために、コミット読み取り(RC)分離レベルではギャップロックが必要です-これがドキュメントステートメントです

ギャップロックは、外部キー制約チェックと重複キーチェックにのみ使用されます。

意味します。

トランザクションT1がRC分離で、一意のキー(インデックス)の一部である列の値を更新するとします。明らかにこれは、新しいキー値がまだ存在しない場合にのみ可能です。トランザクションT2も、RC分離を使用して、T1によって作成されたばかりのキー値に等しいキー値で新しいレコードを挿入しようとしているとします。 RC分離レベルのため、T2はまだコミットされていないため、T2はT1による変更を認識しません。したがって、重複キーエラーは発生しません。問題の一意のインデックスのギャップロックがなかった場合、これは最終的に同じと思われる一意のキーを持つ2つのレコードになります。

MySQL 8では、data_locksdata_lock_waitsおよびperformance_schemaビューを使用して、実際の動作を確認できます。このサンプルテーブルを作成しました。

create table fruits (
  id int not null auto_increment primary key, 
  name varchar(10), 
  property varchar(20)
);
create unique index fruits_ux1 on fruits (name, property);
insert into fruits (name, property) 
values ('Apple', 'red'), ('Apple', 'tart'), 
       ('banana', 'yellow'), ('banana', 'sweet');

トランザクション1で、バナナのプロパティを更新します。

T1> set transaction isolation level read committed;

T1> begin;

T1> update fruits set property = concat(property, '?') where name = 'banana';

新しい値のキーエントリにはギャップロックが設定されていることがわかります(これらの行はトランザクションがコミットされるまで実際には存在しないため、ここでは標準のXロックを使用できません)。

mysql> select thread_id, object_name, index_name, engine_lock_id, lock_type, lock_mode, lock_data  from performance_schema.data_locks;
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+
| thread_id | object_name | index_name | engine_lock_id                        | lock_type | lock_mode     | lock_data              |
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+
|        47 | fruits      | NULL       | 140491829069632:1066:140491735749880  | TABLE     | IX            | NULL                   |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:1:140491735746840 | RECORD    | X             | supremum pseudo-record |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:4:140491735746840 | RECORD    | X             | 'banana', 'yellow', 3  |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:5:140491735746840 | RECORD    | X             | 'banana', 'sweet', 4   |
|        47 | fruits      | PRIMARY    | 140491829069632:5:4:6:140491735747184 | RECORD    | X,REC_NOT_GAP | 4                      |
|        47 | fruits      | PRIMARY    | 140491829069632:5:4:7:140491735747184 | RECORD    | X,REC_NOT_GAP | 3                      |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:6:140491735747528 | RECORD    | X,GAP         | 'banana', 'sweet?', 4  |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:7:140491735747528 | RECORD    | X,GAP         | 'banana', 'yellow?', 3 |
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+

トランザクション2では、重複の可能性を挿入しようとしています。

T2> set transaction isolation level read committed;

T2> begin;

T2> insert into fruits (name, property) values ('banana', 'sweet?');

ステートメントがブロックされ、その一意のインデックスキーのギャップロックでブロックされていることがわかります(blocking_engine_lock_idの値に注意してください)。

mysql> select requesting_engine_lock_id, blocking_engine_lock_id from performance_schema.data_lock_waits;
+---------------------------------------+---------------------------------------+
| requesting_engine_lock_id             | blocking_engine_lock_id               |
+---------------------------------------+---------------------------------------+
| 140491829070536:5:5:6:140491735753104 | 140491829069632:5:5:6:140491735747872 |
+---------------------------------------+---------------------------------------+

同様に、T1が親テーブルから行を削除し、T2がT1によって削除されたばかりの行を参照する子テーブルに新しいレコードを挿入しようとすると(まだ表示されます)、外部キーインデックスのギャップロックにより、潜在的な孤立レコード。

7
mustaccio

ここから:

https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/

ギャップロックは、インデックスレコード間のギャップのロックです。このギャップロックのおかげで、同じクエリを2回実行すると、そのテーブルの他のセッションの変更に関係なく、同じ結果が得られます。これにより、読み取りの一貫性が保たれるため、サーバー間のレプリケーションの一貫性が保たれます。 SELECT * FROM id> 1000 FOR UPDATEを2回実行すると、同じ値が2回取得されることになります。これを実現するために、InnoDBはWHERE句によって検出されたすべてのインデックスレコードを排他ロックでロックし、それらの間のギャップを共有ギャップロックでロックします。

1
raphael75