MySQL · 數據恢復 · undrop-for-innodb

来源:https://www.cnblogs.com/DataArt/archive/2018/10/29/9873182.html
-Advertisement-
Play Games

Ref:https://www.aliyun.com/jiaocheng/1109809.html 摘要: 簡介 undrop-for-innodb 是針對 innodb 的一套數據恢復工具,可以從文件級別恢復諸如:DROP/TRUNCATE table, 刪除表中某些記錄,innodb 文件被刪除 ...


Ref:https://www.aliyun.com/jiaocheng/1109809.html 

摘要: 簡介 undrop-for-innodb 是針對 innodb 的一套數據恢復工具,可以從文件級別恢復諸如:DROP/TRUNCATE table, 刪除表中某些記錄,innodb 文件被刪除,文件系統損壞,磁碟 corruption 等幾種情況。

簡介

undrop-for-innodb 是針對 innodb 的一套數據恢復工具,可以從文件級別恢復諸如:DROP/TRUNCATE table, 刪除表中某些記錄,innodb 文件被刪除,文件系統損壞,磁碟 corruption 等幾種情況。本文簡單介紹下使用方法和原理淺析。

準備

git clone https://github.com/twindb/undrop-for-innodb.git 
make

需要聯合 MySQL 的安裝路徑編譯工具 sys_parser,

gcc `$basedir/bin/mysql_config --cflags` `$basedir/bin/mysql_config --libs` -o sys_parser sys_parser.c

需要的工具都已經完備:

420d94d6-79de-49b3-ad6c-c2648307d1dc.png

 

  • 重要的工具: c_parser && stream_parser && sys_parser
  • 其中 test.sh && recover_dictionary.sh && fetch_data.sh 是測試的腳本,可以看下裡面的邏輯理解工具的用法。
  • 三個目錄
  • dictionary 裡面是模擬 innodb 系統表結構寫的 CREATE TABLE 語句,innodb 的系統表對用戶不可見,可以在 informatioin_schema 表中找到一些值,但實際上系統表是保存在 ibdata 固定的頁上的。
  • sakila 是一些 SQL 語句,用來測試用。
  • include 是從 innodb 拿出來的一些用到的頭文件和源文件。 

DROP TABLE

表結構恢復

一般情況下表結構是存儲在 frm 文件中,drop table 會刪除 frm 文件,還好我們可以從 innodb 系統表裡讀取一些信息恢復表結構。innodb 系統表有

SYS_COLUMNS | SYS_FIELDS | SYS_INDEXES | SYS_TABLES 

關於系統表結構的具體介紹可以參考 系統表 , 這幾個表對於恢復非常重要,下麵以一個恢復表結構的例子來說明。

使用目錄 sakila/actor.sql 中的例子:

CREATE TABLE `actor` (
 `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
 `first_name` varchar(45) NOT NULL,
 `last_name` varchar(45) NOT NULL,
 `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`actor_id`),
 KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8; insert into actor(first_name, last_name) values('zhang', 'jian'); insert into actor(first_name, last_name) values('zhan', 'jian'); insert into actor(first_name, last_name) values('zha', 'jian'); insert into actor(first_name, last_name) values('zh', 'jian'); insert into actor(first_name, last_name) values('z', 'jian');

mysql> checksum table actor;
+-----------+------------+
| Table | Checksum |
+-----------+------------+
| per.actor | 2184463059 |
+-----------+------------+ 1 row in set (0.00 sec)
DROP TABLE actor

需要從系統表中恢復,而系統表是保存在 $datadir/ibdata1 文件中的,使用工具 stream_parser 解析文件內容。

$./stream_parser -f /home/zj118228/rds_5616/data/ibdata1

執行完畢後會在當前目錄下生成文件夾 pages-ibdata1 , 目錄下按照每個頁為一個文件,分為索引頁和數據較大的 BLOB 頁,我們訪問系統表的話,是存在索引頁中的。使用另外一個重要的工具 c_parser 來解析頁的內容。

$./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql | grep 'sakila/actor' 000000005927 24000001C809C6 SYS_TABLES "sakila/actor" 52 4 1 0 80 "" 38 

參數解析:

  • 4 表示文件格式是 REDUNDANT,系統表的格式預設值。另外可以取值 5 表示 COMPACT 格式,6 表示 MySQL 5.6 格式。
  • D 表示只恢復被刪除的記錄。
  • f 後面跟著文件。
  • t 後面跟著 CREATE TABLE 語句,需要根據表的格式來解析文件。

得到的結果 ‘SYS_TABLES’ 欄位後面的就是系統表 SYS_TABLE 中對應存的記錄。 同樣的恢復其它三個系統表:

/* --- SYS_INDEXES 'grep 52' 是對應 SYS_TABLE 的 TALE ID --- */ 
$./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql | grep '52' 000000005927 24000001C807FF SYS_INDEXES 52 57 "PRIMARY" 1 3 38 4294967295 000000005927 24000001C80871 SYS_INDEXES 52 58 "idx\_actor\_last\_name" 1 0 38 4294967295 /* --- SYS_COLUMNS --- */
./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000002.page -t dictionary/SYS_COLUMNS.sql | grep 52 000000005927 24000001C808F2 SYS_COLUMNS 52 0 "actor\_id" 6 1794 2 0 000000005927 24000001C80927 SYS_COLUMNS 52 1 "first\_name" 12 2162959 135 0 000000005927 24000001C8095C SYS_COLUMNS 52 2 "last\_name" 12 2162959 135 0 000000005927 24000001C80991 SYS_COLUMNS 52 3 "last\_update" 3 525575 4 0 /* --- SYS_FIELD 'grep 57\|58' 對應 SYS_INDEXES 的 ID 列 --- */
$./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000004.page -t dictionary/SYS_FIELDS.sql | grep '57\|58' 000000005927 24000001C807CA SYS_FIELDS 57 0 "actor\_id" 000000005927 24000001C8083C SYS_FIELDS 58 0 "last\_name" 

我們恢復表結構的數據都在這四張系統表中了,SYS_COLUMNS 後面幾列的表示 MySQL 內部對於數據類型的編號。

接下來是恢復階段

  1. 使用目錄 dictionary 下的四個文件創建四張表(這裡資料庫名為 recover )。
  2. 把上面恢復出來的數據分別導入到對應的表中(註意相同的行去重)。
  3. 使用工具 sys_parser 恢復 CREATE TABLE 語句。
$./sys_parser -h 127.0.0.1 -u root -P 56160 -d recover sakila/actor
CREATE TABLE `actor`(
 `actor_id` SMALLINT UNSIGNED NOT NULL,
 `first_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
 `last_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
 `last_update` TIMESTAMP NOT NULL,
 PRIMARY KEY (`actor_id`)
) ENGINE=InnoDB; 

對比發現,恢復出來的 CREATE TABLE 語句相比原來創建的語句信息量有點缺少,因為 innodb 系統表裡面存的數據相比 frm 文件是不足的,比如 AUTO_INCREMENT, DECIMAL 類型的精度信息都會缺失,也不會恢復二級索引,外建等。可以看成是表存儲結構的恢復。如果有 frm 文件就可以完完整整的恢復了,這篇文章介紹了恢復方法:Get Create Table From frm

表數據恢復

innodb_file_per_table off

這種情況表中的數據是保存在 ibdata 文件中的,雖然表的數據在資料庫中被刪除了,但是如果沒有被重寫,數據還在保存在文件中的,執行下列步驟來恢復:

  1. 使用 stream_parser 分析 ibdata 文件,分別得到每個頁的文件。
 $./stream_parser -f /home/zj118228/rds_5616/data/ibdata1
  1. 如表結構分析小節中所示,使用 c_parser 分析系統表 SYS_TABLES 和 SYS_INDEXES ,根據表名得到 TABLE ID, 根據 TABLE ID 得到 INDEX ID。(INDEX ID 就是上述例子的第 5 列,值為 57 和 58)
  2. 根據得到的 INDEX ID,到目錄 pages-ibdata1 下去找對應的頁號,這就是對應的索引表數據所在的數據頁。
  3. 使用 c_parser 讀取第 3 步得到的頁文件,得到數據。
$./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000065.page -t actor.sql
-- Page id: 579, Format: COMPACT, Records list: Valid, Expected records: (5 5)
000000005D95 E5000001960110 actor 201 "zhang" "jian" "2017-11-04 12:30:07" 000000005D96 E6000001970110 actor 202 "zhan" "jian" "2017-11-04 12:30:07" 000000005D98 E80000019A0110 actor 203 "zha" "jian" "2017-11-04 12:30:07" 000000005D99 E90000019B0110 actor 204 "zh" "jian" "2017-11-04 12:30:07" 000000005DA9 F1000002480110 actor 205 "z" "jian" "2017-11-04 12:30:08" 

數據看起來沒什麼問題,表結構和數據都有了,導進去即可,看一下 checksum 也相同。

mysql> checksum table actor;
+-----------+------------+
| Table | Checksum |
+-----------+------------+
| per.actor | 2184463059 |
+-----------+------------+
1 row in set (0.00 sec)

innodb_file_per_table on

這種情況下表是保存在各自的 ibd 文件中的,當 drop table 之後 ,ibd 文件會被刪除,此時最好能夠設置磁碟整體只讀,避免有其它進程重寫文件塊。整體的恢復步驟和上一個小節(innodb_file_per_table off) 相同,只是無法從 pages-ibdata1 目錄下麵找到對應的 page 號。 假設已經完成了前兩步,拿到了 INDEX ID。

stream_parser 這個工具不但可以讀文件,還可以讀磁碟,會根據 innodb 數據格式把數據頁讀出來。為了恢復 68 號數據頁,我們執行下麵幾個步驟:

  1. 找到被刪除的 ibd 文件的掛載磁碟 /dev/sda5:

     $df 
     Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda2 52327276 47003636 2702200 95% /
     tmpfs 99225896 9741300 89484596 10% /dev/shm
     /dev/sda1 258576 55291 190229 23% /boot
     /dev/sda5 1350345636 1142208356 208137280 85% /home
     /dev/sdb1 3278622264 2277365092 1001257172 70% /disk1
    
  2. 根據 INDEX ID , 磁碟大小執行 stream_parser,-t 表示磁碟的大小。

     	$./stream_parser -f /dev/sda5 -s 1G -t 1142G
    
  3. 在目錄 pages—sda5 下找到 68 號頁,像上一個小節第 4 步一樣恢複數據即可。
  4. <劃重點> 測試了三次,有兩次是恢復不出來的,因為文件很可能被其它進程重寫,這取決於文件系統調度還有整體伺服器的負載。
  5. 如果掛載的磁碟上還有其它 mysqld 的數據目錄,那麼很可能一個 page 文件會很大,監測到其它 ibd 文件的數據,同一個頁號的綜合在一起,這樣辨別出我們需要的數據就比較麻煩。

文件頁臟寫

MySQL 每次從磁碟讀取數據的時候都會進行 checksum 校驗,如果校驗失敗,整個進程就會重啟或者退出,校驗失敗很可能是文件頁被臟寫了。使用恢復工具直接讀取文件很可能可以把未被臟寫的行或者頁讀取出來,損失降到最低。

模擬臟寫

同樣使用目錄 sakila/actor.sql 中的例子,innodb_per_file_table = on:

CREATE TABLE `actor` (
 `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
 `first_name` varchar(45) NOT NULL,
 `last_name` varchar(45) NOT NULL,
 `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`actor_id`),
 KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8; insert into actor(first_name, last_name) values('zhang', 'jian'); insert into actor(first_name, last_name) values('zhan', 'jian'); insert into actor(first_name, last_name) values('zha', 'jian'); insert into actor(first_name, last_name) values('zh', 'jian'); insert into actor(first_name, last_name) values('z', 'jian'); 

模擬臟寫,打開 actor.ibd 文件, 使用 ‘#’ 覆蓋其中一行數據,

00fdb870-b7df-4122-b7d9-1622bc354737.png

 

從系統表空間確定 INDEX ID (參考 表結構恢復 小節)

$./stream_parser -f /home/zj118228/rds_5616/data/ibdata1
$./stream_parser -f ~/rds_5616/data/per/actor.ibd
$./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql
$./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql

INDEX ID 為 76,讀取數據:

$./c_parser -6f pages-actor.ibd/FIL_PAGE_INDEX/0000000000000076.page -t sakila/actor.sql

 

d27b1b03-ac82-4d60-9717-1c58d9587e64.png
看到有一行數據被 # 號覆蓋,然後丟失了一行。

 

 

臟寫之後資料庫是起不來的,因為 ibd 文件已經損壞了,但此時我們已經拿到了恢復之後的數據,需要把恢復之後的數據導入到資料庫里。導入之前刪除 actor.ibd 文件,然後啟動資料庫後執行 drop table actor, 然後再重新創建表,導入數據即可。如果不小心把 frm 文件也刪掉了,是沒法 drop table 的,可以在其它資料庫里建一個同名,結構相同的表生成 frm 文件,然後拷貝到被刪除的目錄下,然後再執行 drop table。參考:Troubleshooting

原理淺析

c_parser

恢復工具 c_parser 其實是按照 innodb 存儲數據的格式來分析哪些是我們需要的數據本身,所以頁上的數據可以分為兩類:1. 用戶數據 2. 元數據。而元數據的功能其實並不相同,有些損壞無傷大雅,有些損壞卻可能導致整個頁無法恢復。這裡有幾篇介紹Innodb 行記錄格式1 and Innodb 行記錄格式2 ,上一個小節中行記錄格式是 Compact,來分析一下為什麼會丟了一行數據。

這是完好的數據頁,上面是臟寫是把第 12 行數據全部覆蓋了,根據 Compact 類型的格式,12 行末尾的 04 03 表示下一行變長數據類型(‘zha’ ‘jian’)的長度倒序,被覆蓋之後當然無法解析,於是就丟了一行。那麼為什麼沒有影響後續的行數據呢?第 13 行第 2 列的數據 21 表示下行數據的偏移,幸運的沒有被覆蓋。如果這個位元組被覆蓋,那麼整個格式就亂了,無法解析。

4abe1824-db59-46a4-a610-a24a3cd9bfd0.png

 

試了其它幾種情況:

  • 第六行第五列 004C 表示 page 的號,破壞之後 stream 出來的頁號會變,所以從 Innodb 系統表得到的主鍵索引頁號就不對了。
  • infimumsupremum 破壞之後 stream 無法檢測出頁,所以根本產生不了可恢復的數據。

stream_parser

c_parser 是分析頁面中用戶的行數據,從參數中傳入 CREATE TABLE 語句,根據定義的數據格式逐行解析,得到最終恢復的數據。而 stream_parser 是分析 ibd/ibdata 文件(或者掛載的磁碟),得到每一個數據頁的。根據數據頁的元數據,如果滿足下列條件,就被認為是一個合法的 Innodb Index 數據頁:

  • 頁面最開始前四個位元組(checksum)不為 0
  • 頁面 5-8 位元組(頁面在 tablespace 中的偏移)不為零,且小於 (ib_size / UNIV_PAGE_SIZE) 最大偏移量,ibd 文件大小除以 Innodb 頁大小。
  • 在固定偏移處找到 infimumsupremum

參考 stream_parser.c 中的函數 valid_innodb_page, 關於 Blob page 判定條件略有不同,詳細參考 valid_blob_page,這裡以 Index page 為例。

得到一個合法的頁後就以 UNIV_PAGE_SIZE 為大小寫入到以 index_id 命名的文件中(也就是 c_parser 讀入的頁號判斷標準)。

頁數據格式

這裡引用下登博畫的大圖:

undefined

 

根據圖中數據格式,如果頁面前 8 位元組被重寫為 0 ,infimumsupremum 被寫壞,stream_parser 無法檢測出有效頁。如果圖中 Page_no 被寫壞,那麼我們從 Innodb 數據字典中獲得的需要解析的文件頁號恐怕就不對了,也不知道從那裡去恢復。

所以這種恢復方式是寄托在重要頁元數據和行元數據沒有被臟寫的前提下的,上述分析過後,重要的元數據所占比例較小,如果每個位元組被臟寫的概率相同,那麼數據的可恢復性還是比較可觀的。

最後,對於文件系統損壞或者磁碟 corruption,最重要的把數據拷貝出來,而不是去恢覆文件系統或者磁碟,因為上述工具的恢復是基於數據的,參考這篇文章,第一時間使用 dd 命令製作磁碟鏡像,再走上述的恢復流程即可。

 

原文地址: https://yq.aliyun.com/articles/281230?utm_content=m_37044

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概念 簡稱brew,是Mac OSX上的軟體包管理工具,能在Mac中方便的安裝軟體或者卸載軟體,相當於Red hat的yum、Ubuntu的apt get。 安裝命令 ruby e "$(curl fsSL "https://raw.githubusercontent.com/Homebrew/in ...
  • #本文並非原創,屬於本人學習中的記錄筆記或是轉存筆記,如果涉及到哪位高人的創作權益,敬請海涵! Vim 是一個上古神器,本篇文章主要持續總結使用 Vim 的過程中不得不瞭解的一些指令和註意事項,以及持續分享一個前端工作者不得不安裝的一些插件,而關於 Vim 的簡介,主題的選擇,以及為何使用 vim- ...
  • 本章主要講解了section header的定義,各欄位含義和可能的取值。然後介紹了系統預定義的一些section名稱。最後我們綜合運用第二章和第三章的知識,做了一個讀取section names的練習。 ...
  • Redis集群實現了較為完善的高可用方案。本文將詳細介紹集群,主要內容包括:集群的作用;集群的搭建以及設計方案;集群的基本原理;客戶端訪問集群的方法;以及其他實踐中需要的集群知識。 ...
  • 《大數據時代》是國外大數據研究的先河之作,本書作者維克托•邁爾•舍恩伯格被譽為“大數據商業應用第一人”,擁有在哈佛大學、牛津大學、耶魯大學和新加坡國立大學等多個互聯網研究重鎮任教的經歷,早在2010年就在《經濟學人》上發佈了長達14頁對大數據應用的前瞻性研究。 維克托•邁爾•舍恩伯格在書中前瞻性地指 ...
  • 下載mysql安裝程式 官方下載地址:http://dev.mysql.com/downloads/mysql/ 解壓下載文件,如圖 其中data和my.ini文件需要自己創建 my.ini 文件配置如下: 配置環境變數 電腦 屬性 高級系統屬性 環境變數 初始化data目錄 以管理員命令運行cmd ...
  • 1. 基礎知識 安裝mysql5.6資料庫Mysql binlog初步理解 2. 配置mysql 開啟binlog、修改binlog模式為Row Level模式 修改mysql配置文件,在[mysqld]下增加以下內容 3. 重啟mysql資料庫 binlog開啟 生成文件/var/lib/mysq ...
  • MySQL · 引擎特性 · InnoDB 崩潰恢復過程 innodb中的 3個lsn innodb的lsn和oracle的scn一樣,是一個重要的概念。比如 在flush list中正是是使用low lsn作為鏈表的條件參考buf_page_t中的lsn_t oldest_modification ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...