9cubed
ブログ | Tailwind | Vite | Python | MariaDB | Node.js | Linux | PowerShell | Docker | Git | その他 | 将棋ウォーズ | 歌の練習
< 前の記事

トランザクション分離レベル

次の記事 >

スナップショット

MariaDB

[MariaDB]ギャップロック・ネクストキーロック

公開日:2026-01-07
更新日:2026-01-07

1. 概要

ギャップロック(Gap Lock)とネクストキーロック(Next-Key Lock)についてです。

2. テストデータの作成

「排他ロック・共有ロック」と同じデータベースです。データだけ異なります。

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 のレコードは、条件が一致しないため、スキップします。
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 になるため要注意です。

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;
< 前の記事

トランザクション分離レベル

次の記事 >

スナップショット

YouTube X

新着一覧

  • テーブル結合(CROSS JOIN、INNER JOIN、LEFT JOIN)MariaDB
  • 楽観ロック・悲観ロックMariaDB
  • カレントリードMariaDB
  • インデックスMariaDB
  • 論理削除(ソフトデリート)MariaDB
  • awk(オーク)の使い方についてLinux
  • NOT NULL 制約と NULL を許容した時の動作MariaDB
  • 外部キー制約MariaDB
  • MySQL と MariaDB の関係MariaDB
  • Docker で PostgreSQL のコンテナの使用Linux

アーカイブ

  • 2026/01
  • 2025/12
  • 2025/11
  • 2025/10
  • 2025/09
  • 2025/08
  • /00

以前のカテゴリー一覧

  • CakePHP3
  • CentOS7
  • HTML・CSS・JavaScript
  • Haskell
  • JavaScript
  • Kotlin
  • Laravel5
  • PHP
  • Python
  • Ruby
  • RubyOnRails5
  • TypeScript
  • Vue.js
  • Webサーバ講座
  • Webプログラミング講座
  • jQuery
  • linux
  • パソコン講座
  • ブログ
  • プログラミング講座
  • メモ帳作成講座
  • 数学

Copyright © 9cubed. All Rights Reserved.

プライバシーポリシー 利用規約
▲