「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
当咱们对一张表发起查询的时候,是否是这张表的数据越少,查询的就越快?
答案是不必定,这和mysql B+数索引结构有必定的关系。前端
从Innodb存储引擎的逻辑存储结构来看,全部数据都被逻辑的放在一个表空间(tablespace)中,默认状况下,全部的数据都放在一个表空间中,固然也能够设置每张表单独占用一个表空间,经过innodb_file_per_table
来开启。mysql
mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.00 sec)
复制代码
表空间又是由各个段组成的,常见的有数据段,索引段,回滚段等。由于innodb的索引类型是b+树,那么数据段就是叶子结点,索引段为b+的非叶子结点。sql
段空间又是由区组成的,在任何状况下,每一个区的大小都为1M,innodb引擎通常默认页的大小为16k,通常一个区中有64个连续的页(64*16k=1M)。后端
经过段咱们知道,还存在一个最小的存储单元页。它是innodb管理的最小的单位,默认是16K,固然也能够经过innodb_page_size来设置为4K、8K...,咱们的数据都是存在页中的markdown
mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
复制代码
因此innodb的数据结构应该大体以下:数据结构
b+树索引的特色就是数据存在叶子结点上,而且叶子结点之间是经过双向链表方式组织起来的。
假设存在这样一张表:post
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL DEFAULT '',
`age` int(10) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
复制代码
对于主键索引id,假设它的b+树结构可能以下:spa
咱们来看看 select * from user where id=30
是如何定位到的。指针
id=30
,判断在第一层的25-50
之间总结:能够发现一共发起两次io,最后加载到内存检索的时间忽略不计。总耗时就是两次io的时间。code
经过表结构咱们知道,除了id,咱们还有name这个非汇集索引。因此对于name索引,它的结构可能以下:
咱们来看看 select * from user where name=jack
是如何定位到的。
name=jack
,判断在第一层的mary-tom
之间select *
,因此经过id再去主键索引查找总结:name查询两次io,而后经过id再次回表查询两次io,加载到内存的时间忽略不计,总耗时是4次io。
以上面的user表为例,咱们先看看一行数据大概须要多大的空间:经过show table status like 'user'\G
mysql> show table status like 'user'\G
*************************** 1. row ***************************
Name: user
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 10143
Avg_row_length: 45
Data_length: 458752
Max_data_length: 0
Index_length: 311296
Data_free: 0
Auto_increment: 10005
Create_time: 2021-07-11 17:22:56
Update_time: 2021-07-11 17:31:52
Check_time: NULL
Collation: utf8mb4_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
复制代码
咱们能够看到Avg_row_length=45
,那么一行数据大概占45字节,由于一页的大小是16k,那么一页能够存储的数据是16k/45b = 364行数据,这是叶子结点的单page存储量。
以主键索引id为例,int占用4个字节,指针大小在InnoDB中占6字节,这样一共10字节,从root结点出来多少个指针,就能够知道root的下一层有多少个页。由于root结点只有一页,因此此时就是16k/10b = 1638个指针。
innodb引擎中,每一个页都包含一个PAGE_LEVEL的信息,用于表示当前页所在索引中的高度。默认叶子节点的高度为0,那么root页的PAGE_LEVEL + 1就是这棵索引的高度。
那么咱们只要找到root页的PAGE_LEVEL就好了。
经过如下sql能够定位user表的索引的page_no:
mysql> SELECT b.name, a.name, index_id, type, a.space, a.PAGE_NO FROM information_schema.INNODB_SYS_INDEXES a, information_schema.INNODB_SYS_TABLES b WHERE a.table_id = b.table_id AND a.space <> 0 and b.name='test/user';
+-----------+---------+----------+------+-------+---------+
| name | name | index_id | type | space | PAGE_NO |
+-----------+---------+----------+------+-------+---------+
| test/user | PRIMARY | 105 | 3 | 67 | 3 |
| test/user | name | 106 | 0 | 67 | 4 |
+-----------+---------+----------+------+-------+---------+
2 rows in set (0.00 sec)
复制代码
能够看到主键索引的page_no=3,由于PAGE_LEVEL在每一个页的偏移量64位置开始,占用两个字节。因此算出它在文件中的偏移量:16384*3 + 64 = 49152 + 64 =49216,再取前两个字节就是root的PAGE_LEVEL了。
经过如下命令找到ibd文件目录
show global variables like "%datadir%" ;
+---------------+-----------------------+
| Variable_name | Value |
+---------------+-----------------------+
| datadir | /usr/local/var/mysql/ |
+---------------+-----------------------+
1 row in set (0.01 sec)
复制代码
user.ibd在 /usr/local/var/mysql/test/
下。
经过hexdump
来分析data文件。
hexdump -s 49216 -n 10 user.ibd
000c040 00 01 00 00 00 00 00 00 00 69
000c04a
复制代码
000c040 00 01
00 00 00 00 00 00 00 69
00 01就是说明PAGE_LEVEL=1,那么树的高度就是1+1=2。
100w的数据表比1000w的数据表查询更快吗?经过查询的过程咱们知道,查询耗时和树的高度有很大关系。若是100w的数据若是和1000w的数据的树的高度是同样的
,那其实它们的耗时没什么区别。