MySQL 8のSKIP LOCKED・NOWAIT(ロッキングリードオプション)を試してみる

データベース

行ロックを利用してレコードを参照する方法をロッキングリードと呼びます。ロッキングリードを利用することで参照レコードの情報を保護できます。

ロッキングリードには共有ロックを利用するSELECT ... LOCK IN SHARE MODE(MySQL 8以前)・SELECT ... FOR SHARE(MySQL 8)と、排他ロックを利用するSELECT ... FOR UPDATEがあります。

MySQL 8.0からロッキングリードにSKIP LOCKEDNOWAITのオプションが追加され、ロッキングリードの挙動を変更できるようになりました。1

今回はMySQL 8で追加されたロッキングリードオプションであるSKIP LOCKEDNOWAITについて紹介します。

ロッキングリードオプションの挙動

SKIP LOCKEDNOWAITの挙動は以下の通りです。

ロッキングリードオプション 挙動
SKIP LOCKED 選択対象のレコードがロック中の場合SQL実行をスキップする
NOWAIT 選択対象のレコードがロック中の場合ただちSQL実行を失敗にする

ロッキングリードオプションの使い方

ロッキングリードオプションはSQLの最後に追記します。
たとえばSELECT ... FOR UPDATENOWAITを利用したい場合は、SELECT ... FOR UPDATE NOWAITとなります。
なお、SELECT ... LOCK IN SHARE MODEではロッキングオプションは利用できません。

ロッキングオプションの挙動の確認

以下のようなusersテーブルを作成し、ロッキングオプションの挙動を確認してみます。

> SELECT * FROM users\G;
*************************** 1. row ***************************
        id: 1
 last_name: Franecki
first_name: Cherri
       age: 21
*************************** 2. row ***************************
        id: 2
 last_name: Conroy
first_name: Columbus
       age: 18
2 rows in set (0.00 sec)

トランザクション内で排他ロックを作成するUPDATEを実行します。
排他ロックのSQLが実行されているターミナルを『セッションA』と呼ぶことにします。

セッションA

-- トランザクション開始
-- 『BEGING』のかわりに『START TRANSACTION』でもOK
> BEGIN;
Query OK, 0 rows affected (0.01 sec)

-- トランザクション内でid=1のageを21から22に更新する
> UPDATE users SET age = 22 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

『セッションA』で排他ロック中のレコードを別ターミナル『セッションB』からアクセスしたときの挙動について検証していきます。

ロッキングリードオプションなしの場合

排他ロック中のレコードにアクセスするとロック待ちになります。

セッションB

> SELECT * FROM users WHERE id = 1 FOR UPDATE \G;
-- 結果が返ってこない

SKIP LOCKEDの場合

SKIP LOCKEDを利用すると、ロック待ちのレコードへのアクセスはスキップされます。
ロック待ちにならないため結果がすぐに返ってきます。

セッションB

-- ロック中のid = 1の取得はスキップされる
> SELECT * FROM users WHERE id = 1 FOR UPDATE SKIP LOCKED \G;
Empty set (0.00 sec)

-- ロックされていないレコードは取得できる
> SELECT * FROM users FOR UPDATE SKIP LOCKED \G;
*************************** 1. row ***************************
        id: 2
 last_name: Conroy
first_name: Columbus
       age: 18

NOWAITの場合

NOWAITを利用すると、ロック待ちのレコードへのアクセスはエラーになります。
ロック待ちにならないためエラーがすぐに表示されます。

セッションB

-- ロック待ちのレコードにアクセスした場合はエラーになる
> SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT \G;
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.

-- アクセス対象のレコードの一部がロック待ちでもエラーになる
> SELECT * FROM users FOR UPDATE NOWAIT \G;
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考