MySQLを運用していると次のようなエラーが出てMySQLが起動しなくなることがあります。
[ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
このエラーは、InnoDBのシステムテーブルスペースである「ibdata1」ファイルのロック取得に失敗したことを意味しています。このエラーは既に別のMySQLプロセスが同ファイルを使用中であることが原因で起きえます。MySQLでなくとも何かのプロセスが「ibdata1」をロックしてると起きます。このロック原因のプロセスをkillすれば大体解決するのですが、ロックの原因が全く見つからずどうにもならなくなる時もあります。この記事ではそうした状況からMySQLを一時的に復旧し、データを退避したうえでMySQLを再構築する方法を紹介します。
復旧の手順はざっくりまとめると次の通りです。
innodb_force_recovery
を設定して強制的に起動するmysqldump
で全データをエクスポートする- MySQL を初期化または再インストールする
- エクスポートしたデータをインポートする
どうにかデータを読めるようにして、データだけ逃がして、他を問題のない状態に作り直して、データを入れ直して、といった具合です。詳細な内容が続きです。
■ 手順1:innodb_force_recoveryで強制起動
MySQLには innodb_force_recovery というオプションがあります。これは強制的に MySQL を起動するオプションです。これの説明は次のドキュメントが詳しいです。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.21.2 InnoDB のリカバリの強制的な実行
innodb_force_recovery をつけると異常な状態でも MySQL を起動できますが取り扱いには注意が必要です。これは innodb_force_recovery がデータに不可逆的な破壊をもたらす可能性があるオプションであるためです。innodb_force_recoveryは1〜6の整数値を取り、大きい数字ほど強制力が高くなります。数字が大きいほど起動できる可能性は上がりますが同時に不正なデータのリスクも高まります。不正なデータのリスクについても先ほどのリンク先にあります。このリスクを雑に言うと 3 まで安全で 4 がインデックスに影響あり、5と6が不整合なデータが現れる可能性ありです。まず innodb_force_recovery=3 でお祈りしながら起動して、ダメなら4、5、6と上げていく感じです。
■ 手順2:mysqldumpでデータを退避
MySQLを起動出来たら次のコマンドで、すべてのデータベースをダンプします。
mysqldump -u root -p --all-databases --single-transaction --routines --triggers > all_backup.sql
すべてのデータベースのダンプが無理なら必要なデータベースだけ指定してダンプするのも全然ありです。これで一通りのデータをダンプできれば復旧は成功にグッと近づきます。
■ 手順3:MySQLを初期化または再インストール
データのダンプが完了したらMySQLをまっさらな状態にして再度入れ直します。この手順はプラグインなど環境ごとに異なります。インストール時の手順が残ってなかったら、まっさらな状態にする前に何が使われているかを調査してがんばって再現する必要があります。
DockerでMySQLを動かしているならばボリュームなどの永続化部分を作り直すことになります。
■ 手順4:データをインポートする
起動したMySQLに以下のコマンドで先ほどのダンプファイルをリストアします。
mysql -u root -p < all_backup.sql
これにより破損前の状態に近い形でMySQLを復旧できます。
こんな感じでMySQLの復旧ができます。とはいえ常に必要なバックアップが用意できてれば最初からMySQL作り直す+インポートのみで済みますし、冗長化していて正常なレプリカのデータがあるならばそちらのデータをコピーするだけで済みます。最初から備えておくのが一番です。