热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

mysqlbtree每个节点怎么存储_开门见山的问MySQL:InnoDB一棵B+树可以存放多少行数据?你如何作答?...

前言开门见山,面对这样一个问题,你将如何作答?1千万,2千万,或者上亿条数据?具体的答案不重要&

前言

开门见山,面对这样一个问题,你将如何作答?

1千万,2千万,或者上亿条数据?具体的答案不重要,当然肯定也不会是一个固定的数目,今天我们就一起来探讨探讨这个问题。

InnoDB是一种兼顾了高可靠性和高性能的通用存储引擎,它拥有诸多功能和特性,体系结构和工作原理也比较复杂。真要讲明白说透彻,不是一两篇博文能够实现的,也不是今天的重点。

所以,本文不涉及太多的原理性知识,咱们就针对开头提出的问题,通过熟悉一些基本的概念和利用工具来验证,对这个问题做到心中有数。

文件结构

我们知道,InnoDB引擎是支持事务的,所以表里的数据肯定都是存储在磁盘上的。如果在test数据库下创建两个表:t1和t2,那么在相应的数据目录下就会发现两个文件。

[root@localhost test]# ls

db.opt t1.frm t1.ibd t2.frm t2.ibd

[root@localhost test]# pwd

/var/lib/mysql/test

其中,frm文件是表结构信息,ibd文件是表中的数据。

表结构信息包含MySQL表的元数据(例如表定义)的文件,比如表名、表有多少列、列的数据类型啥的,不重要,我们先不管;

ibd文件存储的是表中的数据,比如数据行和索引。这个文件比较重要,它是今天我们的重点研究对象。

我们说,MySQL表里的数据都是存放在磁盘上的。那么在磁盘上,最小单元是扇区,每个扇区可以存放512个字节的数据;操作系统中最小单元是块(block),最小单位是4kb。

在Windows系统中,我们可以通过fsutil fsinfo ntfsinfo c:来查看。

C:\Windows\system32>fsutil fsinfo ntfsinfo c:

NTFS 卷序列号: 0x78f40b2cf40aec66

NTFS 版本: 3.1

LFS 版本: 2.0

扇区数量: 0x000000001bcb6fff

簇总数: 0x0000000003796dff

可用簇: 0x0000000000a63a03

保留总数: 0x00000000000017c3

每个扇区字节数: 512

每个物理扇区字节数: 4096

每个簇字节数: 4096

每个 FileRecord 段字节数: 1024

每个 FileRecord 段簇数: 0

在Linux系统上,可以通过以下两个命令查看,这取决于文件系统的格式。

xfs_growfs /dev/mapper/centos-root | grep bsize

tune2fs -l /dev/mapper/centos-root | grep Block

我们拉回来接着说MySQL,InnoDB存储引擎它也是有最小存储单位的,叫做页(Page),默认大小是16kb。

我们新创建一个表 t3,里面任何数据都没有,我们来看它的ibd文件。

[root@localhost test]# ll

总用量 18579600

-rw-r-----. 1 mysql mysql 67 11月 30 20:59 db.opt

-rw-r-----. 1 mysql mysql 12756 12月 7 21:10 t1.frm

-rw-r-----. 1 mysql mysql 13077839872 12月 7 21:37 t1.ibd

-rw-r-----. 1 mysql mysql 8608 12月 7 21:43 t2.frm

-rw-r-----. 1 mysql mysql 5947523072 12月 7 21:52 t2.ibd

-rw-r-----. 1 mysql mysql 12756 12月 8 21:02 t3.frm

-rw-r-----. 1 mysql mysql 98304 12月 8 21:02 t3.ibd

不仅是t3,我们看到,任何表的ibd文件大小,它永远是16k的整数倍。理解这个事非常重要,MySQL从磁盘加载数据是按照页来读取的,即便你查询一条数据,它也会读取一页16k的数据出来。

聚簇索引

数据库表中的数据都是存储在页里的,那么这一个页可以存放多少条记录呢?

这取决于一行记录的大小是多少,假如一行数据大小是1k,那么理论上一页就可以放16条数据。

当然,查询数据的时候,MySQL也不能把所有的页都遍历一遍,所以就有了索引,InnoDB存储引擎用B+树的方式来构建索引。

聚簇索引就是按照每张表的主键构造一颗B+树,叶子节点存放的是整行记录数据,在非叶子节点上存放的是键值以及指向数据页的指针,同时每个数据页之间都通过一个双向链表来进行链接。

81071f690434

如上图所示,就是一颗聚簇索引树的大致结构。它先将数据记录按照主键排序,放在不同的页中,下面一行是数据页。上面的非叶子节点,存放主键值和一个指向页的指针。

当我们通过主键来查询的时候,比如id=6的条件,就是通过这颗B+树来查找数据的过程。它先找到根页面(page offset=3),然后通过二分查找,定位到id=6的数据在指针为5的页上。然后进一步的去page offset=5的页面上加载数据。

在这里,我们需要理解两件事:

上图中B+树的根节点(page offset=3),是固定不会变化的。只要表创建了聚簇索引,它的根节点页号就被记录到某个地方了。还有一点,B+树索引本身并不能直接找到具体的一条记录,只能知道该记录在哪个页上,数据库会把页载入到内存,再通过二分查找定位到具体的记录。

现在我们知道了InnoDB存储引擎最小存储单元是页,在B+树索引结构里,页可以放一行一行的数据(叶子节点),也可以放主键+指针(非叶子节点)。

上面已经说过,假如一行数据大小是1k,那么理论上一页就可以放16条数据。那一页可以放多少主键+指针呢?

假如我们的主键id为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节。这样算下来就是 16384 / 14 = 1170,就是说一个页上可以存放1170个指针。

一个指针指向一个存放记录的页,一个页里可以放16条数据,那么一颗高度为2的B+树就可以存放 1170 * 16=18720 条数据。同理,高度为3的B+树,就可以存放 1170 * 1170 * 16 = 21902400 条记录。

理论上就是这样,在InnoDB存储引擎中,B+树的高度一般为2-4层,就可以满足千万级数据的存储。查找数据的时候,一次页的查找代表一次IO,那我们通过主键索引查询的时候,其实最多只需要2-4次IO就可以了。

那么,实际上到底是不是这样呢?我们接着往下看。

页的类型

在开始验证之前,我们不仅需要了解页,还需要知道,在InnoDB引擎中,页并不是只有一种。常见的页类型有:

数据页,B-tree Node;

undo页,undo Log Page;

系统页,System Page;

事务数据页,Transaction system Page;

插入缓冲位图页,Insert Buffer Bitmap;

插入缓冲空闲列表页,Insert Buffer Free List;

未压缩的二进制大对象页,Uncompressed BLOB Page;

在这里我们重点来看 B-tree Node,我们的索引和数据就放在这种页上。既然有不同的页类型,我们怎么知道当前的页属于什么页呢?

那么我们就需要大概了解下数据页的结构,数据页由七个部分组成,每个部分都有不同的含义。

File Header,文件头,固定38字节;

Page Header,页头,固定56字节;

Infimum + supremum,固定26字节;

User Records,用户记录,即行记录,大小不固定;

Free Space,空闲空间,大小不固定;

Page Directort,页目录,大小不固定;

File Trailer,文件结尾信息,固定8字节。

其中,File Header用来记录页的一些头信息,共占用38个字节。在这个头信息里,我们可以获取到该页在表空间里的偏移值和这个数据页的类型。

接下来是Page Header,它记录的是数据页的状态信息,共占用56个字节。在这一部分,我们可以获取到两个重要的信息:该页中记录的数量和当前页在索引树的层级,其中0x00代表叶子节点,比如聚簇索引中的叶子节点放的就是整行数据,它总是在第0层。

验证

前面我们已经说过,ibd文件就是表数据文件。这个文件会随着数据库表里数据的增长而增长,不过它始终会是16k的整数倍。里面就是一个个的页,那我们就可以一个一个页的来解析,通过文件头可以判断它是什么页,找到 B-tree Node,就可以看到里面的 Page Level,它的值+1,就代表了当前B+树的高度。

我们现在就来重新创建一个表,为了使这个表中的数据一行大小为1k,我们设置几个char(255)的字段即可。

CREATE TABLE `t5` (

`id` bigint(8) NOT NULL,

`c1` char(245) NOT NULL DEFAULT '1',

`c2` char(255) NOT NULL DEFAULT '1',

`c3` char(255) NOT NULL DEFAULT '1',

`c4` char(255) NOT NULL DEFAULT '1',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

然后笔者写了一个存储过程,用来批量插入数据用的,为了加快批量插入的速度,笔者还修改了innodb_flush_log_at_trx_commit=0,切记生产环境可不要这样玩。

BEGIN

DECLARE i int DEFAULT 0;

select ifnull(max(id),0) into i from t5;

set i = i+1;

WHILE i <&#61; 100000 DO

insert into t5(id)value(i);

set i &#61; i&#43;1;

END WHILE;

END

innodbPageInfo.jar是笔者用Java代码写的一个工具类&#xff0c;用来输出ibd文件中&#xff0c;页的信息。

-path 后面是文件的路径&#xff0c;-v 是否显示页的详情信息&#xff0c;0是 1否。

上面我们创建了t5这张表&#xff0c;一条数据还没有的情况下&#xff0c;我们看一下这个ibd文件的信息。

[root&#64;localhost innodbInfo]# java -jar innodbPageInfo.jar -path /var/lib/mysql/test/t5.ibd -v 0

page offset 00000000,page type

page offset 00000001,page type

page offset 00000002,page type

page offset 00000003,page type ,page level <0000>

page offset 00000000,page type

page offset 00000000,page type

数据页总记录数:0

Total number of page: 6

Insert Buffer Bitmap: 1

File Segment inode: 1

B-tree Node: 1

File Space Header: 1

Freshly Allocated Page: 2

[root&#64;localhost innodbInfo]#

t5表现在没有任何数据&#xff0c;它的ibd文件大小是98304&#xff0c;也就是说一共有6个页。其中第四个页(page offset 3)是数据页&#xff0c;page level等于0&#xff0c;代表该页为叶子节点。因为目前还没有数据&#xff0c;可以认为B&#43;树的索引只有1层。

我们接着插入10条数据&#xff0c;这个page level还是为0&#xff0c;B&#43;树的高度还是1&#xff0c;这是因为一个页大约能存放16条大小为1k的数据。

page offset 00000003,page type ,page level <0000>

数据页总记录数:10

Total number of page: 6

当我们插入15条数据的时候&#xff0c;一个页就放不下了&#xff0c;原本为新分配的页(Freshly Allocated Page)就会变成数据页&#xff0c;原来的根页面(page offset&#61;3)就会升级成存储目录项的页。offset 04 和 05就变成了叶子节点的数据页&#xff0c;所以现在整个B&#43;树的高度为2。

page offset 00000003,page type ,page level <0001>

page offset 00000004,page type ,page level <0000>

page offset 00000005,page type ,page level <0000>

数据页总记录数:15

Total number of page: 6

继续插入10000条数据&#xff0c;我们再来看一下B&#43;树高的情况。当然现在信息比较多了&#xff0c;我们把输出结果写到文件里。

java -jar innodbPageInfo.jar -path /var/lib/mysql/test/t5.ibd -v 0 > t5.txt

截取部分结果如下&#xff1a;

[root&#64;localhost innodbInfo]# vim t5.txt

page offset 00000003,page type ,page level <0001>

page offset 00000004,page type ,page level <0000>

page offset 00000005,page type ,page level <0000>

page offset 00000000,page type

数据页总记录数:10000

Total number of page: 1216

B-tree Node: 716

可以看到&#xff0c;1万条1k大小的记录&#xff0c;一共用了716个数据页&#xff0c;根页面显示的树高还是2层。

前面我们计算过&#xff0c;2层的B&#43;树理论上可以存放18000条左右&#xff0c;笔者测试大约13000条数据左右&#xff0c;B&#43;树就会成为3层了。

page offset 00000003,page type ,page level <0002>

数据页总记录数:13000

Total number of page: 1472

B-tree Node: 933

原因也不难理解&#xff0c;因为每个页不可能只放数据本身。

首先每个页都有一些固定的格式&#xff0c;比如文件头部、页面头部、文件尾部这些&#xff0c;我们的数据放在用户记录这部分里的&#xff1b;

其次&#xff0c;用户记录也不只放数据行&#xff0c;每个数据行还有一些其他标记&#xff0c;比如是否删除、最小记录、记录数、在堆中的位置信息、记录的类型、下一条记录的相对位置等等&#xff1b;

另外&#xff0c;MySQL参考手册中也有说到&#xff0c;InnoDB会保留页的1/16空闲&#xff0c;以便将来插入或者更新索引使用&#xff0c;如果主键id不是顺序插入的&#xff0c;那可能还不是1/16&#xff0c;会占用更多的空闲空间。

总之&#xff0c;我们理解一个页不会全放数据就行了。所以&#xff0c;实测跟理论上不一致也是完全正常的&#xff0c;因为上面的理论没有排除这些项。

接着来&#xff0c;我们再插入1000万条数据&#xff0c;现在ibd文件已经达到11GB。

page offset 00000003,page type ,page level <0002>

数据页总记录数:10000000

Total number of page: 725760

B-tree Node: 715059

我们看到&#xff0c;1千万条数据&#xff0c;数据页已经有71万个&#xff0c;B&#43;树的高度还是3层&#xff0c;这也就是说几万条数据和一千万条数据的查询效率基本上是一样的。 比如我们现在根据主键ID查询一条数据&#xff0c;select * from t5 where id &#61; 6548215; &#xff0c;查询时间显示用了0.010秒。

什么时候会到4层呢&#xff1f;大概在1300万左右&#xff0c;B&#43;树就会增加树高到4层。

什么时候会到5层呢&#xff1f;笔者没测试出来&#xff0c;因为插入到5000万条数据的时候&#xff0c;ibd数据文件大小已经55G了&#xff0c;虚拟机已经空间不足了。。

page offset 00000003,page type ,page level <0003>

数据页总记录数:50000000

B-tree Node: 3575286

即便是5000万条数据&#xff0c;我们通过主键ID查询&#xff0c;查询时间也是毫秒级的。

理论上要达到十亿 - 百亿行数据&#xff0c;树高才能到5层。如果有小伙伴用这种方法&#xff0c;测试出来5层高的数据&#xff0c;欢迎在评论区留言&#xff0c;让我看看。

另外&#xff0c;朋友们有没有意识到一个问题&#xff1f;其实影响B&#43;树树高的因素&#xff0c;不仅是数据行&#xff0c;还有主键ID的长度。我们上面的测试中&#xff0c;ID的类型是bigint(8)&#xff0c;在其他字段长度均不变的情况下&#xff0c;我们把ID的类型改为int(4)&#xff0c;相同的树高就会容纳更多的数据&#xff0c;因为它单个页能承载的指针数变多了。

CREATE TABLE &#96;t6&#96; (

&#96;id&#96; int(4) NOT NULL,

&#96;c1&#96; char(245) NOT NULL DEFAULT &#39;1&#39;,

&#96;c2&#96; char(255) NOT NULL DEFAULT &#39;1&#39;,

&#96;c3&#96; char(255) NOT NULL DEFAULT &#39;1&#39;,

&#96;c4&#96; char(255) NOT NULL DEFAULT &#39;1&#39;,

PRIMARY KEY (&#96;id&#96;)

) ENGINE&#61;InnoDB DEFAULT CHARSET&#61;utf8mb4;

针对t6这张表&#xff0c;我们插入16000条数据&#xff0c;然后输出一下页面信息。

page offset 00000003,page type ,page level <0001>

数据页总记录数:16000

B-tree Node: 1145

我们来看&#xff0c;如果按照主键ID类型bigint(8)来测试&#xff0c;13000条数据的时候&#xff0c;树高就已经是3了&#xff0c;现在改为int(4)&#xff0c;16000条数据&#xff0c;树高依然还是2层。尽管数据页(B-tree Node)数量还是那么多&#xff0c;变化并不大&#xff0c;但是它不影响树高。

ok&#xff0c;看到这里&#xff0c;相信朋友们对开头提出的问题已经有自己的答案了&#xff0c;如果你也跟着试一遍&#xff0c;理解可能会更加深入。

看到这&#xff0c;还有道经典的面试题&#xff1a;为什么MySQL的索引要使用B&#43;树而不是其它树形结构?比如B树&#xff1f;

简单来说&#xff0c;其中有一个原因就是B&#43;树的高度比较稳定&#xff0c;因为它的非叶子节点不会保存数据&#xff0c;只保存键值和指针的情况下&#xff0c;一个页能承载大量的数据。你想啊&#xff0c;B树它的非叶子节点也会保存数据的&#xff0c;同样的一行数据大小是1kb&#xff0c;那么它一页最多也只能保存16个指针&#xff0c;在大量数据的情况下&#xff0c;树高就会速度膨胀&#xff0c;导致IO次数就会很多&#xff0c;查询就会变得很慢。

源码地址

本文的innodbPageInfo.jar代码是笔者参考 MySQL技术内幕(InnoDB存储引擎)一书中的工具包&#xff0c;书里作者是用Python写的&#xff0c;所以笔者在这里用Java重新实现了一遍。

朋友们可以拿这个工具看一看&#xff0c;自己认为较大的表&#xff0c;它的B&#43;树索引到底有几层&#xff1f;

最后

大家看完有什么不懂的可以在下方留言讨论.

谢谢你的观看。

觉得文章对你有帮助的话记得关注我点个赞支持一下&#xff01;



推荐阅读
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文介绍了将mysql从5.6.15升级到5.7.15的详细步骤,包括关闭访问、备份旧库、备份权限、配置文件备份、关闭旧数据库、安装二进制、替换配置文件以及启动新数据库等操作。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 面试经验分享:华为面试四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试
    最近有朋友去华为面试,面试经历包括四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试。80%的人都在第一轮电话面试中失败,因为缺乏基础知识。面试问题涉及 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 进入配置文件目录:[rootlinuxidcresin-4.0.]#cdusrlocalresinconf查看都有哪些配置文件:[rootlinuxid ... [详细]
  • 三、寻找恶意IP并用iptables禁止掉找出恶意连接你的服务器80端口的IP,直接用iptables来drop掉它;这里建议写脚本来运行, ... [详细]
  • Java程序员必会的40个Linux命令!
    你知道的越多,不知道的就越多,业余的像一棵小草!你来,我们一起精进!你不来,我和你的竞争对手一起 ... [详细]
  • oracle安装时找不到启动,Oracle没有开机自启是怎么回事?这一步骤很重要
    重启Oracle数据库重启Oracle数据库包括启动Oracle数据库服务进程和启动Oracle数据库两步,大家继续往下看。按照《【Oracle】什么?作为DBA&# ... [详细]
  • 目录Atlas介绍Atlas部署Atlas基本管理Atlas结合MHA故障恢复读写分离建议Atlas介绍Atlas是由Qihoo360Web平台部基础架构团队开发维护的一个基于My ... [详细]
  • MySQL 数据库基础学习 一、SQL的作用及分类 二、数据类型 三、存储引擎  (建库建表、数据插入等))
    MySQL 数据库基础学习 一、SQL的作用及分类 二、数据类型 三、存储引擎 (建库建表、数据插入等)) ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
author-avatar
雨的到来2009
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有