写点什么

innodb_ruby 项目简介

用户头像
keaper
关注
发布于: 3 小时前
innodb_ruby 项目简介

本系列文章翻译自 Jeremy Cole's Blog 中的 InnoDB系列 文章 。共 16 篇,本文为第 2 篇。原文链接:A quick introduction to innodb_ruby


因翻译水平有限,为了避免对读者造成误解,一些专有名词的翻译会在其后用[]标记出原文。

innodb_ruby 项目简介

本文中所使用的 `innodb_ruby` 版本为 [0.8.8(发布于2014年2月3日)]


On learning InnoDB: A journey to the core译文)一文中,我提到了 innodb_ruby 项目。现在我来演示一下它的一些功能。我不会去解释所有提及的 InnoDB 结构,这不是本文的初衷,后面的文章中会继续介绍这些结构!

安装 innodb_ruby

如果你熟悉 Rubygems,并且已经安装并配置好了 Ruby 环境,那么你只需要执行如下命令即可完成安装(因为我经常将 innodb_ruby gems 推送到 RubyGems,所以可以直接使用 gem install 命令完成安装):


gem install innodb_ruby
复制代码


译者注:RubyGemsRuby 的一个包管理框架,类似于Python中的pipRubyGems管理的包称为gems,同时RubyGems有一个公共的存储库 rubygems.org 供开发人员使用。


如果这个方法不起作用,那么你可能需要查看 RubyGems文档 来帮助你完成安装。或者就此放弃一切希望。:-D


当你成功安装了 innodb_ruby,在你的 path 下应该有一个 innodb_space 命令:


$ innodb_space Error: File must be provided with -f argument
Usage: innodb_space -f <file> [-p <page>] [-l <level>] <mode> [<mode>, ...]
复制代码

生成一些数据

为了进行演示,我们需要创建更多的记录以便检查不同的数据结构。首先需要确保你运行了一个版本足够新的 MySQL ServerMySQL 5.5 或者更新的版本),使用 Barrracuda 格式的表,并且启用了 innodb_file_per_table(译者注:通过innodb_file_per_table启动参数来开启)。使用下面这一段 ruby 代码可以创建一个非常简单的表并填充一些数据:


#!/usr/bin/env ruby
require "mysql"
m = Mysql.new("127.0.0.1", "root", "", "test")
m.query("DROP TABLE IF EXISTS t")
m.query("CREATE TABLE t (i INT UNSIGNED NOT NULL, PRIMARY KEY(i)) ENGINE=InnoDB")
(1..1000000).to_a.shuffle.each_with_index do |i, index| m.query("INSERT INTO t (i) VALUES (#{i})") puts "Inserted #{index} rows..." if index % 10000 == 0end
复制代码


执行这段代码将会生成一个包含100万行数据的表(为了让事情更有趣,我们将这些数据是按照随机的顺序插入),大约48MB,也就是大约3,071个大小为16KB的页面。


译者注:执行上述代码需要安装mysqlgem。如果通过gem install mysql安装失败,可以尝试使用gem install mysql2来安装mysql2来操作MySQL,并且需要对代码进行如下改写:


#!/usr/bin/ruby -w
require "mysql2"
m = Mysql2::Client.new( :host => "127.0.0.1", :username => "root", :password => "", :database => "test")
m.query("DROP TABLE IF EXISTS t")
m.query("CREATE TABLE t (i INT UNSIGNED NOT NULL, PRIMARY KEY(i)) ENGINE=InnoDB")
(1..1000000).to_a.shuffle.each_with_index do |i, index| m.query("INSERT INTO t (i) VALUES (#{i})") puts "Inserted #{index} rows..." if index % 10000 == 0end
复制代码


(请注意,如果你自己尝试使用上面的方法来新建数据,你应该通过SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty'命令观察并等待所有脏页刷新完成后再继续,因为下面介绍的工具将要访问的是磁盘上的表空间文件,与运行中的 InnoDB 实例中的数据并不完全一致。)


译者注:Innodb_buffer_pool_pages_dirty 状态变量表示 InnoDB 缓冲池[buffer pool]中的当前的脏页数量。该状态变量的值为0则表示所有的脏页已经刷新到磁盘。

检查表空间文件

innodb_space中的space-page-type-regions命令能够提供对表空间文件的最高级别概览,它会将相同类型的连续页面打印为一行:


$ innodb_space -f test/t.ibd space-page-type-regionsstart       end         count       type                0           0           1           FSP_HDR             1           1           1           IBUF_BITMAP         2           2           1           INODE               3           37          35          INDEX               38          63          26          FREE (ALLOCATED)    64          2188        2125        INDEX               2189        2239        51          FREE (ALLOCATED)    2240        2240        1           INDEX               2241        2303        63          FREE (ALLOCATED)    2304        2304        1           INDEX               2305        2367        63          FREE (ALLOCATED)    2368        2368        1           INDEX               2369        2431        63          FREE (ALLOCATED)    2432        2432        1           INDEX               2433        2495        63          FREE (ALLOCATED)    2496        2496        1           INDEX               2497        2687        191         FREE (ALLOCATED)    
复制代码


先不必深究 InnoDB 内部实现细节,你可以看到 一些 InnoDB 的簿记[bookkeeping]结构(FSP_HDR 页、 IBUF_BITMAP 页 和 INODE 页)、实际的表数据(INDEX 页)和空闲空间(FREE (ALLOCATED) 页)。


每个索引(实际上是每个“文件段” 或 每个索引对应的“文件段”)所消耗的空间也很有趣(输出中的 usedallocated 是以页为单位的):


$ innodb_space -f test/t.ibd space-indexesid          root        fseg        used        allocated   fill_factor 15          3           internal    3           3           100.00%     15          3           leaf        2162        2528        85.52%      
复制代码


每个索引都有一个 “内部[internal]”文件段(用于非叶子结点页面)和一个“叶[leaf]”文件段(用于叶子结点页面)。页面可能被分配给一个文件段,但是当前还未使用(也就是类型为 FREE (ALLOCATED)的页面),"fill_factor"列表示的是使用的页面和未使用的页面的比率。(请注意,这个比率与索引页 有多满 没有关系,那完全是另外一回事。)


译者注:这里说的索引有多满指的是索引树上页面的饱和度,删除记录或者添加记录导致的页分裂都有可能导致页面的饱和度降低,也就是实际保存真实数据的空间变少了。

检查单个页面

page-dump 命令能够转储[dump]关于单个页面的所有信息。目前它严重依赖于 Ruby 中经典的美化打印[pretty-printer] 模块 pp 来打印这些结构,清理这个依赖将是未来需要做的一件大事。innodb_ruby 会首先使用最基本的 Innodb::Page 类解析页面,然后根据通用 Header 中的 type 字段将不同的类型的页面交给专门的类进行进一步解析(如 Innodb::Page::INDEX 类用于解析 INDEX 类型的页面)。


我们来观察一下空间中第一个 INDEX 页面,也就是是上面创建的测试表的索引树的根节点,它位于3号页面:


$ innodb_space -f test/t.ibd -p 3 page-dump
复制代码


第一行输出会告诉你哪个类正在处理这个页面:


#<Innodb::Page::Index:0x007fe304855360>:
复制代码


紧接着是 FIL Header的信息:


fil header:{:checksum=>621772966, :offset=>3, :prev=>nil, :next=>nil, :lsn=>102947976, :type=>:INDEX, :flush_lsn=>0, :space_id=>1}
复制代码


FIL HeaderFIL Trailer 对于所有页面类型都是通用的,主要包含有关页面自身的相关信息。


而除此之外的其他信息则取决于页面的类型;对于 INDEX 类型页面,page-dump 命令会转储以下信息:


  • “页面头部[page header]”,关于INDEX类型 页面的相关信息

  • “文件段头部[fseg header]”,有关该索引使用的文件段文件段可以视为一组区段)的空间管理信息

  • 页面不同部分的空间大小统计(以字节为单位): 空闲空间、数据占用空间以及记录大小 等

  • 系统记录[system records],infimum 记录和 supremum 记录

  • 页面目录[page directory]的内容,用于提高记录搜索的效率

  • 用户记录[records],由用户存储的实际数据(只有加载了“记录描述器[record describer]”,用户记录中具体的字段才会被解析)


译者注:infimum 记录和 supremum 记录是InnoDB中的INDEX类型的页面中用于表示“无穷小”和“无穷大”的两条特殊记录。

看看索引空间的消耗

使用 space-index-pages-summary 命令可以看到 INDEX 页面的一些重要的空间消耗数据:


$ innodb_space -f test/t.ibd space-index-pages-summary | head -n 10page        index   level   data    free    records 3           15      2       26      16226   2       4           15      0       9812    6286    446     5           15      0       15158   860     689     6           15      0       10912   5170    496     7           15      0       10670   5412    485     8           15      0       12980   3066    590     9           15      0       11264   4808    512     10          15      0       4488    11690   204     11          15      0       9680    6418    440     
复制代码


通过输出你可以看到数据占用空间、空闲空间以及表中的记录数量。


如果你已经安装了 gnuplot,并且安装了 Ruby gnuplot gem,那么就很容易为这些信息制作一个有用的散点图(尽管不是很漂亮) :


$ innodb_space -f test/t.ibd space-index-pages-free-plotWrote t_free.png
复制代码


space-index-pages-free-plot命令生成的图如下:



y轴 表示每页的空闲空间大小,x轴 表示页码,同时也表示文件偏移量。

理解行数据

为了使innodb_ruby在检查实际表时发挥作用,需要为其提供一些理解表结构的方法。这可以通过"describer"类来完成,该类是可以动态加载的。这是 innodb_ruby 没有良好文档记录(或良好设计)的一个方面。上述表(表结构为i INT UNSIGNED NOT NULL, PRIMARY KEY (i) 并且没有其他的索引)的一个简单的描述类[describer classs]如下:


class SimpleTDescriber < Innodb::RecordDescriber  type :clustered  key "i", :INT, :UNSIGNED, :NOT_NULLend
复制代码


将这个类保存在一个simple_t_describer.rb文件中,便可以在 innodb_space 中使用 -r <file> 加载 ,并使用-d <class> 参数启用:


$ innodb_space -f test/t.ibd -r /path/to/simple_t_describer.rb -d SimpleTDescriber <mode>
复制代码


加载 记录描述器[record describer] 主要完成了两件事:


  • page-dump命令中启用记录解析和转储。这可以使得 :key:row 能够被填充到转储的记录中,还包括事务ID[transaction ID] 和 回滚指针[roll pointer](这两个隐藏字段在存储位置上位于 键字段非主键字段 之间,因此至少需要知道如何解析键字段才能访问这两个隐藏字段)

  • 允许使用所有的索引递归功能,包括index-recurse命令。为了解析 InnoDBB+树 中的“节点指针记录[node point record]”,需要解析记录内容的能力,


译者注:

  1. 事务ID回滚指针 是在主键索引中每条记录都有的隐藏字段

  2. :key:row 在记录描述器[record describer]中分别用来描述一个索引中的 键字段非键字段。在主键索引中 键字段 包含主键定义中的所有字段,非键字段 包含表定义的其他字段;在二级索引中,键字段 包含二级索引定义的所有字段, 非键字段 包含所有的主键字段。

  3. “节点指针记录[node point record]” 指的是InnoDB B+ 树中的非叶子结点页面中的记录,其中的记录在索引信息之后还包含子节点的页码,所以需要先解析索引字段才能够得到子结点的页码,以便进行索引遍历。


这里有一些示例页面的完整转储记录:test_t_page_3_page_dump.txt (索引根页面)和 test_t_page_4_page_dump.txt (索引叶子页面)。

递归索引

有了记录描述器之后,就可以使用 index-recurse命令 递归遍历索引:


$ innodb_space -f test/t.ibd -r /path/to/simple_t_describer.rb -d SimpleTDescriber -p 3 index-recurseROOT NODE #3: 2 records, 26 bytes  NODE POINTER RECORD >= (i=252) -> #36  INTERNAL NODE #36: 1117 records, 14521 bytes    NODE POINTER RECORD >= (i=252) -> #4    LEAF NODE #4: 446 records, 9812 bytes      RECORD: (i=1) -> ()      RECORD: (i=2) -> ()      RECORD: (i=3) -> ()      RECORD: (i=4) -> ()      RECORD: (i=5) -> ()
复制代码


这实际上会按照升序遍历 B+树 (也就是全表扫描) ,同时打印出遍历到的每个节点(也就是页面)的一些信息,并转储叶子结点页面上的用户记录。这里有一个更大的输出示例(10k行):test_t_page_3_index_recurse.txt

未来还会有更多

我希望这是一个有用的入门介绍。未来还会有更多有关innodb_ruby的介绍。非常欢迎提供补丁,评论和建议!


更新 1: Davi 指出了一些已经更正的错误和错误。:) 确保你使用的是上面例子中的最新代码。

发布于: 3 小时前阅读数: 4
用户头像

keaper

关注

还未添加个人签名 2018.01.19 加入

还未添加个人简介

评论

发布
暂无评论
innodb_ruby 项目简介