使用innodb_ruby分析InnoDB内部结构

Posted 运维那点事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用innodb_ruby分析InnoDB内部结构相关的知识,希望对你有一定的参考价值。

从博客复制过来的一篇文章,排版很乱,所以你懂得。

一、innodb_ruby介绍

如果你想了解mysql InnoDB内部结构,不好意思,官方在5.7版本之前并没有什么好的工具。所以你只能自己写一个工具去分析表空间文件或使用别人开源的工具,比如innodb_ruby。在MySQL 5.7版本,官方加强了innochecksum工具,使之也可以简单分析表空间,比如查看页类型统计信息等。但跟Jeremy Cole大神带来的Innodb格式化分析工具innodb_ruby相比还是弱了点(可以对比一下相同的功能)。innodb_ruby工具的强大之处在于可以将Innodb内部存储的信息多方位格式化的解释给大家,让人更清晰的理解InnoDB的内部结构。它是一款非常轻量级的应用,同时存在bug风险,不建议给生产环境使用。但是我们在测试环境就可以很好的了解该工具和该工具带来的便利。

README中是这样说的:

  • 一款学习工具,让你很轻松的理解InnoDB架构。

  • 一款教学工具,通过ruby的irb的交互式界面,可以看到InnoDB的架构与发生的变化。

  • 一款调查工具,InnoDB引擎不会告诉你它在磁盘正在做那些任务或者做过那些任务。例如,我们可以获取页的饱和度,每页具体含有多少记录,B+Tree的具体架构等详细内容。

  • 一款调试工具,当一些行为或操作会影响到数据结构的时候,可以通过innodb_ruby工具直观的审查,确保与期望的结果相同。

二、innodb_ruby安装

此工具是使用Ruby写的,所以自然要安装Ruby环境。然后使用RubyGems进行安装innodb_ruby,所以自然也要安装RubyGems。我这里在CentOS下,直接就是用YUM安装了。

$ yum ruby ruby-devel rubygems

以root身份安装:

$ sudo gem install innodb_ruby

以指定用户身份安装:

$ sudo gem install --user-install innodb_ruby

运行:

$ innodb_space ...

RubyGems(简称 gems)是一个用于对 Ruby 组件进行打包的 Ruby 打包系统。 它提供一个分发 Ruby 程序和库的标准格式,还提供一个管理程序包安装的工具。RubyGems的功能类似于Linux下的yum工具,使用它可以方便的从远程服务器下载并安装Rails。

三、使用方法

有两种方式运行innodb_space。

$ innodb_space --help | more
innodb_space -s ibdata1 [-T tname [-I iname]] [options]
innodb_space -f tname.ibd [-r ./desc.rb -d DescClass] [options] <mode>

针对单个空间文件(ibdata或.ibd):

选项 参数 描述
-F <文件名> 加载表空间文件(系统表空间或独立表空间)

针对将自动加载每个文件的表空间文件的系统表空间:

选项 参数 描述
-s <文件名> 加载系统表空间文件(例如ibdata1)
-T <表名> 使用给定的表名,格式DB_NAME/TABLE_NAME。
-I <索引名称> 使用给定的索引名称。

四、功能介绍

先创建一些测试数据。

create table t(
  a int not null,
  b varchar(8000),
  c int not null,
  primary key (a),
  key idx_c (c)
) engine=innodb;

insert into t select 1,repeat('a',7000),1;
insert into t select 2,repeat('a',7000),2;
insert into t select 3,repeat('a',7000),3;
insert into t select 4,repeat('a',7000),4;

在这个表中,插入b列长度为7000,因此可以人为的方式使目前每个页(每页16k)只能存放两条记录。

  • Space File Structure(表空间文件结构)

system-spaces

在该模式下,innodb_space列出所有物理对象的数量。

$ innodb_space -s ibdata1 system-spaces
name                            pages       indexes     
(system)                        13056       6 
test/t                          9           2
...

space-indexes

列出表空间中所有索引统计信息(系统空间或每个文件表空间)

$ innodb_space -s ibdata1 -T test/t space-indexes
id          name                            root        fseg        used        allocated   fill_factor 
264         PRIMARY                         3           internal    1           1           100.00%     
264         PRIMARY                         3           leaf        3           3           100.00%     
265         idx_c                           4           internal    1           1           100.00%     
265         idx_c                           4           leaf        0           0           0.00%

name:索引的名称,PRIMARY代表的就是聚集索引,因为InnoDB表是聚集所以组织表,行记录就是聚集索引;idx_c就是辅助索引的名称。

root:索引中根节点的page号;可以看出聚集索引的根节点是第3个page(为什么是从第三个page开始,看下文space-page-type-regions),辅助索引的根节点是第4个page。

fseg:page的说明,internal表示非叶子节点或属于根节点,leaf表示叶子节点(也就是数据页)。

used:索引使用了多少个page,可以看出聚集索引的根节点点使用了1个page,叶子节点使用了3个page;辅助索引idx_c的叶子节点使用了1个page。

allocated:索引分配了多少个page,可以看出聚集索引的根节点分配了1个page,叶子节点分配了3个page;辅助索引idx_c的叶子节点分配了1个page

fill_factor:索引的填充度,所有的填充度都是100%。

space-page-type-regions

遍历空间中的所有页面,统计每个类型的页共占用了多少页。

$ innodb_space -s ibdata1 -T test/t space-page-type-regions
start       end         count       type                
0           0           1           FSP_HDR             
1           1           1           IBUF_BITMAP         
2           2           1           INODE               
3           7           5           INDEX               
8           8           1           FREE (ALLOCATED)

start:从第几个page开始。

end:从第几个page结束。

count:占用了多少个page。

type:page的类型。

从上面的结果可以看出:“FSP_HDR”、“IBUF_BITMAP”、“INODE”是分别占用了0,1,2号的page,从3号page开始才是存放数据和索引的页(Index),占用了3~7号的page,共5个page。

接下来,根据得到的聚集索引和辅助索引的根节点来获取索引上的其他page的信息。

space-page-type-summary

遍历所有页面并按类型打印总页数总计。

$ innodb_space -s ibdata1 -T test/t space-page-type-summary
type                count       percent     description         
INDEX               5           55.56       B+Tree index        
INODE               1           11.11       File segment inode  
IBUF_BITMAP         1           11.11       Insert buffer bitmap
FSP_HDR             1           11.11       File space header   
ALLOCATED           1           11.11       Freshly allocated

space-extents-illustrate

统计所有的页在表空间的饱和度信息,每个页面显示彩色块(按index/purpose着色),根据页面中的数据量调整大小。

$ innodb_space -s ibdata1 -T test/t space-extents-illustrate

  Start Page ╭────────────────────────────────────────────────────────────────╮
           0 │███▁▁▄▇▄░                                                                                            │
             ╰────────────────────────────────────────────────────────────────╯

Legend (█ = 1 page):
  Page Type                                                         Pages    Ratio
  █ System                                                              3   33.33%
  █ Index 266 (test/t.PRIMARY)                                          4   44.44%
  █ Index 267 (test/t.idx_c)                                            1   11.11%
  ░ Free space                                                          1   11.11%

space-lsn-age-illustrate

统计所有的页在表空间的饱和度信息,每个页面显示彩色块 (按页面修改LSN的年龄着色)。

$ innodb_space -s ibdata1 -T test/t space-lsn-age-illustrate

  Start Page ╭────────────────────────────────────────────────────────────────╮
           0 │████████                                                                                             │
             ╰────────────────────────────────────────────────────────────────╯

Legend (█ = 1 page):
       Min LSN <─────────────────────────────────────────> Max LSN     
  133718587851 ███████████████████████████████████████████ 133718640766
  • Page Structure(页结构)

page-account

解释单个页面的用途。

$ innodb_space -s ibdata1 -T test/t -p 3 page-account
Accounting for page 3:
  Page type is INDEX (B+Tree index, table and index data stored in B+Tree structure).
  Extent descriptor for pages 0-63 is at page 0, offset 158.
  Extent is not fully allocated to an fseg; may be a fragment extent.
  Page is marked as used in extent descriptor.
  Extent is in free_frag list of space.
  Page is in fragment array of fseg 1.
  Fseg is in internal fseg of index 266.
  Index root is page 3.
  Index is test/t.PRIMARY.

page-dump

打印页结构信息,需要了解InnoDB页结构。

$ innodb_space -s ibdata1 -T test/t -p 3 page-dump
#<Innodb::Page::Index:0x00000001e582c8>:

fil header:
{:checksum=>926525874,
 :offset=>3,
 :prev=>nil,
 :next=>nil,
 :lsn=>133718640734,
 :type=>:INDEX,
 :flush_lsn=>0,
 :space_id=>133}

fil trailer:
{:checksum=>712405307, :lsn_low32=>574654558}

page header:
{:n_dir_slots=>2,
 :heap_top=>162,
 :garbage_offset=>0,
 :garbage_size=>0,
 :last_insert_offset=>154,
 :direction=>:right,
 :n_direction=>2,
 :n_recs=>3,
 :max_trx_id=>0,
 :level=>1,
 :index_id=>266,
 :n_heap=>5,
 :format=>:compact}

fseg header:
{:leaf=>
  <Innodb::Inode space=<Innodb::Space file="test/t.ibd", page_size=16384, pages=9>, fseg=2>,
 :internal=>
  <Innodb::Inode space=<Innodb::Space file="test/t.ibd", page_size=16384, pages=9>, fseg=1>}

sizes:
  header           120
  trailer            8
  directory          4
  free           16210
  used             174
  record            42
  per record     14.00

page directory:
[99, 112]

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

garbage records:

records:
{:format=>:compact,
 :offset=>126,
 :header=>
  {:next=>140,
   :type=>:node_pointer,
   :heap_number=>2,
   :n_owned=>0,
   :min_rec=>true,
   :deleted=>false,
   :nulls=>[],
   :lengths=>{},
   :externs=>[],
   :length=>5},
 :next=>140,
 :type=>:clustered,
 :key=>[{:name=>"a", :type=>"INT", :value=>1}],
 :row=>[],
 :sys=>[],
 :child_page_number=>5,
 :length=>8}

{:format=>:compact,
 :offset=>140,
 :header=>
  {:next=>154,
   :type=>:node_pointer,
   :heap_number=>3,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :nulls=>[],
   :lengths=>{},
   :externs=>[],
   :length=>5},
 :next=>154,
 :type=>:clustered,
 :key=>[{:name=>"a", :type=>"INT", :value=>2}],
 :row=>[],
 :sys=>[],
 :child_page_number=>6,
 :length=>8}

{:format=>:compact,
 :offset=>154,
 :header=>
  {:next=>112,
   :type=>:node_pointer,
   :heap_number=>4,
   :n_owned=>0,
   :min_rec=>false,
   :deleted=>false,
   :nulls=>[],
   :lengths=>{},
   :externs=>[],
   :length=>5},
 :next=>112,
 :type=>:clustered,
 :key=>[{:name=>"a", :type=>"INT", :value=>4}],
 :row=>[],
 :sys=>[],
 :child_page_number=>7,
 :length=>8}

page-records

汇总页面内的所有记录。

$ innodb_space -s ibdata1 -T test/t -p 3 page-records
Record 126: (a=1) → #5
Record 140: (a=2) → #6
Record 154: (a=4) → #7

上面的结果是解析聚集索引根节点页的信息,1行就代表使用了1个page。所以,叶子节点共使用了3个page,根节点使用了1个page,这是一棵高度为2的B+树,跟space_indexes的解析结果一致。

Record 126: (a=1) → #5

a = 1,代表的就是表中a为1的记录,因为a是主键。

-> #5,代表的是指向5号page。

Record 126: (a=5) → #5,整行的意思就是5号page的a最小值是1,包含了1~2的行记录。

注意:page号并不是连续的。如果想打印出具体行数据,直接“-p 5”就可以打印出具体记录,聚集索引的叶子节点是包含了行记录的所有数据。

同理,解析辅助索引idx_c,但是需要注意的是,在解析辅助索引是,需要加上“-I idx_c”。

$ innodb_space -s ibdata1 -T test/t -I idx_c -p 4 page-records
Record 125: (c=1) → (a=1)
Record 138: (c=2) → (a=2)
Record 151: (c=3) → (a=3)
Record 164: (c=4) → (a=4)

从上面可以看出,辅助索引idx_c的key是c列,对应主键。叶子节点共使用了1个page,这棵B+树高度为1。你可以多构造一点数据,这样,这里显示的信息与3号page类似,比如“(c=1) -> #9”,然后在9号页面会显示“(c=1) → (a=1)”。

page-directory-summary

页目录字典记录。

$ innodb_space -s ibdata1 -T test/redis -p 3 page-directory-summary
slot    offset  type          owned   key
0       99      infimum       1       
1       174     node_pointer  4       ()
2       238     node_pointer  4       ()
3       302     node_pointer  4       ()
4       366     node_pointer  4       ()
5       430     node_pointer  4       ()
6       494     node_pointer  4       ()
7       558     node_pointer  4       ()
8       112     supremum      6

从owned可以看出,每个字典一般包含4~8个数据记录,这里的infimum和supremum是page的下(上)确界。

page-illustrate

详细说明一个页面的内容,并且根据类型进行着色显示。

$ innodb_space -s ibdata1 -T test/redis -p 3 page-illustrate      
      Offset ╭────────────────────────────────────────────────────────────────╮
           0 │█████████████████████████████████████▋██████████████████████████│
          64 │█████████▋███████████████████▋████████████▋████████████▋ ████▋██ │
         128 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██                   │
         192 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         256 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         320 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         384 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         448 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         512 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         576 │█▋       ████▋███▋       ████▋███▋       ████▋███▋       ████▋██│
         640 │█▋                                                                                     |
         704 │                                                                                        │ 
             .......
       16256 │                                                                                        │
       16320 │                                        █▋█▋█▋█▋█▋█▋█▋█▋███████▋        │
             ╰────────────────────────────────────────────────────────────────╯

Legend (█ = 1 byte):
  Region Type                         Bytes    Ratio
  █ FIL Header                           38    0.23%
  █ Index Header                         36    0.22%
  █ File Segment Header                  20    0.12%
  █ Infimum                              13    0.08%
  █ Supremum                             13    0.08%
  █ Record Header                       165    1.01%
  █ Record Data                         132    0.81%
  █ Page Directory                       18    0.11%
  █ FIL Trailer                           8    0.05%
  ░ Garbage                               0    0.00%
    Free                              15941   97.30%
  • Index Structure(索引结构)

index-recurse

通过递归整个B+树(通过递归扫描所有页面,而不仅仅是按列表的叶子页面)来执行索引扫描(执行完整索引扫描):

$ innodb_space -s ibdata1 -T test/t -I PRIMARY index-recurse
ROOT NODE #3: 3 records, 42 bytes
  NODE POINTER RECORD ≥ (a=1) → #5
  LEAF NODE #5: 1 records, 7029 bytes
    RECORD: (a=1) → (b="aaaaaaaaaa.., c=1)
....

可以看到主键(这里是a)与记录的对应关系。

查看辅助索引,可以看到辅助索引是直接索引他对应的主键。

$ innodb_space -s ibdata1 -T test/t -I idx_c index-recurse  
ROOT NODE #4: 4 records, 52 bytes
  RECORD: (c=1) → (a=1)
  RECORD: (c=2) → (a=2)
  RECORD: (c=3) → (a=3)
  RECORD: (c=4) → (a=4)

index-record-offsets

将索引作为索引递归进行递归处理,但在索引页中打印每条记录的偏移量。

$ innodb_space -s ibdata1 -T test/t -I PRIMARY index-record-offsets
page_offset         record_offset       
5                   128                 
6                   128                 
6                   7157                
7                   128

index-level-summary

打印给定级别的所有索引页面的摘要信息。一般level 0就是叶子节点、大于level 0的就是非叶子节点(可能是非叶子节点或根节点,要看B+树高度)。

$ innodb_space -s ibdata1 -T test/t -I PRIMARY -l 0 index-level-summary
page    index   level   data    free    records min_key 
5       266     0       7029    9223    1       a=1
6       266     0       14058   2194    2       a=2
7       266     0       7029    9223    1       a=4
  • Record Structure(记录结构)

record-dump

给定记录偏移量,打印记录的详细说明及其包含的数据。

$ innodb_space -s ibdata1 -T test/t -p 5 -R 128 record-dump
Record at offset 128

Header:
  Next record offset  : 112
  Heap number         : 2
  Type                : conventional
  Deleted             : false
  Length              : 8

System fields:
  Transaction ID: 176582861
  Roll Pointer:
    Undo Log: page 441, offset 272
    Rollback Segment ID: 123
    Insert: true

Key fields:
  a: 1

Non-key fields:
  b: "aaaaa..."
  c: 1
  • Record History(记录历史)

record-history

显示给定记录的unod日志。

$ innodb_space -s ibdata1 -T test/t -p 5 -R 128 record-history
Transaction   Type                Undo record
(n/a)         insert              (a=-1017027736) → ()

undo-history-summary 

显示所有变动过的记录。

$ innodb_space -s ibdata1 undo-history-summary
  • Additional exploration(额外探索)

space-lists

显示表空间列表统计信息(free, free_frag, full_frag, free_inodes, and full_inodes),包括列表中的第一个和最后一个页面的列表长度和列表节点信息。

$ innodb_space -s ibdata1 space-lists
name                length      f_page      f_offset    l_page      l_offset    
free                82          0           3478        0           3918        
free_frag           3           0           1638        0           638         
full_frag           7           0           158         0           318         
full_inodes         4           2           38          498         38          
free_inodes         3           323         38          412         38

space-list-iterate

迭代列表中的所有范围,并在列表中显示范围或inode。

$ innodb_space -s ibdata1 space-list-iterate -L free_frag

space-inodes-summary

打印表空间中每个inode的摘要信息。

$ innodb_space -s ibdata1 space-inodes-summary

<参考>

https://github.com/jeremycole/innodb_ruby/wiki

https://blog.csdn.net/chuck_perry/article/details/64441289

以上是关于使用innodb_ruby分析InnoDB内部结构的主要内容,如果未能解决你的问题,请参考以下文章

使用innodb_ruby探查Innodb索引结构

使用innodb_ruby 分析innodb物理文件

InnoDB存储引擎存储结构详解-实战篇

InnoDB存储引擎存储结构详解-实战篇

innodb 死锁分析之相关表结构说明

019:InnoDB 表空间内部组织结构