如何获取InnoDB树的高度

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 前言 作为DBA了解InnoDB的页组织方式是最基础的,在实际工作中,免不了会评估SQL会消耗多少IO,怎么评估呢?作为InnoDB表和树的高度或者深度有关系。 查看树的高度? 之前研究了半天:https://www.

前言

作为DBA了解InnoDB的页组织方式是最基础的,在实际工作中,免不了会评估SQL会消耗多少IO,怎么评估呢?
作为InnoDB表和树的高度或者深度有关系。

查看树的高度?

之前研究了半天:
https://www.percona.com/blog/2009/04/28/the_depth_of_a_b_tree/
http://code.openark.org/blog/mysql/the-depth-of-an-index-primer
根据

Scholmi notes that there are two main features determining the depth of a B-tree (or B+-tree):

The number of rows in the database. We’ll call that N.
The size of the indexed key. Let’s call B the number of key that fit in a B-tree node. (Sometimes B is used to refer to the node size itself, rather than the number of keys it holds, but I hope my choice will make sense directly.)
Given these quantities, the depth of a B-tree is logB N, give or take a little. That’s just (log N)/log B. Now we can rephrase Scholmi’s point as noting that small keys means a bigger B, which reduces (log N)/log B. If we cut the key size in half, then the depth of the B-tree goes from (log N)/log B to (log N)/log 2B (twice as many keys fit in the tree nodes), and that’s just (log N)/(1+log B).

Let’s put some numbers in there. Say you have a billion rows, and you can currently fit 64 keys in a node. Then the depth of the tree is (log 109)/ log 64 ≈ 30/6 = 5. Now you rebuild the tree with keys half the size and you get log 109 / log 128 ≈ 30/7 = 4.3. Assuming the top 3 levels of the tree are in memory, then you go from 2 disk seeks on average to 1.3 disk seeks on average, for a 35% speedup.

里面算的不对,人肉算也没达到这个高度。也可能是我没有理解作者的意思,没有用对公式。那么根据结合前面的Innodb页结构,如何正确的获取数的高度呢?继续拿这个表举例子:

mysql> show create table sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `gmt_create` datetime NOT NULL,
  `gmt_modified` datetime NOT NULL,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` varchar(500) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  `is_used` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_is_used` (`is_used`),
  KEY `idx_gmt_create` (`gmt_create`)
) ENGINE=InnoDB AUTO_INCREMENT=69313841 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

方法一

[root@localhost mysql]# innodb_space -f test/sbtest1.ibd space-index-pages-summary | head -n 10
page        index   level   data    free    records
3           74      2       5166    10904   369
4           75      2       408     15834   24
5           76      2       486     15756   27
6           77      2       486     15756   27
7           74      0       15028   1192    68
8           74      0       15028   1192    68
9           74      1       14700   1030    1050
10          74      0       15028   1192    68
11          74      0       15028   1192    68

page_level是2,所以这个树高度是page_level+1=3;

方法二

mysql> show global variables like "%innodb_page%";
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_page_cleaners | 1     |
| innodb_page_size     | 16384 |
+----------------------+-------+
2 rows in set (0.00 sec)

mysql> show table status like 'sbtest1'\G
*************************** 1. row ***************************
           Name: sbtest1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 25926320
 Avg_row_length: 279
    Data_length: 7254032384
Max_data_length: 0
   Index_length: 1293697024
      Data_free: 3145728
 Auto_increment: 69313841
    Create_time: 2018-01-19 14:53:11
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)
mysql> desc sbtest1;
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| gmt_create   | datetime     | NO   | MUL | NULL    |                |
| gmt_modified | datetime     | NO   |     | NULL    |                |
| k            | int(11)      | NO   | MUL | 0       |                |
| c            | varchar(500) | NO   |     |         |                |
| pad          | char(60)     | NO   |     |         |                |
| is_used      | int(11)      | YES  | MUL | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

通常一棵B+树可以存放多少行数据?

这里我们先假设B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数。

上文我们已经说明单个叶子节点(页)中的记录数=16K/279=58。(我们从上面可以看到每行记录的数据平均大小为279个字节)。

那么现在我们需要计算出非叶子节点能存放多少指针,其实这也很好算,表中的主键ID为int类型,长度为4字节,而指针大小在InnoDB源码中设置为6字节,这样一共10字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/10=1638。那么可以算出一棵高度为2的B+树,能存放1638*58=95004条这样的数据记录。

根据同样的原理我们可以算出一个高度为3的B+树可以存放:1638*1638*58=155616552条这样的记录。
高度为4的B+树可以存放:
1638*1638*1638*58=254899912176条这样的记录。
而在实际应用中,大部分是以bigint作为主键的,主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170。
根据同样的原理我们可以算出一个高度为2,3,4的B+树能够存放的记录数。
mysql> select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
| 26313272 |
+----------+
1 row in set (4.23 sec)

我们的表一共是3层。

方法三

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/sbtest1';
+--------------+----------------+----------+------+-------+---------+
| name         | name           | index_id | type | space | PAGE_NO |
+--------------+----------------+----------+------+-------+---------+
| test/sbtest1 | PRIMARY        |       74 |    3 |    45 |       3 |
| test/sbtest1 | k_1            |       75 |    0 |    45 |       4 |
| test/sbtest1 | idx_is_used    |       76 |    0 |    45 |       5 |
| test/sbtest1 | idx_gmt_create |       77 |    0 |    45 |       6 |
+--------------+----------------+----------+------+-------+---------+
4 rows in set (0.00 sec)

primary key的高度是3,其他索引的可以看上表。

方法四

因为主键索引B+树的根页在整个表空间文件中的第3个页开始,所以可以算出它在文件中的偏移量:16384*3=49152(16384为页大小)。

另外根据《InnoDB存储引擎》中描述在根页的64偏移量位置前2个字节,保存了page level的值,因此我们想要的page level的值在整个文件中的偏移量为:16384*3+64=49152+64=49216,前2个字节中。

接下来我们用hexdump工具,查看表空间文件指定偏移量上的数据:

[root@localhost test]# hexdump -s 49216 -n 10 sbtest1.ibd
000c040 0200 0000 0000 0000 4a00
000c04a

page_level是2,B+树高度为page level+1=3

如果通过二级索引查找记录最多需要花费多少次IO呢?

从上面的图中可以看出需要花费:
从二级索引找到主键+主键找到记录,比如二级索引有3层,聚簇索引有3层,那么最多花费的IO次数是:3+3=6

参考

姜承尧 《MySQL技术内幕:InnoDB存储引擎》
姜承尧 http://www.innomysql.com/查看-innodb表中每个的索引高度/
https://blog.jcole.us/2013/01/10/btree-index-structures-in-innodb/?spm=a2c4e.11153940.0.0.77d994fcZH1hIv

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6月前
|
存储 SQL 关系型数据库
InnoDB主键索引树和二级索引树
InnoDB主键索引树和二级索引树
83 0
InnoDB主键索引树和二级索引树
|
14天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
123 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
14天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
14天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的InnoDB存储引擎
InnoDB是MySQL的默认存储引擎,广泛应用于互联网公司。它支持事务、行级锁、外键和高效处理大量数据。InnoDB的主要特性包括解决不可重复读和幻读问题、高并发度、B+树索引等。其存储结构分为逻辑和物理两部分,内存结构类似Oracle的SGA和PGA,线程结构包括主线程、I/O线程和其他辅助线程。
【赵渝强老师】MySQL的InnoDB存储引擎
|
5月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
1月前
|
存储 缓存 关系型数据库
详细解析MySQL中的innodb和myisam
总之,InnoDB和MyISAM各有千秋,选择合适的存储引擎应基于对应用程序特性的深入理解,以及对性能、数据完整性和可扩展性的综合考量。随着技术发展,InnoDB因其全面的功能和日益优化的性能,逐渐成为更广泛场景下的首选。然而,在特定条件下,MyISAM依然保留其独特的价值。
128 0
|
3月前
|
监控 关系型数据库 MySQL
在Linux中,mysql的innodb如何定位锁问题?
在Linux中,mysql的innodb如何定位锁问题?
|
3月前
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
73 6
|
5月前
|
存储 SQL 关系型数据库
【MySQL技术内幕】6.3-InnoDB中的锁
【MySQL技术内幕】6.3-InnoDB中的锁
199 57