Dev Study
← データベースの仕組み
レッスン 13 / 13·約 9 分·第6期 データベースの仕組み

デッドロックは異常ではない — "Just try again."

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

ここまで、隙間へのロック・索引に掛かるロック・索引が使われない条件を見てきました。最後に、それらが噛み合った結果として起きるデッドロックを扱います。対象は MySQL 8.4 と PostgreSQL 18 です。

まず結論

デッドロックは障害ではありません。公式はこう書いています——“Deadlocks are a classic problem in transactional databases, but they are not dangerous unless they are so frequent that you cannot run certain transactions at all.” そして対処の指示は3語です——**“Just try again.”**

巻き戻される側の選び方にも俗説があります。公式は “`InnoDB` **tries to pick** small transactions to roll back” と書き、「小さい」の尺度は**挿入・更新・削除した行数**です。**「後から来た方が殺される」という記述はマニュアルにありません。** また**デッドロック検知**(即時)と**ロック待ちタイムアウト**(既定 **50** 秒)は別の機構です。

? 読む前に予測

InnoDB がデッドロックを検知したとき、どちらのトランザクションを巻き戻すかはどう決まるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

公式の結論は3語

デッドロックは、名前の強さから障害のように扱われがちです。定義自体は素朴なものです。

A deadlock is a situation in which multiple transactions are unable to proceed because each transaction holds a lock that is needed by another one. Because all transactions involved are waiting for the same resource to become available, none of them ever releases the lock it holds.

互いに相手の持つ鍵を待っている状態です。放っておけば誰も進みません。だから InnoDB は自分で解きます。

When deadlock detection is enabled (the default), InnoDB automatically detects transaction deadlocks and rolls back a transaction or transactions to break the deadlock.

そして公式の評価はこうです。

Deadlocks are a classic problem in transactional databases, but they are not dangerous unless they are so frequent that you cannot run certain transactions at all.

危険かどうかの基準が頻度に置かれています。 起きたこと自体は問題ではありません。要求されるのは対処の用意です。

Normally, you must write your applications so that they are always prepared to re-issue a transaction if it gets rolled back because of a deadlock.

Always be prepared to re-issue a transaction if it fails due to deadlock. Deadlocks are not dangerous. Just try again.

“Just try again.” これが公式の指示です。そして逃げ道は塞がれています。

Thus, even if your application logic is correct, you must still handle the case where a transaction must be retried.

ロジックが正しくてもリトライは必要です。「正しく書けば起きない」種類の問題ではありません。

✓ ここまでの確認

デッドロックに対してアプリケーション側が必ず用意すべきものは何だと公式は述べていますか。

具体実物

どちらが巻き戻されるのか

「後から来た方が殺される」という説明をよく聞きます。マニュアルにその記述はありません。 書かれているのはこうです。

InnoDB tries to pick small transactions to roll back, where the size of a transaction is determined by the number of rows inserted, updated, or deleted.

読むべき点が2つあります。ひとつは “tries to pick”——試みるのであって保証ではありません。もうひとつは尺度です。「小さい」とは挿入・更新・削除した行数が少ないこと。経過時間でも、保持しているロックの数でも、SELECT した行数でもありません。

巻き戻される側の選び方 — 俗説と公式表
俗説 後から来た方が巻き戻される → マニュアルに記述なし
俗説 実行時間が長い方が巻き戻される → 記述なし
俗説 ロック数が多い方が巻き戻される → 記述なし
 
公式 "tries to pick small transactions to roll back"
小さい = 挿入・更新・削除した行数が少ない
ただし tries to pick(保証ではない)

検知にも限界があります。InnoDB から見えないロックは扱えません。

InnoDB is aware of table locks if innodb_table_locks = 1 (the default) and autocommit = 0, and the MySQL layer above it knows about row-level locks. Otherwise, InnoDB cannot detect deadlocks where a table lock set by a MySQL LOCK TABLES statement or a lock set by a storage engine other than InnoDB is involved.

さらに、検知そのものを切る判断もありえます。

On high concurrency systems, deadlock detection can cause a slowdown when numerous threads wait for the same lock. At times, it may be more efficient to disable deadlock detection and rely on the innodb_lock_wait_timeout setting for transaction rollback when a deadlock occurs.

検知はタダではない——同じロックを大量のスレッドが待つと、検知自体が遅さの原因になりえます。

検知の打ち切りもあります。

A wait-for list that exceeds 200 transactions is treated as a deadlock and the transaction attempting to check the wait-for list is rolled back.

このときは確認しようとした側が巻き戻されます。

具体実物

検知とタイムアウトは別の機構

混同されやすい2つを分けます。既定値は次のとおりです。

プロパティ表の記載どおりの既定値設定ファイル
innodb_deadlock_detect Default: ON Boolean Global Dynamic: Yes
innodb_lock_wait_timeout Default: 50 Integer Global, Session Dynamic: Yes

検知は待ちの輪を見つけて即座に片方を巻き戻し、タイムアウトは待ちきれなかった場合の打ち切りです。

The length of time in seconds an InnoDB transaction waits for a row lock before giving up. The default value is 50 seconds. A transaction that tries to access a row that is locked by another InnoDB transaction waits at most this many seconds for write access to the row before issuing the following error: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

巻き戻される範囲は、InnoDB Error Handling の節に明記されています。

A lock wait timeout causes InnoDB to roll back the current statement (the statement that was waiting for the lock and encountered the timeout). To have the entire transaction roll back, start the server with --innodb-rollback-on-timeout enabled.

巻き戻るのは the current statement だけです。既定のままなら、タイムアウトした文の前に成功した文はそのトランザクションに残ります。「50秒待って全部やり直し」と覚えると、途中の状態を見落とします。最大値は 1073741824 秒(約34年)です。

しかもタイムアウトはデッドロックでなくても起きます。前のトランザクションが鍵を握り続けるだけでも 50 秒後にエラーになるので、文面だけで「デッドロックだ」と判断すると原因を取り違えます。

もう一つ、実務で頻出する形があります。

If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.

重複キーエラーが起きた側もロックを取ります。 「無ければ作る」を並行して走らせる箇所が、これに当たりやすい形です。

診断手段も案内されています——“To view the last deadlock in an InnoDB user transaction, use SHOW ENGINE INNODB STATUS.” 頻発するなら “If frequent deadlocks highlight a problem with transaction structure or application error handling, enable innodb_print_all_deadlocks to print information about all deadlocks to the mysqld error log.”

✓ ここまでの確認

デッドロック検知とロック待ちタイムアウトの関係について、正しいのはどれですか。

⇄ 2つを見比べる
具体例 A

MySQL — デッドロックで巻き戻す

待ちの輪を検知して片方を巻き戻します。選ばれるのは挿入・更新・削除した行数が少ない側を「選ぼうとする」もので、保証ではありません。公式は危険ではないと述べ、対処として再実行を指示します。

MySQL 8.4 の指示コマンド出力
"Deadlocks are not dangerous. Just try again."
 
選定 "tries to pick small transactions"
尺度 number of rows inserted, updated, or deleted
検知 既定 ON / 即座に巻き戻し
別機構 lock wait timeout = 既定 50 秒
→ roll back the current statement (既定)
具体例 B

PostgreSQL — 直列化の失敗を返す

SERIALIZABLE では直列化できない条件を監視し、見つかると失敗を返します。predicate locking は使いますが待ちを生まず、デッドロックの原因にもならないと明記されています。それでもアプリ側の要求は同じで、リトライの用意です。

PostgreSQL 18 の記述コマンド出力
"applications using this level must be prepared
to retry transactions due to serialization
failures."
 
機構 Serializable Snapshot Isolation
待ち "these locks do not cause any blocking"
デッドロックの原因にもならない

一方は待ちの輪、もう一方は待たない監視。機構が正反対なのに、公式が要求するコードはなぜ同じ形になるのでしょうか。

どちらも「このトランザクションは失敗しうる」という事実をアプリに返します。返ってきた側は何をすべきでしょうか。

この章のまとめ

  • 公式の評価は “Deadlocks are not dangerous. Just try again.” 危険の基準は頻度で、起きたこと自体ではない。
  • ロジックが正しくてもリトライは必要——“even if your application logic is correct, you must still handle the case where a transaction must be retried.”
  • 巻き戻される側は “tries to pick small transactions”。尺度は挿入・更新・削除した行数で、保証ではない。「後から来た方」はマニュアルに無い。
  • 検知とタイムアウトは別機構。 検知は既定 ON で即座、タイムアウトは既定 50 秒(最大 1073741824 秒)。巻き戻るのは既定では the current statement だけで、全体にするには --innodb-rollback-on-timeout(既定 OFF)。
  • InnoDB の外のロック(LOCK TABLES・他エンジン)は検知できない。高並行では検知自体が遅さの原因になりうる。
  • 重複キーエラーも共有ロックを取り、デッドロックの材料になる。診断は SHOW ENGINE INNODB STATUS と innodb_print_all_deadlocks。
  • PostgreSQL の SERIALIZABLE は待たない代わりに serialization failure を返す。リトライ前提の設計は両者共通の要求。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

「デッドロックが出たら設計が間違っているので、出ないように直すべきだ」。この主張を公式の記述に照らして評価してください。

用語集(4語)
デッドロック
“A deadlock is a situation in which multiple transactions are unable to proceed because each transaction holds a lock that is needed by another one. Because all transactions involved are waiting for the same resource to become available, none of them ever releases the lock it holds.”
innodb_deadlock_detect
デッドロック検知の有効・無効。Default Value は `ON`、Type は Boolean、Scope は Global。有効時は “`InnoDB` automatically detects transaction deadlocks and rolls back a transaction or transactions to break the deadlock.”
innodb_lock_wait_timeout
行ロックを待つ秒数。Default Value は `50`。“The default value is 50 seconds.” 超過時は `ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction` を返す。巻き戻す範囲は既定では**その文だけ**で、“To have the entire transaction roll back, start the server with `--innodb-rollback-on-timeout` enabled.” 最大値は **1073741824** 秒(プロパティ表の Maximum Value)。
serialization failure(PostgreSQL)
SERIALIZABLE で直列化できない条件が検出されたときに返る失敗。文書は “applications using this level must be prepared to retry transactions due to serialization failures.” と、リトライの用意を要求する。