第二次编辑
innodb_ruby项目的简介
[这篇文章截至到2014年2月3日,innodb_ruby的版本是0.8.8]
安装 innodb_ruby
如果你熟悉Ruby和gems(或者你刚好有一个配置好的Ruby),我会有规律的推送innodb_ruby gems到RubyGems,所有你只需要:
Ruby有windows环境。参考安装教程:菜鸟教程-Ruby
在我翻译时,时间来到2023年12月16日,innodb_ruby的版本是0.12.0,而ruby的版本是:3.2.2。而且最重要的是MySQL的版本已更新到8.0
gem install innodb_ruby
如果上述安装没有生效,你可能需要试着检查下RubyGems 手册,让你的安装生效,或者直接放弃。
原文的手册地址已不可用,译者也不懂Ruby,建议自行搜索,或者使用菜鸟教程亦或是参考我收集的安装手册Ruby中文文档或者官方文档
当你安装好之后,”innodb_space“命令应该可以执行,参考:
$ innodb_space
Error: File must be provided with -f argument
Usage: innodb_space -f <file> [-p <page>] [-l <level>] <mode> [<mode>, ...]
造点数据
对于以下示例,为了更加适当的测试不同的数据结构,要的不只是几行数据。确保你有一个足够新的MySQL服务器(最好是MySQL 5.5),并且文件格式是"Barrracuda"还要启用"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 == 0
end
这会生成一个100万行数据的表(以随机顺序插入数据,因为我想搞事情),大约48MiB大小,3017个16KiB的page页。(注意:如果你在家里尝试这些,你应该要先看看SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_page_dirty
等待所有的脏页数据刷新进去。因为下面的命令行工具将访问磁盘上的表空间文件不会和正在运行的InnoDB实例相互协调)
上述命令中需要先安装ruby需要的mysql连接包。我使用的方式是安装mysql2替代原命令中的mysql。下面是执行步骤
1.启动cmd先执行D:\your\path\Ruby32-x64\bin\ridk.cmd enable
2.然后再执行:gem install mysql2 --platform=ruby -- --with-mysql-dir=D:/your/path/to/Ruby27-x64/msys64/mingw64
。命令行中的目录需要自行替换,它在你自己的ruby安装目录下。解决办法参考文档
mysql2参考:
require ‘mysql2’
client = Mysql2::Client.new(
:host => ‘127.0.0.1’, # 主机
:username => ‘root’, # 用户名
:password => ‘’, # 密码
:database => ‘test’, # 数据库
:encoding => ‘utf8’ # 编码
)
client.query(“DROP TABLE IF EXISTS m”)
client.query(“CREATE TABLE m (i INT UNSIGNED NOT NULL, PRIMARY KEY(i)) ENGINE=InnoDB”)
(1…1000000).to_a.shuffle.each_with_index do |i, index|
client.query(“INSERT INTO m (i) VALUES (#{i})”)
puts “Inserted #{index} rows…” if index % 10000 == 0
end
使用ruby脚本插入mysql数据特别慢(批量提交可解决,我不会Ruby),可使用java代码造数据提交sql。
表空间文件概览
space-page-type-regions
可能是innodb_space最高层次概述之一,它为每一个给定的页类型的连续块(contiguous block)打印一行信息。
$ 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内部实现细节,你看到的是InnoDB的薄记结构(FSR_HDR,IBUF_BITMAP,和INODE页),实际的表数据(INDEX pages),和可用空间(FREE(ALLOCATED))
每个索引(实际上是每个文件段(“file segment”)或者每个索引的文件段)以页为单位的空间消耗列表,也相当有意思:
$ 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%
每个索引都有一个内部文件段(“internal file segment”)用于非叶子页和一个叶子文件段(”leaf file segment“)用于叶子页。页分配给文件段,但是不会被立即使用(比如类型为:FREE(ALLOCATED)的页),所以“file_factor”展示的是已用空间和未用空间的比值。(注意:它和索引页的填充程度无关,这是另外一回事儿)
页结构信息
page-dump
模式转储它所知道关于页的所有内容。目前,它严重依赖”Ruby pretty-printer“模块”pp“打印页结构–在将来,这是一件要麻烦事儿。innodb_ruby库最初使用最小的 ”Innodb::Page类“解析页结构,然后使用通用头部(“common header”)中的类型字段选择性的将不同的页类型交给特定的类(例如INDEX类型交给“Innodb::Page::Index”)做进一步解析。
开始看到的一个好的页面是第一页索引页,它是上面创建的测试表索引树的根节点,位于第4页(“即page 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 header” 和 ”footer“) 是所有页类型都有的结构且主要包含了关于页本身的信息。
该内容后面跟着的额外信息,它取决于页类型;对于索引页来说是如下内容:
- “page header”(页头部),关于索引页的信息
- “fseg header”(文件段头部),被索引页使用的文件段(区组)的空间管理相关的信息
- 页不同部分大小(以字节为单位)的摘要信息:可用空间(”free space“),数据空间(”data space“),记录大小(”record size“),等等
- “the system records”(系统记录),下确界和上确界
- “page directory”(页目录)的内容,它被用于提高数据(”record“)的搜索效率
- ”user records“(数据),用户所存储的真实数据(除非一条记录的描述者(“describer”)被加载,否则字段不会被解析)
索引的空间消耗
通过space-index-pages-summary
模式可以查看一些索引页数据相关的空间消耗
$ 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
通过这个命令可以让你很轻松的看到一些数据量和可用空间,以及这张表的记录计数(”records count“)。
如果你有一个正在运行的gnupolt并且Ruby gnupolt gem也安装好了,制作一个非常有用的空间消耗散点图也同样简单。
$ innodb_space -f test/t.ibd space-index-pages-free-plot
Wrote t_free.png
通过space-index-pages-free-plot
制作的散点图如下:
可用空间散点图–Y坐标表示每页可用空间的数量,而X坐标表示页编号同样也表示文件的偏移量。点击图片查看全尺寸版本。
如果是windows系统,那么文中所用的gnupolt要使用
gem install gnuplot
,还需下载exe文件,并设置环境变量。下载页和官方地址。安装好之后设置windows系统环境变量即可运行上述画图命令。此外,命令在哪里执行,图片就生成在那个目录下面。比如D:\soft\ruby下执行的命令,那么图片就在这个目录下。
理解行数据
为了让innodb_ruby项目在查看表结构时更有用,需要提供某种方式来更好地理解表数据(”table schema“)。它通过描述者类(“describer class”)完成查看,并且可以被动态的加载。这个是innodb_ruby库中的一个内容,目前还没有很好的文档记录(或尚未设计完好)。上面那个表(i INT UNSIGNED NOT NULL, PRIMARY KEY (i) and no other columns or indexes
)的一个简单描述类(”describer class“)表示如下:
class SimpleTDescriber < Innodb::RecordDescriber
type :clustered
key "i", :INT, :UNSIGNED, :NOT_NULL
end
如果这个文件被保存为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>
最后的”mode“可以使用
innodb_space --help
查看有哪些"mode"选项。上述命令中我所使用的模式是page-dump
即为
innodb_space -f test/t.ibd -r /path/to/simple_t_describer.rb -d SimpleTDescriber -p 4 page-dump
这里-p 4
是我推断MySQL8的根索引是page4
加载描述者(“describer”)主要做两件事:
- 以
page-dump
模式启用记录解析和转储。这将导致:主键索引(”:key“)和其他行索引(”*:row keys *“)被填充到转储记录中,同样也可获得事务ID和回滚指针(它们被存储在索引和非索引字段之间,因此至少要知道如何解析索引字段否则根本不能拿到这些数据)。 - 允许所有的索引递归函数,包括
indes-recurse
模式。InnoDB的内部B+tree“node pointer records”(”节点指针记录“)和 B+tree page连在一起,为了解析它们需要解析数据(”records“)的能力。
下面提供了一些完整记录的page转储的样本:
test_t_page_3_page_dump.txt (根页索引)和test_t_tpage_4_dump(一个叶子页索引)
递归一个索引
一旦拿到了数据的描述者(”describer),就可以使用index-recurse模式递归索引:
$ 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) -> ()
它将以升序顺序实实在在的遍历B+tree(基本上是全表扫描),然后打印出它所遇到的关于每个节点(页)的信息并转储用户记录(“user records”)到页子页上。以下是一个一个非常大的样本大约10k行数据:
test_t_page_3_index_recurse.txt。
未来将会有更多的内容
我希望这是个很有用的介绍。在将来将会有有更多的内容。非常欢迎提出修正,评论和建议。
更新 Davi指出几个错别字和错误,已经得到修正。确保您使用的是上述示例中的最新代码。
文章评论