地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【MySQL】クラッシュリカバリのUndoフェーズでやること

散歩中に疑問に思ったので調べた。クラッシュ時のシナリオとリカバリの時の脳内のダンプを書いておく

  • TxA, begin
    • レコードAをUpdate
      • buffer poolの書き換え
      • undoログへ書き込み(2->1へする)
      • redo log bufferへ書き込み
      • 未コミット
  • サーバクラッシュ
  • 再起動
    • redoフェーズ
      • ログの内容をテーブルスペースへ書き込み
    • undoフェーズ

ここまでが想像してたやつ。ドキュメント見ると他にもやってることはあるがバックグラウンドスレッドが頑張ってくれるのでクラッシュリカバリのメインはここまで

dev.mysql.com

疑問に思ったのがここ。再起動時にUndoログからロールバックする必要ってあるの?と思った。まあ当然不要な処理をinnodbがやるわけないので必要なのだがその理由を調べてみた。

不完全な transactions の Roll back

未完了のトランザクションは、予期しない終了時または fast shutdown でアクティブだったトランザクションです。 未完了のトランザクションをロールバックするためにかかる時間は、サーバーの負荷に応じて、そのトランザクションが中断される前にアクティブであった期間の 3 または 4 倍になる場合があります。

不要だと思った理由

なんで不要だと思ったかというと未コミットのトランザクションの操作はメモリ上にのみ存在してクラッシュした時点で揮発するのでは?と思ったから。メモリ上というのはredo log bufferとbuffer pool。変更していたかどうかをinnodbは知るすべがなさそうと思った。

必要な理由

redo log bufferはあくまでもbufferであってcommitされてないデータもbufferが無くなればディスクに書く。なので未コミットのトランザクションロールバックする必要がある。

dev.mysql.com

そういやbuffer poolもディスクに無かったっけ?

dev.mysql.com

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ツールでも書くかなと思ったがやめた(笑))

参考

www.alibabacloud.com

【MySQL】バックグラウンドスレッドを調べる

SELECT THREAD_ID,NAME FROM performance_schema.threads where TYPE = 'background';
+-----------+----------------------------------------+
| THREAD_ID | NAME                                   |
+-----------+----------------------------------------+
|         1 | thread/sql/main                        |
|         2 | thread/sql/thread_timer_notifier       |
|         3 | thread/innodb/io_ibuf_thread           |
|         4 | thread/innodb/io_log_thread            |
|         5 | thread/innodb/io_read_thread           |
|         6 | thread/innodb/io_read_thread           |
|         7 | thread/innodb/io_read_thread           |
|         8 | thread/innodb/io_read_thread           |
|         9 | thread/innodb/io_write_thread          |
|        10 | thread/innodb/io_write_thread          |
|        11 | thread/innodb/io_write_thread          |
|        12 | thread/innodb/io_write_thread          |
|        13 | thread/innodb/page_cleaner_thread      |
|        15 | thread/innodb/srv_lock_timeout_thread  |
|        16 | thread/innodb/srv_error_monitor_thread |
|        17 | thread/innodb/srv_monitor_thread       |
|        18 | thread/innodb/srv_master_thread        |
|        19 | thread/innodb/srv_purge_thread         |
|        20 | thread/innodb/buf_dump_thread          |
|        21 | thread/innodb/dict_stats_thread        |
|        22 | thread/sql/signal_handler              |
+-----------+----------------------------------------+

ロングトランザクションを見つける

cousmersとinstrumentsを有効化する

UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME = 'transaction';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_transactions_history_long';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_statements_history_long';

有効化したらevents_transactions_history_longとevents_statements_history_longをジョインすることでクエリの中身を見つつトランザクションの内容を確認することができる

  SELECT 
    transactions.thread_id,
    transactions.event_id,
    transactions.nesting_event_id,
    sys.format_time(transactions.timer_wait),
    statements.nesting_event_id,
    statements.sql_text
  FROM
    performance_schema.events_transactions_history_long AS transactions
  JOIN
    performance_schema.events_statements_history_long AS statements ON transactions.event_id = statements.nesting_event_id
  WHERE transactions.timer_wait > 10000000000000;