2025.07.24
Log::info()が使えない!?Laravel.logのPermission denied エラーを解決する(Docker環境)
2018.05.30
DBMySQL8で、排他ロックされたレコードがスキップできるようになった。
こんにちは、MTです。
今回は、MySQL8で SKIP LOKED
と NOWAIT
の2機能が追加されたので、実際に試してみました。
どちらも行ロックに関する機能で、
SKIP LOCKED
NOWAIT
というものです。
他のDBには同等の機能が備わっていたりするのですが、MySQLはやっと提供されたかという感じです。
従来(MySQL5.7まで)は上記のような機能は提供されていなかったので、
か
方法を取るしかありませんでした。
-- トランザクションA
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE;
+----+------+
| id | name |
+----+------+
| 1 | sato |
+----+------+
1 row in set (0.00 sec)
-- トランザクションB
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
-- トランザクションA で id が 1 のレコードはロックされているので、待ち続ける。
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
ロックされていた場合は直ちにエラーを返してきてほしいものですが、そのような機能は無いので待ち続けるしかありません。
また、ロックを判断するためのカラムに関しては、MySQLの仕組みにテーブル構造が引きずられてるので、できるだけ避けたい。
今回追加された SKIP LOCKED、NOWAIT は、上記の問題を解決します。
まずはデータの準備から。
IDと名前だけの user
テーブルを作成し、データを登録します。
mysql> CREATE TABLE user (
-> id INT PRIMARY KEY
-> , name VARCHAR(255) NOT NULL
-> );
Query OK, 0 rows affected (0.11 sec)
mysql> INSERT INTO user VALUES
-> (0, 'tanaka')
-> , (1, 'sato')
-> , (2, 'matsumoto');
Query OK, 3 rows affected (0.03 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM user;
+----+-----------+
| id | name |
+----+-----------+
| 0 | tanaka |
| 1 | sato |
| 2 | matsumoto |
+----+-----------+
3 rows in set (0.00 sec)
あるレコードを排他ロックする際、既にロックされている場合にはスキップする機能です。
例えば トランザクションA
で id
が 1
のレコードをロックし、トランザクションB
で全レコードをロックしようとした場合、
トランザクションA
でロックした id
が 1
のレコードは トランザクションB
でスキップされます。
-- トランザクションA
-- id が 1 のレコードをロックする。
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE;
+----+------+
| id | name |
+----+------+
| 1 | sato |
+----+------+
1 row in set (0.00 sec)
-- トランザクションB
-- 全レコードをロックする。
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM user FOR UPDATE SKIP LOCKED;
+----+-----------+
| id | name |
+----+-----------+
| 0 | tanaka |
| 2 | matsumoto |
+----+-----------+
2 rows in set (0.00 sec)
id
が 1
のレコードは抽出されていませんね。
SKIP LOCKED
は既にロックされていた場合にロックしない(スキップする)機能でしたが、NOWAIT
はロックできないと分かった時点で直ちに失敗にするための機能です。
少し話を戻しますが、SELECT FOR UPDATE SKIP LOCKED
の結果が「対象レコード無し」の場合、
「レコード自体が存在しない」のか「他でロックされている」のか
が分かりません。
-- トランザクションA
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE;
+----+------+
| id | name |
+----+------+
| 1 | sato |
+----+------+
1 row in set (0.00 sec)
-- トランザクションB
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE SKIP LOCKED;
Empty set (0.00 sec)
業務要件上、上記のようなケースが許容されている場合は良いですが「処理対象データのロックが取れない」場合にはエラーに倒したい場合が多いのではないでしょうか。
かつ、ロックが取れないと判明した時点で即時に。
このような場合は NOWAIT
を使用します。
-- トランザクションA
-- id が 1 のデータをロック。
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE;
+----+------+
| id | name |
+----+------+
| 1 | sato |
+----+------+
1 row in set (0.01 sec)
-- トランザクションB でも id が 1 のデータをロック。
-- ただし、既にロックされている場合には直ちにエラーとする。
mysql> BEGIN;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT * FROM user WHERE id = 1 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.
文章だと分かりませんが、SELECT文を発行した直後にエラーが返ってきます。
SKIP LOCKED
NOWAIT
【記事への感想募集中!】
記事への感想・ご意見がありましたら、ぜひフォームからご投稿ください!【テクノデジタルではエンジニア/デザイナーを積極採用中です!】
下記項目に1つでも当てはまる方は是非、詳細ページへ!Qangaroo(カンガルー)
【テクノデジタルのインフラサービス】
当社では、多数のサービスの開発実績を活かし、
アプリケーションのパフォーマンスを最大限に引き出すインフラ設計・構築を行います。
AWSなどへのクラウド移行、既存インフラの監視・運用保守も承りますので、ぜひご相談ください。
詳細は下記ページをご覧ください。
最近の記事
タグ検索