[MariaDB]楽観ロック・悲観ロック
公開日:2026-01-16
更新日:2026-01-16
更新日:2026-01-16
1. 概要
排他制御の楽観ロック・悲観ロックについてです。
楽観ロックは「他の人が同時に同じデータを更新することは滅多にないだろう。もしそんなことがあったら逆立ちして町内を一周してやるよ。」と言う楽観的な考えで作られた排他制御で、
悲観ロックは「他の人が同時に同じデータを更新することがあるはずだ。もしそんなことされたら二度と立ち直れない。」と言う悲観的な考えで作られた排他制御です。
楽観ロックは「他の人が同時に同じデータを更新することは滅多にないだろう。もしそんなことがあったら逆立ちして町内を一周してやるよ。」と言う楽観的な考えで作られた排他制御で、
悲観ロックは「他の人が同時に同じデータを更新することがあるはずだ。もしそんなことされたら二度と立ち直れない。」と言う悲観的な考えで作られた排他制御です。
2. 使用する場所
悲観ロック:競合が許されない処理(決済、予約、在庫変更など)
楽観ロック:Web アプリの入力画面~更新処理
楽観ロック+リトライ:競合が許されない処理
楽観ロック:Web アプリの入力画面~更新処理
楽観ロック+リトライ:競合が許されない処理
3. 楽観ロック
テーブルに version を持たせて、入力画面に入った時に取得した version と、更新処理時の version を比較して、値が異なる場合は、誰かに変更されていると判断する方式です。
3.1 データベースとテーブル
コマンド
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
version INT DEFAULT 0 NOT NULL
);
INSERT INTO users (name, email) VALUES ('taro', 'taro@example.com');3.2 処理の流れ
入力画面に入った時に version を取得して、hidden などに入れます。
更新ボタンが押されたら、hidden などで保持していた verion を更新条件に入れて、更新します。
影響行数が 1 件の場合は、正常に更新されています。
影響行数が 0 件の場合は、更新失敗です。他の人によって変更されていることをユーザに通知してエラーにします。
入力画面に入ってから、入力に時間がかかる場合は、競合の確率が高くなります。
その場合、編集ユーザID(locked_by)と編集開始時刻(locked_at)をテーブルに保持して、入力画面に入った時に、他のユーザが編集中であることを通知したり、
入力画面の中で定期的に WebAPI で version を取得して、変更されている場合は、他のユーザに変更されたことを通知します。
コマンド
SELECT name, email, version FROM users WHERE ID = 1;
更新ボタンが押されたら、hidden などで保持していた verion を更新条件に入れて、更新します。
コマンド
UPDATE users
SET email = 'taro2@example.com', version = version + 1
WHERE ID = 1 AND version = { hidden で保持していた version }
影響行数が 1 件の場合は、正常に更新されています。
影響行数が 0 件の場合は、更新失敗です。他の人によって変更されていることをユーザに通知してエラーにします。
入力画面に入ってから、入力に時間がかかる場合は、競合の確率が高くなります。
その場合、編集ユーザID(locked_by)と編集開始時刻(locked_at)をテーブルに保持して、入力画面に入った時に、他のユーザが編集中であることを通知したり、
入力画面の中で定期的に WebAPI で version を取得して、変更されている場合は、他のユーザに変更されたことを通知します。
4. 悲観ロック
排他ロックをかけて、他のトランザクションからは同時に変更できないようにする方式です。
競合すると他のトランザクションはブロックされて待機するため、競合の確率が高い場合は、パフォーマンスが悪くなります。
ちなみに、上記の在庫を減らすだけの場合は、悲観ロック(事前にロック)にせずに、UPDATE の条件を追加することで、SQL の数を減らせます。
4.1 処理の流れ
コマンド
BEGIN;
# 排他ロック
SELECT 在庫数 FROM 在庫テーブル WHERE ID = 1 FOR UPDATE;
if (在庫数 - 注文数 < 0) {
エラーにする
}
# 更新
UPDATE 在庫テーブル SET 在庫数 = 在庫数 - 注文数 WHERE ID = 1;
# コミット
COMMIT;
競合すると他のトランザクションはブロックされて待機するため、競合の確率が高い場合は、パフォーマンスが悪くなります。
ちなみに、上記の在庫を減らすだけの場合は、悲観ロック(事前にロック)にせずに、UPDATE の条件を追加することで、SQL の数を減らせます。
コマンド
BEGIN;
# 更新
UPDATE 在庫テーブル SET 在庫数 = 在庫数 - 注文数 WHERE ID = 1 AND 在庫数 - 注文数 > 0;
影響行数が 0 の場合は、在庫不足のためエラーにする
# コミット
COMMIT;
