この記事では、InnoDBログシーケンス番号が未来のものになってしまった時について焦点を当ててみる。
前書き : InnoDBログシーケンス番号って何?
ログシーケンス番号(LSN)は、InnoDBによってあちこちで使われている重要なパラメータだ。最も重要な役割としては、クラッシュリカバリとバッファプールのパージの制御だ。
内部的には、InnoDB LSNカウンタが巻戻ることはあり得ない。そして、InnoDBがredoログに50バイト書き込めば、LSNも50バイト進む。そんな風に、LSNをメガバイト単位、ギガバイト単位で数えることもできる。
ここで問題 : LSNが未来を指している!
以下のように設定し、
innodb_force_recovery=6
データを操作するクエリを実行したとする。
例えば、バックアップのためにmysqldump
でダンプした後、不整合のあるテーブルを削除したとすると、InnoDBはibdata1に間違ったLSNを書き込んでしまい、MySQLサーバを再起動するとエラーログにエラーメッセージが書き込まれてしまうだろう。
120323 4:38:52 InnoDB: Error: page 0 log sequence number 6094071743825
InnoDB: is in the future! Current system log sequence number 10000.
解決法 : LSNを変更する方法
通常、LSNを修正する最も安全な方法は、(LSNが正しい位置に戻るために)必要な量のデータをinsertあるいはdeleteすればよい。
しかし、LSNが数TB分もあった場合はどうするべきか? いくつかの方法がある。
- バックアップデータを使う
- 全テーブルをMyISAMに変換し、ibdata1とib_logfile*を削除し、サーバを再起動してから全テーブルをInnoDBに変換し直す
- mysqldumpでのダンプとリストア
- 巨大なデータベースを持っている場合は、黒魔術を使う
1から3の方法が使えない場合に正しいLSNを得るためには、InnoDBのファイルを直接いじるか、mysqldのメモリ空間を変更するという、やや危険な方法をとるしかない。
- debuginfoパッケージがインストールされていることを確認する
- 作業中に(絶対!)クエリが実行されないようにする。さもないとLSNが更新されてしまう。
gdb -p `pgrep -x mysqld` gdb) p log_sys->lsn $1 = 12300 (gdb) set log_sys->lsn = 12300000; Invalid character ';' in expression. (gdb) set log_sys->lsn = 12300000 (gdb) c
mysqldをシャットダウンする。通常のシャットダウンができるはず。
エラーログに正しいLSNが記録されているか確認する。
mysqldを起動し、正しいLSNが表示されるか確認する。
LOG --- Log sequence number 12300000 Log flushed up to 12300000 Last checkpoint at 12300000
何かをinsertしてみて、LSNが変わるのを確認する。
その他の問題 : LSN変更後にデータベースが壊れるのを回避する
- もちろん、MySQLの内部構造に立ち入った方法なので、将来のInnoDBバージョンではうまくいかない可能性がある。このブログ記事を書くためには、5.5.32-rel31.0-549.preciseのメモリ上のLSNを書き換えてみた。この方法があなたの使っているバージョンで動くかどうかは、ステージング環境で確認して欲しい。
- また、もしサーバがクラッシュするとリカバリできないことになるので、運用中のサーバのLSNをオンラインで変更するのは非常によろしくないことだ。
- あるタイミングにおいて、MySQLサーバは同じLSNを持ってアイドル状態でなければならない。Log sequence numberとLog flushed up to、Last checkpoint atはアイドル状態のサーバでは全て一致する。もしサーバがアイドル状態でなく、
SHOW MASTER STATUS\G
の出力に変化がないなら、SET GLOBAL innodb_fast_shutdown=0
を実行して、MySQLを再起動しよう。 - MySQLサーバの再起動は、LSNの変化をトランザクションログに書き込む。
- 手順の前に、システムが安定状態でなければならない。サーバの再起動時のmysqldのクラッシュは、データの破損を招く。再起動のプロセスが素早く終わること、エラーログを確認してリカバリプロセスが実行中でないことを確認しよう。
このような不整合は何から引き起こされるのだろうか?
mysqldがクラッシュすると、再起動時にInnoDBではクラッシュリカバリが実行される。InnoDBのクラッシュリカバリでは、ディスク上にあった最後のチェックポイントから、Log flushed up toのポジションまでトランザクションログを適用していく。Log flushed up toポジションが存在しないポジションだった場合、InnoDBは過去のイベントを適用しようとする。これは、トランザクションログはリングバッファのルールに従っているからだ。変更の前にトランザクションログを再作成すれば、追加の変更を適用することもできる。mysqldのクラッシュ時も重大なデータ欠損が起きないようにすることさえできる。
最後の忠告 : gdbでメモリを変更する際は常にバックアップを取ること。特に、そのバージョンのMySQLでテストしたことないことをやるときは。