QA@IT
«回答へ戻る

s/ネクストキーロック/ギャップロック/

942
-`SELECT ~ FOR UPDATE` が空振りしたことで InnoDB のネクストキーロックによるデッドロックになったのだと思われます。
+`SELECT ~ FOR UPDATE` が空振りしたことで InnoDB のギャップロックによるデッドロックになったのだと思われます。
 
 例えば次のようなテーブル定義で・・・
 
 ```
 
 `SELECT ~ FOR UPDATE` で行がヒットしない場合、そのトランザクション中でその行(id が 4 の行)が存在しないことを保証する必要があります。
-ただし、存在しない行はロック出来ないのでネクストキーロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。
+ただし、存在しない行はロック出来ないのでギャップロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。
 
 そのため、2 つのトランザクション (A) と (B) が以下のように並列に実行されるとデッドロックします。
 

SELECT ~ FOR UPDATE が空振りしたことで InnoDB のギャップロックによるデッドロックになったのだと思われます。

例えば次のようなテーブル定義で・・・

CREATE TABLE z
(
  id INT NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO z (id) VALUES (2), (6);

次のSQLを並列に実行した場合、SELECT ~ FOR UPDATE が空振りするとタイミングによってはデッドロックします。

BEGIN;
SELECT * FROM z WHERE id = 4 FOR UPDATE;
INSERT INTO z VALUES (4);
COMMIT;

SELECT ~ FOR UPDATE で行がヒットしない場合、そのトランザクション中でその行(id が 4 の行)が存在しないことを保証する必要があります。
ただし、存在しない行はロック出来ないのでギャップロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。

そのため、2 つのトランザクション (A) と (B) が以下のように並列に実行されるとデッドロックします。

/* (A). 3~5 が共有ロック */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (B). 3~5 が共有ロック(共有なので競合しない) */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (A). (B) の共有ロックと競合する */
INSERT INTO z VALUES (4);

/* (B). (A) の共有ロックと競合する → デッドロック!*/
INSERT INTO z VALUES (4);
`SELECT ~ FOR UPDATE` が空振りしたことで InnoDB のギャップロックによるデッドロックになったのだと思われます。

例えば次のようなテーブル定義で・・・

```sql
CREATE TABLE z
(
  id INT NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO z (id) VALUES (2), (6);

```

次のSQLを並列に実行した場合、`SELECT ~ FOR UPDATE` が空振りするとタイミングによってはデッドロックします。

```sql
BEGIN;
SELECT * FROM z WHERE id = 4 FOR UPDATE;
INSERT INTO z VALUES (4);
COMMIT;
```

`SELECT ~ FOR UPDATE` で行がヒットしない場合、そのトランザクション中でその行(id が 4 の行)が存在しないことを保証する必要があります。
ただし、存在しない行はロック出来ないのでギャップロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。

そのため、2 つのトランザクション (A) と (B) が以下のように並列に実行されるとデッドロックします。

```sql
/* (A). 3~5 が共有ロック */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (B). 3~5 が共有ロック(共有なので競合しない) */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (A). (B) の共有ロックと競合する */
INSERT INTO z VALUES (4);

/* (B). (A) の共有ロックと競合する → デッドロック!*/
INSERT INTO z VALUES (4);
```

回答を投稿

SELECT ~ FOR UPDATE が空振りしたことで InnoDB のネクストキーロックによるデッドロックになったのだと思われます。

例えば次のようなテーブル定義で・・・

CREATE TABLE z
(
  id INT NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO z (id) VALUES (2), (6);

次のSQLを並列に実行した場合、SELECT ~ FOR UPDATE が空振りするとタイミングによってはデッドロックします。

BEGIN;
SELECT * FROM z WHERE id = 4 FOR UPDATE;
INSERT INTO z VALUES (4);
COMMIT;

SELECT ~ FOR UPDATE で行がヒットしない場合、そのトランザクション中でその行(id が 4 の行)が存在しないことを保証する必要があります。
ただし、存在しない行はロック出来ないのでネクストキーロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。

そのため、2 つのトランザクション (A) と (B) が以下のように並列に実行されるとデッドロックします。

/* (A). 3~5 が共有ロック */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (B). 3~5 が共有ロック(共有なので競合しない) */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (A). (B) の共有ロックと競合する */
INSERT INTO z VALUES (4);

/* (B). (A) の共有ロックと競合する → デッドロック!*/
INSERT INTO z VALUES (4);
`SELECT ~ FOR UPDATE` が空振りしたことで InnoDB のネクストキーロックによるデッドロックになったのだと思われます。

例えば次のようなテーブル定義で・・・

```sql
CREATE TABLE z
(
  id INT NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO z (id) VALUES (2), (6);

```

次のSQLを並列に実行した場合、`SELECT ~ FOR UPDATE` が空振りするとタイミングによってはデッドロックします。

```sql
BEGIN;
SELECT * FROM z WHERE id = 4 FOR UPDATE;
INSERT INTO z VALUES (4);
COMMIT;
```

`SELECT ~ FOR UPDATE` で行がヒットしない場合、そのトランザクション中でその行(id が 4 の行)が存在しないことを保証する必要があります。
ただし、存在しない行はロック出来ないのでネクストキーロックという方法で、↑のケースであれば id が 3 ~ 5 の行が共有ロックされたような状態になります。

そのため、2 つのトランザクション (A) と (B) が以下のように並列に実行されるとデッドロックします。

```sql
/* (A). 3~5 が共有ロック */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (B). 3~5 が共有ロック(共有なので競合しない) */
SELECT * FROM z WHERE id = 4 FOR UPDATE;

/* (A). (B) の共有ロックと競合する */
INSERT INTO z VALUES (4);

/* (B). (A) の共有ロックと競合する → デッドロック!*/
INSERT INTO z VALUES (4);
```