[MariaDB]ギャップロック・ネクストキーロック
公開日:2026-01-07
更新日:2026-01-07
更新日:2026-01-07
1. 概要
ギャップロック(Gap Lock)とネクストキーロック(Next-Key Lock)についてです。
2. テストデータの作成
「排他ロック・共有ロック」と同じデータベースです。データだけ異なります。
DB の作成
テーブルの作成
テストデータの作成
DB の作成
コマンド
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
テーブルの作成
コマンド
CREATE TABLE products (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
price INTEGER,
stock INTEGER DEFAULT 0,
PRIMARY KEY (id)
);
テストデータの作成
コマンド
DELETE FROM products;
INSERT INTO products (id, name, price, stock) VALUES (5, 'A', 500, 5), (10, 'B', 1000, 10), (15, 'C', 1500, 15);
データ
+----+------+-------+-------+
| id | name | price | stock |
+----+------+-------+-------+
| 5 | A | 500 | 5 |
| 10 | B | 1000 | 10 |
| 15 | C | 1500 | 15 |
+----+------+-------+-------+3. レコードロック(Record Lock)
指定された行のみをロックします。
4. ギャップロック(Gap Lock)
ギャップロックとは、レコード間のレコードが存在しない隙間をロックします。
これによりファントムリードを防ぐことができます。
レコードが存在する行はロックしません。
id = 5, 10, 15 のレコードがある時に、id between 8 and 12 で検索した場合、
id = 6 ~ 9, 11 ~ 14 のレコードの隙間がロックされます。
また、ギャップロックでロックされた部分は、INSERT だけがブロックされます。
SELECT ~ FOR UPDATE, UPDATE, DELETE はブロックされません。
これによりファントムリードを防ぐことができます。
レコードが存在する行はロックしません。
id = 5, 10, 15 のレコードがある時に、id between 8 and 12 で検索した場合、
id = 6 ~ 9, 11 ~ 14 のレコードの隙間がロックされます。
また、ギャップロックでロックされた部分は、INSERT だけがブロックされます。
SELECT ~ FOR UPDATE, UPDATE, DELETE はブロックされません。
処理の流れ
id = 5 のレコードは、条件が一致しないため、スキップします。
id = 10 のレコードは、条件が一致するため、前のレコードとの隙間(id = 6 ~ 9)をロックします。。
id = 15 のレコードは、条件が一致しないため、検索が終了となり、前のレコードとの隙間(id = 11 ~ 14)をロックします。
id = 10 のレコードは、条件が一致するため、前のレコードとの隙間(id = 6 ~ 9)をロックします。。
id = 15 のレコードは、条件が一致しないため、検索が終了となり、前のレコードとの隙間(id = 11 ~ 14)をロックします。
5. ネクストキーロック(Next-key Lock)
ネクストキーロックは、ギャップロックにレコードロックを加えてロックします。
MySQL でトランザクション分離レベルが REPEATABLE READ(デフォルト)の場合、範囲検索をするとネクストキーロックが使用されます。
id = 5, 10, 15 のレコードがある時に、id between 8 and 12 で検索した場合、
id = 6 ~ 15 のレコードの隙間がロックされます。
ロックの終了が検索条件範囲外の id = 15 になるため要注意です。
MySQL でトランザクション分離レベルが REPEATABLE READ(デフォルト)の場合、範囲検索をするとネクストキーロックが使用されます。
id = 5, 10, 15 のレコードがある時に、id between 8 and 12 で検索した場合、
id = 6 ~ 15 のレコードの隙間がロックされます。
ロックの終了が検索条件範囲外の id = 15 になるため要注意です。
6. 動作確認
データは上記のテストデータを使用します。
6.1 その1
AとBは異なるトランザクション
コマンド
A:BEGIN;
B:BEGIN;
A:SELECT * FROM products where id between 8 and 12 FOR UPDATE; # 6 ~ 15 がロックされる
B:次の中の1つを実行する。処理対象が id = 6 ~ 15 の場合は、ロックが解除されるまで待機する
UPDATE products SET stock = stock - 1 where id = 5; # 待機しない。要注意。ロック開始は次の行から。
INSERT INTO products (id, name, price, stock) VALUES (6, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (7, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (8, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (9, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
UPDATE products SET stock = stock - 1 where id = 10; # Aのトランザクションが終わるまで待機(レコードロック)
INSERT INTO products (id, name, price, stock) VALUES (11, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (12, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (13, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
INSERT INTO products (id, name, price, stock) VALUES (14, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
UPDATE products SET stock = stock - 1 where id = 15; # Aのトランザクションが終わるまで待機(レコードロック)
INSERT INTO products (id, name, price, stock) VALUES (16, 'Z', 1000, 10); # 待機しない
A:COMMIT;
B:ROLLBACK;6.2 その2
コマンド
A:BEGIN;
B:BEGIN;
A:SELECT * FROM products where id between 8 and 12 FOR UPDATE; # 6 ~ 15 がロックされる
B:次の中の1つを実行する。
SELECT * FROM products where id = 9 FOR UPDATE; # 待機しない (ギャップロック)
SELECT * FROM products where id = 10 FOR UPDATE; # Aのトランザクションが終わるまで待機(レコードロック)
SELECT * FROM products where id = 11 FOR UPDATE; # 待機しない (ギャップロック)
SELECT * FROM products where id = 15 FOR UPDATE; # Aのトランザクションが終わるまで待機(レコードロック)
A:COMMIT;
B:ROLLBACK;6.3 その3
コマンド
A:BEGIN;
B:BEGIN;
A:SELECT * FROM products where id > 8 FOR UPDATE; # 6 以降全てロックされる
B:次の中の1つを実行する。
SELECT * FROM products where id = 5 FOR UPDATE; # 待機しない (ロックされてない)
SELECT * FROM products where id = 9 FOR UPDATE; # 待機しない (ギャップロック)
SELECT * FROM products where id = 10 FOR UPDATE; # Aのトランザクションが終わるまで待機(レコードロック)
SELECT * FROM products where id = 11 FOR UPDATE; # 待機しない (ギャップロック)
SELECT * FROM products where id = 15 FOR UPDATE; # Aのトランザクションが終わるまで待機(レコードロック)
INSERT INTO products (id, name, price, stock) VALUES (16, 'Z', 1000, 10); # Aのトランザクションが終わるまで待機(ギャップロック)
A:COMMIT;
B:ROLLBACK;
