MySQLのクラッシュリカバリでUndoフェーズ必要なのなんでだろ
— Ryuichi@Gurasan:|| (@ryuichi_1208) 2022年11月6日
散歩中に疑問に思ったので調べた。クラッシュ時のシナリオとリカバリの時の脳内のダンプを書いておく
- TxA, begin
- レコードAをUpdate
- buffer poolの書き換え
- undoログへ書き込み(2->1へする)
- redo log bufferへ書き込み
- 未コミット
- レコードAをUpdate
- サーバクラッシュ
- 再起動
ここまでが想像してたやつ。ドキュメント見ると他にもやってることはあるがバックグラウンドスレッドが頑張ってくれるのでクラッシュリカバリのメインはここまで
疑問に思ったのがここ。再起動時にUndoログからロールバックする必要ってあるの?と思った。まあ当然不要な処理をinnodbがやるわけないので必要なのだがその理由を調べてみた。
不完全な transactions の Roll back 未完了のトランザクションは、予期しない終了時または fast shutdown でアクティブだったトランザクションです。 未完了のトランザクションをロールバックするためにかかる時間は、サーバーの負荷に応じて、そのトランザクションが中断される前にアクティブであった期間の 3 または 4 倍になる場合があります。
不要だと思った理由
なんで不要だと思ったかというと未コミットのトランザクションの操作はメモリ上にのみ存在してクラッシュした時点で揮発するのでは?と思ったから。メモリ上というのはredo log bufferとbuffer pool。変更していたかどうかをinnodbは知るすべがなさそうと思った。
必要な理由
redo log bufferはあくまでもbufferであってcommitされてないデータもbufferが無くなればディスクに書く。なので未コミットのトランザクションはロールバックする必要がある。
そういやbuffer poolもディスクに無かったっけ?
buffer poolの保存機能はあるがあくまでも正常終了した際の処理なのでクラッシュしたら保存してる暇も無いのでそもそも機能が動かない。(save完了後にクラッシュとか間でクラッシュした際の動きは気になる)
Undoフェーズはバックグラウンドで実行される
これもへぇとなったがUndoログはバックグラウンドで実行されるらしい。このタイミングは対象のレコードはすべてロック状態となるので大量トランザクションでクラッシュした際はクエリが大量にロック待ちになってしまう可能性がある。performance_schemaのthreadsテーブルとかでUndoあたりを実行しているスレッドが無いかを見ると良いのだろうか(未検証)
MVCCとのつながり
Undoログにはコミット済みのトランザクションもMVCCを実現するために記載されている。クラッシュリカバリ時にはコミット済みかどうかを判断する必要があるがそれを実現するためにUndoログはセグメントごとに持つヘッダーにstateが記載されいる。トランザクションの状態は以下のような状態を取りうる。TRX_UNDO_ACTIVEの場合にトランザクションはロールバックされる
/** Types of an undo log segment */ /** contains undo entries for inserts */ constexpr uint32_t TRX_UNDO_INSERT = 1; /** contains undo entries for updates and delete markings: in short, modifys (the name 'UPDATE' is a historical relic) */ constexpr uint32_t TRX_UNDO_UPDATE = 2; /* States of an undo log segment */ /** contains an undo log of an active transaction */ constexpr uint32_t TRX_UNDO_ACTIVE = 1; /** cached for quick reuse */ constexpr uint32_t TRX_UNDO_CACHED = 2; /** insert undo segment can be freed */ constexpr uint32_t TRX_UNDO_TO_FREE = 3; /** update undo segment will not be reused: it can be freed in purge when all undo data in it is removed */ constexpr uint32_t TRX_UNDO_TO_PURGE = 4; /** contains an undo log of an prepared transaction for a server version older * than 8.0.29 */ constexpr uint32_t TRX_UNDO_PREPARED_80028 = 5; /** contains an undo log of an prepared transaction */ constexpr uint32_t TRX_UNDO_PREPARED = 6; /* contains an undo log of a prepared transaction that has been processed by the * transaction coordinator */ constexpr uint32_t TRX_UNDO_PREPARED_IN_TC = 7;
(ちなみにUndoログ自体は可変長のレコードとして実装されているようでdumpツールでも書くかなと思ったがやめた(笑))