Database Locks

一部のコンテンツは LLM によって生成されており、まだ手動で検証されていません。

データベースのロック機構(きこう) は、並行(へいこう) アクセスを制御(せいぎょ) し、データの一貫性(いっかんせい)整合性(せいごうせい)確保(かくほ) するために使用(しよう) される。

Pessimistic / Optimistic Lock

悲観的ロック(Pessimistic Lock)

データ競合(きょうごう)頻繁(ひんぱん)発生(はっせい) する環境(かんきょう)(てき) している。データの安全性(あんぜんせい)確保(かくほ) できるが、システムの並行性(へいこうせい)低下(ていか) する。

楽観的ロック(Optimistic Lock)

()() りが(おお)()() みが(すく) ないシナリオに(てき) している。競合(きょうごう)(すく) ない場合(ばあい) 、システムパフォーマンスを効果的(こうかてき)向上(こうじょう) させる。ただし、競争(きょうそう)(はげ) しい場合(ばあい)過度(かど) なリトライが発生(はっせい) する可能性(かのうせい) がある。

  sequenceDiagram
    participant A as ClientA
    participant B as ClientB
    participant DB as Database

    B ->> DB: read product
    DB ->> B: return product(Quantity=10, Version=1)
    A ->> DB: read product
    DB ->> A: return product(Quantity=10, Version=1)
    B ->> DB: update product(Quantity=8)
    DB ->> DB: update quantity, incr version(Version=2) success
    A ->> DB: update product(Quantity=9, Version=1)
    DB ->> DB: version checked failed(Version != 1)
    note over A, DB: conflict!
    DB ->> A: OptimisticLockErr

DB ロックの種類

Database Locks Types

ロック範囲による分類

ロックタイプ説明
Row-Level Lockテーブル(ない)特定(とくてい)(ぎょう) をロックし、(ほか)(ぎょう) への同時(どうじ) アクセスを許可(きょか)
Page-Level Lockデータベース(ない)特定(とくてい) のページ(固定(こてい) サイズのデータブロック)をロック
Table-Level Lockテーブル全体(ぜんたい) をロック。実装(じっそう)簡単(かんたん) だが並行性(へいこうせい)大幅(おおはば)低下(ていか)

ロックモードによる分類

ロックタイプ説明
Shared Lock (S Lock)共有(きょうゆう) ロック。複数(ふくすう) のトランザクションが同時(どうじ) にリソースを()() れるが変更(へんこう) はできない
Exclusive Lock (X Lock)排他(はいた) ロック。トランザクションがリソースを()()可能(かのう)保持中(ほじちゅう)(ほか) のトランザクションはロックを取得(しゅとく) できない
Update Lock (U Lock)更新(こうしん) ロック。トランザクションがリソースを更新(こうしん) しようとする(さい) のデッドロックを防止(ぼうし)

その他のロックタイプ

ロックタイプ説明
Schema Lockスキーマロック。データベースオブジェクトの構造(こうぞう)保護(ほご)
Bulk Update Lock (BU Lock)一括(いっかつ) 更新(こうしん) ロック。一括挿入(いっかつそうにゅう) 操作(そうさ) ()使用(しよう) し、必要(ひつよう) なロック(すう)削減(さくげん) してパフォーマンスを向上(こうじょう)
Key-Range Lockキー範囲(はんい) ロック。インデックスデータでファントムリードを防止(ぼうし)

MySQL / PostgreSQL Lock サポート

Lock 種類(しゅるい)MySQL (InnoDB)PostgreSQL備考(びこう)
Row-Level Lock自動(じどう)自動(じどう)InnoDB はデフォルトで(ぎょう) レベルロック。エンジンがクエリに(もと) づいて自動選択(じどうせんたく)
Page-Level Lock両方(りょうほう) ともサポートなし。SQL Server 固有(こゆう)
Table-Level LockLOCK TABLESLOCK TABLE手動(しゅどう) 指定(してい) 可能(かのう) だが、通常(つうじょう) エンジンが自動処理(じどうしょり)
Shared Lock (S)SELECT ... FOR SHARESELECT ... FOR SHAREMySQL 8.0+ で FOR SHARE 構文(こうぶん) をサポート
Exclusive Lock (X)SELECT ... FOR UPDATESELECT ... FOR UPDATE両方(りょうほう) とも(おな)構文(こうぶん)
Update Lock (U)SQL Server 固有(こゆう) 。MySQL/PostgreSQL にはこの概念(がいねん) がない
Schema Lock✅ Metadata Lock✅ AccessExclusiveLockDDL 操作時(そうさじ)自動取得(じどうしゅとく)
Bulk Update LockSQL Server 固有(こゆう)
Key-Range Lock✅ Gap Lock / Next-Key Lock✅ Predicate Lockファントムリード防止(ぼうし)使用(しよう)
MySQL InnoDB のロック機構(きこう)大半(たいはん) がストレージエンジンによって SQL (ぶん)分離(ぶんり) レベルに(もと) づいて自動選択(じどうせんたく) される。ユーザーは Row Lock や Gap Lock を直接指定(ちょくせつしてい) できない。

Row Lock 競合の軽減

戦略(せんりゃく)説明(せつめい)
トランザクション範囲(はんい)縮小(しゅくしょう)必要(ひつよう)(ぎょう) のみロックし、(はや) めに COMMIT
長時間(ちょうじかん) ロック保持(ほじ)回避(かいひ)トランザクション(ない)時間(じかん) のかかる操作(そうさ) (API ()() しなど)を() ける
適切(てきせつ) なインデックスの使用(しよう)インデックスなしの UPDATE/DELETE は Table Lock に昇格(しょうかく) する可能性(かのうせい)
分割処理(ぶんかつしょり)大量(たいりょう) 更新時(こうしんじ)分割(ぶんかつ) 実行(じっこう) し、一度(いちど) にロックする行数(ぎょうすう)() らす

一括更新のベストプラクティス

分割 UPDATE + LIMIT(アプリケーション層ループ)

アプリケーション(そう)()(かえ)実行(じっこう) し、影響行数(えいきょうぎょうすう) が 0 になるまで継続(けいぞく)

-- 単発実行、アプリケーション層で affected_rows を判定して継続を決定
UPDATE orders
SET status = 'archived'
WHERE created_at < '2024-01-01' AND status = 'completed'
LIMIT 1000;

アプリケーション(そう) ロジック(擬似(ぎじ) コード):

while True:
    affected = db.execute(UPDATE ... LIMIT 1000)
    if affected == 0:
        break
    sleep(0.1)  # オプション:ロック競合軽減

サブクエリを使用(しよう) した一括更新(いっかつこうしん)推奨(すいしょう)

アプリケーション(そう) で SQL を直接組(ちょくせつく)()可能(かのう)一時(いちじ) テーブル不要(ふよう)

UPDATE user_accounts ua
INNER JOIN (
    -- ID を必ずソートしてデッドロックを防止
    SELECT 1 as user_id, 50.00 as amount UNION ALL
    SELECT 2, 100.00 UNION ALL
    SELECT 3, 75.50
    ORDER BY user_id ASC
) as temp ON ua.user_id = temp.user_id
SET ua.balance = ua.balance + temp.amount
WHERE ua.balance + temp.amount >= 0;  -- 楽観的チェックを維持
**ORDER BY ソートの重要性(じゅうようせい) **:複数行(ふくすうぎょう) 更新時(こうしんじ)(こと) なるトランザクションが(こと) なる順序(じゅんじょ)(おな) じデータをロックするとデッドロックが発生(はっせい) 。PK で統一(とういつ) ソートすることで、すべてのトランザクションが(おな)順序(じゅんじょ) でロックを取得(しゅとく)

一時テーブルを使用した一括更新

データ(りょう)(おお)場合(ばあい)複数回(ふくすうかい) クエリが必要(ひつよう)場合(ばあい)適用(てきよう)

-- 1. 更新対象の ID を一時テーブルに書き込む
CREATE TEMPORARY TABLE tmp_update_ids AS
SELECT id FROM orders WHERE created_at < '2024-01-01' LIMIT 10000;

-- 2. JOIN で一括更新
UPDATE orders o
INNER JOIN tmp_update_ids t ON o.id = t.id
SET o.status = 'archived';

-- 3. 一時テーブルをクリーンアップ
DROP TEMPORARY TABLE tmp_update_ids;
autocommit=0 一括(いっかつ) コミットについて一括(いっかつ) INSERT では効果(こうか)顕著(けんちょ) 。UPDATE でも有効(ゆうこう) だが、トランザクションサイズに注意(ちゅうい)(おお) きすぎるトランザクションは undo log の肥大化(ひだいか) やロック保持時間(ほじじかん)長期化(ちょうきか)(まね) くため、1000〜5000 (けん) ごとにコミットすることを推奨(すいしょう)

Gap Lock

ギャップロックは MySQL InnoDB の特殊(とくしゅ) なロック機構(きこう) である。

  • SELECT ... FOR UPDATE などのステートメントによってトリガーされる
  • インデックスキー(PK)(かん)隙間(すきま) をロックし、(ほか) のトランザクションがその隙間(すきま)(あたら) しい(ぎょう)挿入(そうにゅう) するのを防止(ぼうし)

Demo Table

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    salary DECIMAL(10, 2) NOT NULL
);

INSERT INTO employees (id, name, salary) VALUES (1, 'Alice', 50000.00);
INSERT INTO employees (id, name, salary) VALUES (3, 'Charlie', 70000.00);

防止幻讀

TimeTransaction ATransaction B
T0BEGIN;BEGIN;
T1SELECT * FROM employees WHERE id BETWEEN 1 AND 3 FOR UPDATE;
T2INSERT INTO employees (id, name, salary) VALUES (2, 'Bob', 60000.00);
T3COMMIT; — トランザクション A の Commit を待機(たいき)
T4SELECT * FROM employees; — ID(2) のデータはテーブルに存在(そんざい) しない
T5COMMIT;
T6SELECT * FROM employees; — ID(2) のデータがテーブルに表示(ひょうじ) される

データの誤削除/変更防止

TimeTransaction ATransaction B
T0BEGIN;BEGIN;
T1SELECT count(1) FROM employees WHERE id >= 1; — 2 (けん) 表示(ひょうじ)
T2INSERT INTO employees (id, name, salary) VALUES (4, 'Jack', 10000.00);
T3COMMIT; — トランザクション A の Commit を待機(たいき)
T4SELECT count(1) FROM employees WHERE id >= 1;依然(いぜん) として 2 (けん) 表示(ひょうじ)
T5COMMIT;
T6SELECT count(1) FROM employees WHERE id >= 1; — 3 (けん) 表示(ひょうじ)

Distributed Locks

分散(ぶんさん) ロックは、分散(ぶんさん) システムにおいて複数(ふくすう) のノードが共有(きょうゆう) リソースへのアクセスを調整(ちょうせい) するために使用(しよう) される。

Centralized Locking

中央(ちゅうおう) 集権的(しゅうけんてき) ロック。単一(たんいつ) のノードがすべてのロックを管理(かんり)

問題(もんだい) 単一障害点(たんいつしょうがいてん) (SPOF)

Token-Based Locking

トークンベースのロック。

問題(もんだい)

  • Token Lost(トークン紛失(ふんしつ)
  • Token Expired(トークン期限切(きげんぎ) れ)

Quorum-Based Locking (RedLock)

Redis Cluster を(れい) に(5 ノードと仮定(かてい) ):

手順(てじゅん)

  1. Acquire Time:クライアントがまず現在(げんざい) 時刻(じこく) T1記録(きろく)
  2. Lock Acquisition
    • (かく) Redis ノードで SETNX コマンドを使(つか) ってロック取得(しゅとく)(こころ) みる
    • (かく) ノードは以下(いか)(かえ) す:
      • OK:ロック取得(しゅとく) 成功(せいこう)
      • NULL:ロックが(ほか) のクライアントに保持(ほじ) されている
    • (かく) SETNX コマンドで有効(ゆうこう) 期限(きげん) (TTL)を設定(せってい) し、ロックが永久(えいきゅう)存在(そんざい) しないようにしてデッドロックを回避(かいひ)
  3. Calculate Elapsed Time経過(けいか) 時間(じかん)計算(けいさん) T2 - T1
  4. Quorum CheckN/2 + 1 = 5/2 + 1 = 3成功(せいこう) ノード(すう) > 3 ならロック成功(せいこう)
  5. UnlockingDEL使(つか) ってロックを解放(かいほう)

利点(りてん)

  • Quorum Requirement:1〜2 つの Redis ノードが故障(こしょう) しても、過半数(かはんすう) のノード((れい) :3 ノード)が正常(せいじょう)動作(どうさ) していればロックを(ただ) しく取得(しゅとく)解放(かいほう) できる
  • Fault Tolerance単一障害点(たんいつしょうがいてん)回避(かいひ)

関連トピック