Bootstrap

[翻译]innodb_ruby 项目简介

本系列文章翻译自 中的 文章 。共 16 篇,本文为第 2 篇。原文链接:

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

innodb_ruby 项目简介

本文中所使用的 `innodb_ruby` 版本为 []

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

安装 innodb_ruby

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

gem install innodb_ruby

译者注: 是 的一个包管理框架,类似于中的。管理的包称为,同时有一个公共的存储库 供开发人员使用。

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

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

$ innodb_space 
Error: File must be provided with -f argument

Usage: innodb_space -f  [-p ] [-l ]  [, ...]

生成一些数据

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

#!/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 == 0
end

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

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

#!/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 == 0
end

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

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

检查表空间文件

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

$ innodb_space -f test/t.ibd space-page-type-regions
start       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_space -f test/t.ibd space-indexes
id          root        fseg        used        allocated   fill_factor 
15          3           internal    3           3           100.00%     
15          3           leaf        2162        2528        85.52%      

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

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

检查单个页面

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

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

$ innodb_space -f test/t.ibd -p 3 page-dump

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

#:

紧接着是 的信息:

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

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

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

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

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

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

  • 系统记录[], 记录和 记录

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

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

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

看看索引空间的消耗

使用 命令可以看到 页面的一些重要的空间消耗数据:

$ innodb_space -f test/t.ibd space-index-pages-summary | head -n 10
page        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     

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

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

$ innodb_space -f test/t.ibd space-index-pages-free-plot
Wrote t_free.png

命令生成的图如下:

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

理解行数据

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

class SimpleTDescriber < Innodb::RecordDescriber
  type :clustered
  key "i", :INT, :UNSIGNED, :NOT_NULL
end

将这个类保存在一个文件中,便可以在 中使用 加载 ,并使用 参数启用:

$ innodb_space -f test/t.ibd -r /path/to/simple_t_describer.rb -d SimpleTDescriber 

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

  • 在命令中启用记录解析和转储。这可以使得 和 能够被填充到转储的记录中,还包括[] 和 [](这两个隐藏字段在存储位置上位于 和 之间,因此至少需要知道如何解析才能访问这两个隐藏字段)

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

译者注:

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

递归索引

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

$ innodb_space -f test/t.ibd -r /path/to/simple_t_describer.rb -d SimpleTDescriber -p 3 index-recurse
ROOT 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) -> ()

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

未来还会有更多

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

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