你的位置:首页 > 数据库

[数据库]mysql innodb 数据打捞(一)innodb 页面结构特征


如果文件系统损坏或意外删除了数据库文件,只要磁盘空间没有被覆盖,其实数据都还在磁盘的扇区中,还是可以恢复出来的,有些通用的文件恢复工具好象也可以恢复文件 ,但这里要研究的是在通用文件 恢复工具失效的时候。

innodb文件是按页保存的,这为打捞工作提供了非常有利的条件,页面具有一些特征,我们可以根据页面特征来把数据页从磁盘中提取出来,也就是数据打捞。

Innodb表空间的概念:
表空间,数据文件的集合,在innodb就是idb文件集合.
一个表空间可以有多个idb文件 组成;
innodb会对数据文件每16k进行编号(从0开始),作为该页的页号,并且相邻文件的编号是也是连贯的;
Innodb使用表空间id来区分不同的表空间,共享表空间的id总是为0,其他独立表空间的id依次递增。那么,某个页面的唯一标识就是<strong>(space_id, page_no);</strong>
(ps:昨天我扫描我电脑上的ibdata1)

首先,我们需要知道innodb文件页面结构和结构中每个部分的特征;
页面整体结构,图片来源网络(http://www.cnblogs.com/vinchen/archive/2012/09/10/2679478.html)

<span ><img src="http://www.veryjuly.com/blog/wp-content/uploads/2016/05/2012091022353013-300x220.jpg" alt="2012091022353013" width="300" height="220" /></span>

1. 页头(Page Header):记录页面的控制信息,共占150字节,包括页的左右兄弟页面指针、页面空间使用情况等,

2. 最小虚记录、最大虚记录:两个固定位置存储的虚记录,本身并不存储数据。最小虚记录比任何记录都小,而最大虚记录比任何记录都大。

3. 记录堆(record heap):指上图的橙黄色部分。表示页面已分配的记录空间,也是索引数据的真正存储区域。记录堆分为两种,即有效记录和已删除记录。有效记录就是索引正常使用的记录,而已删除记录表示索引已经删除,不在使用的记录,如上图的深蓝色部分。随着记录的更新和删除越来越频繁,记录堆中已删除记录将会越多,即会出现越来越多的空洞(碎片)。这些已删除记录连接起来,就会成为页面的自由空间链表。

4. 未分配空间:指页面未使用的存储空间,随着页面不断使用,未分配空间将会越来越小。当新插入一条记录时,首先尝试从自由空间链表中获得合适的存储位置(空间足够),如果没有满足的,就会在未分配空间中申请。

5. slot区:slot是一些页面有效记录的指针,每个slot占两个字节,存储了记录相对页面首地址的偏移。如果页面有n条有效记录,那么slot的数量就在n/8+2~n/4+2之间。下一节详细介绍slot区,它是记录页面有序和二分查找的关键
6. 页尾(Page Tailer):页面最后部分,占8个字节,主要存储页面的校验信息。

页面中的页头,最大/最小虚记录以及页尾都是页面中有固定的存储位置。
<div>特征主要在Page Header中,但文件 数据部分也有些特征,文件尾也可能是一个特征;</div>
Page Header结构与特征:(分成通用头和不同页类型的头)
*通用页头:表示所有页面都使用的页头,占38个字节;
*数据页头:接下来的112个字节是数据页头,表示数据页的头信息,(根据通用头中不同的页类型这里是不同的头,但我们打捞数据时更关心数据页头,因为数据都保存中数据页中,也就是页类型为FIL_PAGE_INDEX)
数据页页头的总大小为38+112=150字节;

通用页头:(源码中有关通用头的定义在文件 :file0fil.h中)

通用页头共占38个字节,依次为。
*页面checksum值(4字节):页面内容的校验值,用于校验页面内容的合法性和可靠性,
(如果能找出算法,数据打捞时判断页面将是一个很有效的字段)

•页号(4字节):页面在表空间的页号
•左兄弟页面的页号(4字节):上一篇第一节介绍了B+树的基础内容,提到相同层次页面是通过一个双向链表连接起来的。而左兄弟页号就是该页在链表中的左兄弟页号。当然,左右兄弟页面都必须属于同一表空间。
•右兄弟页面的页号(4字节):同上,右兄弟页号。

*页面LSN(8字节):页面最后一次修改的LSN值,用于页面刷盘和恢复。LSN是一个递增的日志序列号。
*页面类型(2字节):innodb有若干种页面类型,通过这个可以区分,数据页面页面类型都为FIL_PAGE_INDEX(17855)。
(17855很有意思呀,简直就是为数据打捞准备的,数据都是保存在这人h类型的页面中的,我们要的就是这个类型的页面,理论只靠这个标志就可以让垃圾块出现的几率小到1/64K,按簇大小4K算,每256G的磁盘才会出现一个垃圾块)

•文件刷盘LSN(8字节):只有共享表空间各文件首页使用,记录服务器正常结束的LSN值,一般只用于检查和校验
如果是这样,也就是说正常的数据页不用吗,那这样就又是一人h标识)
•表空间ID(4字节):表空间ID;(如果表空间从0开始的话,那么正常的数据库表空间ID都不会在y大,这也许也是个判断依据。)
2.2 数据页头
数据页头共占112个字节,成员比较多,也相对复杂些,由低字节到高字节,包括一下成员:

PAGE_N_DIR_SLOTS(2字节):指slot区的slot的个数,每个slot两个字节。
PAGE_HEAP_TOP(2字节):堆顶指针,未分配空间的首地址
(如果是相对页的位置,那么应该在页头之后)

PAGE_N_HEAP(2字节):记录堆内记录数,包括已删除记录和最大最小虚记录。因此,初始化为2。第15 bit为1表示row_format=compact

PAGE_FREE(2字节):第一个已删除记录偏移。所有已删除记录连接在一起成为自由空间链表。
(又是偏移,可以判断一下了)

PAGE_GARBAGE(2字节):已删除记录占用的总字节数,即记录堆内已删除的总空间大小。
PAGE_LAST_INSERT(2字节):最后一次插入记录的偏移
PAGE_DIRECTION(2字节):页面最后一次的插入方向,如果本次插入比上次插入的值大就是PAGE_RIGHT,反之就是PAGE_LEFT;

PAGE_N_DIRECTION(2字节):相同插入方向的连续插入次数
PAGE_N_RECS(2字节):页面有效记录数
PAGE_MAX_TRX_ID(8字节):最后一次改变页面的事务ID,仅在二级索引中有效,用于二级索引记录MVCC多版本可见性判断。

PAGE_LEVEL(2字节):页面在索引中的层次,叶子节点层次为0。
PAGE_INDEX_ID(8字节):页面所属索引的ID。
(这个在数据恢复中很重要,)
PAGE_BTR_SEG_LEAF(10字节):叶子节点段头inode信息,仅在B+树的根页有效
PAGE_BTR_SEG_TOP(10字节):内节点段头inode信息,仅在B+树的根页有效
数据页头的成员较多,PAGE_N_DIR_SLOTS、PAGE_N_HEAP、PAGE_N_RECS比较简单,主要

4. 页尾
页尾是页面的最后8个字节,包括两部分,主要用于页面内容的校验,其中包括:

OLD_CHKSUM:4字节,页尾的checksum值,与通用页头的页面checksum值使用不同的算法。
LSN_LOWER_4BYTES:4字节,记录通用页头中页面LSN的低四字节。
通过页头的页面checksum值、页面LSN和页尾的OLD_CHKSUM、LSN_LOWER_4BYTES可以判断一个页面是否corrupted,判断算法是(详见函数buf_page_is_corrupted):

1. 判断LSN_LOWER_4BYTES是否等于页面LSN的低四字节,若不相等,返回TRUE。

用于页面计数,每次页面插入、更新或删除记录,都有可能影响这些值。而其他页头可以通过一些具体操作介绍其关键作用。