叫ぶうさぎの悪ふざけ

うさぎが目印のWebエンジニアが、得たことや思ったことを言の葉に乗せて叫ぶ場所です。

学習記録:12月16日(日):【MySQL】frmファイル欠損、ibdファイル存在、テーブル構成を推測したい【リストア不能】

これは 俺のインプットアウトプット記録 Advent Calendar 2018 の16日目のエントリーです。

結論から言ってしまうと、テーブル修復でエラーになっていたのは .ibdファイル がそもそもなかったからでした。おそらく、MacBookのストレージ容量不足でファイルを退避させた際に、なぜか .frmファイル を一緒に退避させなかったみたいですね(遠い目)

というわけで、振り返りつつ、どうにか .ibdファイル を直接読むことはできないものか、ともがいてみます。

昨日まで

昨日は MySQL 5.7.19 の環境をマイナーバージョン指定で作ろう、と思い立ったものの、おそらくソースからの make install じゃないと無理では…ということで、もともと使っていた環境に、エラーのあったテーブルをリストアし、mysql_upgradeを再実行すればいいかな、と思っていました。

なんと .frmファイル欠損

が、いざリストアしてみると肝心のテーブルがない…。あ、テーブル個別でバックアップ取っていたな、と思ってzipファイルを開くと…なんと .ibdファイルそのもの

.frmファイル がテーブル構造を表すファイルで、 .ibdファイル が実データを表すファイル。これではテーブル構造がわからぬ…しかも前回、公式にしたがって DROP TABLE しているので構造がわからない。

うーむ困った…。

`.ibdファイルを直接読んで、レコードの状態をみることはできないものか。

と探していると、 innodb_ruby というツールを提供しているかたを発見。

wiki に書かれている通りにインストールし、使ってみました。

innodb_ruby

以下の手順でインストールしました。

インストール

# バックアップを取っていたと思ったらibdファイルだけだったディレクトリで作業
$ pwd
/Users/mamy1326/dev/vagrant/mysql57/var/lib/mysql/mamy1326/innodb_ruby
$ sudo gem install innodb_ruby
Password:
Fetching: bindata-2.4.4.gem (100%)
Successfully installed bindata-2.4.4
Fetching: digest-crc-0.4.1.gem (100%)
Successfully installed digest-crc-0.4.1
Fetching: innodb_ruby-0.9.15.gem (100%)
Successfully installed innodb_ruby-0.9.15
Parsing documentation for bindata-2.4.4
Installing ri documentation for bindata-2.4.4
Parsing documentation for digest-crc-0.4.1
Installing ri documentation for digest-crc-0.4.1
Parsing documentation for innodb_ruby-0.9.15
Installing ri documentation for innodb_ruby-0.9.15
Done installing documentation for bindata, digest-crc, innodb_ruby after 3 seconds
3 gems installed

GitHubから諸々取得

$ git clone https://github.com/jeremycole/innodb_ruby.git
Cloning into 'innodb_ruby'...
remote: Enumerating objects: 2663, done.
remote: Total 2663 (delta 0), reused 0 (delta 0), pack-reused 2663
Receiving objects: 100% (2663/2663), 16.79 MiB | 483.00 KiB/s, done.
Resolving deltas: 100% (1451/1451), done.
naosama-mac:mamy1326 mamy1326$ cd innodb_ruby
naosama-mac:innodb_ruby mamy1326$ innodb_space -s ../
c_log.ibd    innodb_ruby/ 
naosama-mac:innodb_ruby mamy1326$ innodb_space -s ../c_log.ibd system-spaces
name                            pages       indexes     
/Library/Ruby/Gems/2.3.0/gems/innodb_ruby-0.9.15/bin/innodb_space:206:in `block in system_spaces': undefined method `pages' for nil:NilClass (NoMethodError)
    from /Library/Ruby/Gems/2.3.0/gems/innodb_ruby-0.9.15/bin/innodb_space:211:in `system_spaces'
    from /Library/Ruby/Gems/2.3.0/gems/innodb_ruby-0.9.15/bin/innodb_space:1976:in `<top (required)>'
    from /usr/local/bin/innodb_space:22:in `load'
    from /usr/local/bin/innodb_space:22:in `<main>'
$ cd innodb_ruby

innodb_space 実行

藁をもすがる気持ちで実行してみます。 なお、実行コマンドは wiki と、 日々の覚書 mysqlディレクトリーに知らない.ibdファイルがある in MySQL 8.0.0 を参考にさせていただきました。

  • -f オプション
    • 対象はibdata1ではなく、テーブル個別のibd
  • 対象ファイル
    • c_log.ibd ファイルに対し読み込みを実行する
  • page-dump
    • innodb_rubyが理解できるほとんどの構造体の表現を含むページの内容をインテリジェントにダンプします
    • 直訳ですけど、page単位でdumpしてくれる…のか?
    • -p 5 が5ページ分のdumpってことかな?
  • -T オプション
    • 指定されたテーブル名を使用します、とのこと。データベース名とテーブル名を指定します

で、dumpした結果。正直よくわかりませんな(遠い目

$ innodb_space -f ../c_log.ibd -T mamy1326/c_log -p 5 page-dump | less
#<Innodb::Page::Index:0x00007fe6b10dcb78>:

fil header:
{:checksum=>816151722,
 :offset=>5,
 :prev=>nil,
 :next=>nil,
 :lsn=>42947405534,
 :type=>:INDEX,
 :flush_lsn=>0,
 :space_id=>139}

fil trailer:
{:checksum=>816151722, :lsn_low32=>4292699870}

page header:
{:n_dir_slots=>3,
 :heap_top=>274,
 :garbage_offset=>0,
 :garbage_size=>0,
 :last_insert_offset=>265,
 :direction=>:right,
 :n_direction=>4,
 :n_recs=>11,
 :max_trx_id=>0,
 :level=>2,
 :index_id=>106,
 :n_heap=>13,
 :format=>:compact}

fseg header:
{:leaf=>
  <Innodb::Inode space=<Innodb::Space file="../c_log.ibd", page_size=16384, pages=559616>, fseg=6>,
 :internal=>
  <Innodb::Inode space=<Innodb::Space file="../c_log.ibd", page_size=16384, pages=559616>, fseg=5>}

sizes:
  header           120
  trailer            8
  directory          6
  free           16096
  used             288
  record           154
  per record     14.00

page directory:
[99, 181, 112]

system records:
{:offset=>99,
 :header=>
  {:next=>125,
   :type=>:infimum,
   :heap_number=>0,
   :n_owned=>1,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>125,
 :data=>"infimum\x00",
 :length=>8}
{:offset=>112,
 :header=>
  {:next=>112,
   :type=>:supremum,
   :heap_number=>1,
   :n_owned=>8,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>112,
 :data=>"supremum",
 :length=>8}

garbage records:

records:
{:format=>:compact,
 :offset=>125,
 :header=>
  {:next=>139,
   :type=>:node_pointer,
   :heap_number=>2,
   :n_owned=>0,
   :min_rec=>true,
   :deleted=>false,
   :length=>5},
 :next=>139}

{:format=>:compact,
 :offset=>139,
 :header=>
  {:next=>167,
   :type=>:node_pointer,
   :heap_number=>3,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>167}

{:format=>:compact,
 :offset=>167,
 :header=>
  {:next=>181,
   :type=>:node_pointer,
   :heap_number=>5,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>181}

{:format=>:compact,
 :offset=>181,
 :header=>
  {:next=>195,
   :type=>:node_pointer,
   :heap_number=>6,
   :n_owned=>4,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>195}

{:format=>:compact,
 :offset=>195,
 :header=>
  {:next=>209,
   :type=>:node_pointer,
   :heap_number=>7,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>209}

{:format=>:compact,
 :offset=>209,
 :header=>
  {:next=>223,
   :type=>:node_pointer,
   :heap_number=>8,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>223}

{:format=>:compact,
 :offset=>223,
 :header=>
  {:next=>237,
   :type=>:node_pointer,
   :heap_number=>9,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>237}

{:format=>:compact,
 :offset=>237,
 :header=>
  {:next=>251,
   :type=>:node_pointer,
   :heap_number=>10,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>251}

{:format=>:compact,
 :offset=>251,
 :header=>
  {:next=>265,
   :type=>:node_pointer,
   :heap_number=>11,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>265}

{:format=>:compact,
 :offset=>265,
 :header=>
  {:next=>153,
   :type=>:node_pointer,
   :heap_number=>12,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>153}

{:format=>:compact,
 :offset=>153,
 :header=>
  {:next=>112,
   :type=>:node_pointer,
   :heap_number=>4,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :length=>5},
 :next=>112}

次の課題

まずもってテーブル構造を、レコードから推測できないことにはリストアは不可能そうです。

しかしせっかく .ibdファイル を読み込むところまできたので、できる限りのことをしてみようと思います。

これで自由にレコード読めるようになったら面白いじゃん?

せっかく便利なツールを作ってくれている人がいるし、個人的に中身を直接読んでみたいと常々思っていたので、趣味的に願ったり叶ったり。

アップグレードはしばし棚上げして、 .ibdファイル の読み方を学習してみたいと思います。

もしこのエントリを読んでくださっているかたがいらしたならば、おそらく面白くないんじゃないかなあ…と思いながらも、自分自身はやっていて楽しいのでこうしてエントリにしているわけでございます。

1歩ずつ地道に進めてみて、結果までたどり着き、誰かしらの有益な情報になればいいなあ、と思いながら、やれるところまでやってみようと思います。

で、もしダメなら諦めて、くだんのテーブルは捨ててアップグレードを進めたいと思います。

アップグレード時にテーブル修復が失敗していたのも、そもそも .ibdファイル がなかったからで、ないならないで、消して進めれば本題はクリアできると思いますので。

というわけで、明日も引き続き .ibdファイル と戯れたいと思います!