InnoDB学习(七)之索引结构
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。可以将数据库索引和书的目录进行类比,通过书的目录我们可以快速查找到章节位置,如果没有目录就只能一页页翻书查找了。
索引数据结构
可以用于提升查询效率的索引结构很多,常见的有B树索引、哈希索引和B+树索引。接下来我们会对这些索引一一进行介绍,并说明InnoDB为什么采用B+树作为索引。
磁盘IO
文件是存储在硬盘上面的。当下硬盘的读取速度十分有限,所以在进行查询定位某个数据的时候,应该尽可能地减少磁盘I/O次数。
磁盘预读
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。
局部性原理:CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页的大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
合理利用磁盘预读
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。
如果我们能合理使用磁盘预读的特性,使每次磁盘IO读到的页中的数据都是有用的,就可以大大提升数据的查询效率。
B树索引
B树可以看作是对二叉查找树的一种扩展,B树允许每个节点有M-1个子节点,B树有以下特点:
- 根节点至少有两个子节点;
- 每个节点包含M-1条数据,节点中的数据安装索引递增顺序排序;
- 节点中有最多有M个指针指向下一层节点,这些指针位于节点的多个数据之间,下一层节点的所有数据值大于指针左侧的数据,小于指针右侧的数据;
- 每个节点至少包含M/2条数据;
接下来我们用下表示例的用户数据来构建B树,如表所示,用户数据包含姓名、性别、年龄三个字段,我们把用户年龄作为数据库主键(假设年龄具有唯一性),那么构建出来的B树的结构如下图所示。
|||||||||||
|--|--|--|--|--|--|--|--|--|--|--|
|姓名|陈尔|张散|李思|王舞|赵流|孙期|周跋|吴酒|郑史|
|性别|男|男|女|女|男|男|男|女|男|
|年龄|5|10|20|28|35|56|25|80|90|
![B树索引]
相比较与常见的二叉树,B树的一个节点中存放了更多的数据,这样做可以有效的减少一次数据查找过程中的磁盘IO次数:
- 二叉树每个节点只存放一个数据,节点之间用指针关联,节点之间的空间是离散的,所以每个节点都对应一次磁盘IO,查找一次数据的IO次数为O($log_2$N);
- B树的节点可以存放M-1个数据,如果这M-1个数据刚好可以放到一个页中,那么B树查找一次数据的IO次数为O($log_M$N);
哈希索引
哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。哈希表是一种以键-值(Key-Value)存储数据的结构,用户可以在O(1)时间复杂度内按照Key查找到对应的Value。
哈希表通常是一个数组,数据在数组中的位置可以按照索引的值安装哈希算法进行计算,如果两个数据的索引值计算出来的位置相同,那么通常可以采用链地址法解决冲突(其它解决地址冲突的方法还有开放定制法,链地址法,公共溢出区法,再散列法等)。
如下表数据所示,我们依旧按照用户的年龄为用户数据建立索引(假设用户年龄不会相同),我们采用的哈希算法为 addr=age%10,我们可以建立长度为10的数组作为哈希表,按照哈希函数一一把用户放入哈希表,按照用户年龄查找用户时,可以直接计算出用户所在的位置,从而得到用户信息,最终得到的哈希表以及查询流程如下图所示。
姓名 | 陈尔 | 张散 | 李思 | 王舞 | 赵流 | 孙期 | 周跋 | 吴酒 | 郑史 |
性别 | 男 | 男 | 女 | 女 | 男 | 男 | 男 | 女 | 男 |
年龄 | 5 | 10 | 20 | 28 | 35 | 56 | 25 | 80 | 90 |
哈希索引有以下优点:
- 占用的额外空间小,为数据新建一个哈希索引需要的额外空间为O(N),和索引字段长度无关;
- 查询速度极快,哈希函数合理的情况下,程序可以在O(1)的磁盘IO次数内查找到数据;
哈希索引有以下缺点:
- 无法进行范围查询,哈希过程中已经丢失了索引的顺序性;
- 无法对数据进行排序查找,比如查找年龄最大的用户;
- 无法使用部分索引查找,比如前缀查询等;
- 哈希函数不合理的情况下,会导致哈希冲突问题,造成查询效率变低;
B+树索引
InnoDB使用的索引的数据结构是B+树,数据库表定义中的每一个索引对应一颗B+树,默认的聚簇索引也是一颗B+树,B+树有以下特征:
- 所有节点关键字是按递增次序排列,并遵循左小右大原则;
- 非叶节点的子节点数在1到M之间(下图中M为3),空树除外;
- 非叶节点的索引数目大于等于ceil(M/2)个且小于等于M个;
- 所有叶子节点均在同一层,叶子节点之间有从左到右的指针;
- 数据存储在叶子节点,非叶子节点只存储索引;
接下来我们用几条示例的用户数据来构建B+树,如表所示,用户数据包含姓名、性别、年龄三个字段,我们把用户年龄作为数据库主键(假设年龄具有唯一性),那么构建出来的B+树的结构如下图所示。
姓名 | 陈尔 | 张散 | 李思 | 王舞 | 赵流 | 孙期 | 周跋 | 吴酒 | 郑史 |
性别 | 男 | 男 | 女 | 女 | 男 | 男 | 男 | 女 | 男 |
年龄 | 5 | 10 | 20 | 28 | 35 | 56 | 25 | 80 | 90 |
B+树索引数据结构有以下列出的几种优势:
- 查询性能稳定,查询一条数据需要的IO次数往往是树的高度次;
- 范围查询效率高,安装索引范围查询时,可以先查找的第一个满足要求的数据,然后向后遍历,直到第一个不满足条件的数据为止,中间的数据都符合要求;
- 查询效率高,往往一次数据查询只需要2~3次磁盘IO;
- 叶子节点存储所有数据,不需要去B+树之外找数据;
InnoDB为什么采用B+树
在InnoDB引擎中,我们为数据库创建的索引都是以B+树的形式存在,为什么InnoDB不采用哈希索引或者B树索引呢?主要是基于以下原因:
- 数据库查询经常会出现非等值查询,哈希索引在这种情况下无法工作;
- 相比于B树,B+树索引非叶子节点不存放数据,从而磁盘一次IO可以读取更多的索引数据,有效减少磁盘IO次数;
- 数据库查询经常会出现范围查询,B+树底层的叶子节点之间按照顺序排列,可以更有效的实现范围查询;
自增主键
通过上文我们知道,B+树需要维护索引的有序性。
- 当用户向B+树插入数据,如果插入点对应的节点有空余位置,那么只需要挪动节点中的数据,并把需要插入的数据放入B+树即可;
- 当用户向B+树插入数据,如果插入点对应的节点没有空余位置,那么就需要生成一个新的节点,并把一部分数据挪过去;这种情况不仅会影响插入效率,由于分裂出来的节点只有部分数据,所以会导致空间的利用率降低;
- 当用户删除B+树中的数据时,如果节点或相邻节点的数据量很少,那么只需要直接删除数据,并按挪动节点中的其它数据即可;
- 当用户删除B+树中的数据时,如果节点和相邻节点的数据量很少,那么在删除之后,可能需要把节点和相邻节点合并,从而提高空间利用率;
基于B+树需要维护索引有序性的特点,我们对索引字段提出以下建议:
- 对于数据插入比较多的场景,主键索引字段最好是递增的。递增的主键每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。
- 主键索引的长度应当尽量小,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。
在InnoDB中,我们应当尽量使用自增主键,自增主键有插入效率高、占用空间小等优势。
数据空洞与重建索引
数据空洞
当你对InnoDB进行修改操作时,例如删除一些行,这些行只是被标记为“已删除”,而不是真的从索引中物理删除了,因而空间也没有真的被释放回收。InnoDB的Purge线程会异步的来清理这些没用的索引键和行,但是依然没有把这些释放出来的空间还给操作系统重新使用,因而会导致页面中存在很多空洞。如果表结构中包含动态长度字段,那么这些空洞甚至可能不能被InnoDB重新用来存新的行,因为空间空间长度不足。
数据空洞带来的问题:
- 删除表中的数据后,表占用的空间不会变小,造成空间浪费;
- 会降低数据查询的速度,因为空洞会占用页空间;
我们可以通过以下SQL来查看数据库中的空洞大小,执行语句如下所示,返回结果中的DATA_FREE表示表中空闲数据块的大小。
select data_length,data_free from information_schema.tables where table_schema='test' and table_name='test';
重建索引
当一张表的索引中的数据空洞过多时,会影响SQL语句的执行效率,此时我们就需要清理这些数据空洞。
清理数据空洞比较好的办法是重建索引,因为重建索引的过程中,会按照索引的大小排序后建立索引,建立出来的索引比较紧凑。
有什么办法可以重建索引呢?我们比较直观的想法肯定是先删除索引,再重建索引。然而不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。
alter table user_info drop primary key;
alter table user_info add primary key(id);
InnoDB中可以通过以下转换数据引擎的语句来重建表的所有索引。这是因为在转换数据引擎(即使没有真正转换)的过程中,会读取表中所有的数据,再重新写入,这个过程中,会释放空洞。需要注意的是,通过这种方法重建索引耗时比较长。
alter table test engine=innodb
本文最先发布至微信公众号,版权所有,禁止转载!
InnoDB学习(七)之索引结构的更多相关文章
- 使用innodb_ruby探查Innodb索引结构
使用innodb_ruby探查Innodb索引结构 innodb_ruby 是使用 Ruby 编写的 InnoDB 文件格式解析器.innodb_ruby 的目的是暴露一些其他隐藏的 InnoDB 原 ...
- SQL索引学习-索引结构
前一阵无意中和同事讨论过一个SQL相关的题(通过一个小问题来学习SQL关联查询),很惭愧一个非常简单的问题由于种种原因居然没有回答正确,数据库知识方面我算不上技术好,谈起SQL知识的学习我得益于200 ...
- ElasticSearch 学习记录之如任何设计可扩容的索引结构
扩容设计 扩容的单元 一个分片即一个 Lucene 索引 ,一个 Elasticsearch 索引即一系列分片的集合 一个分片即为 扩容的单元 . 一个最小的索引拥有一个分片. 一个只有一个分片的索引 ...
- 学习索引结构的一些案例——Jeff Dean在SystemML会议上发布的论文(下)
[摘要] 除了范围索引之外,点查找的Hash Map在DBMS中起着类似或更重要的作用. 从概念上讲,Hash Map使用Hash函数来确定性地将键映射到数组内的随机位置(参见图[9 ],只有4位开销 ...
- MySQL存储引擎MyISAM和InnoDB,索引结构优缺点
MySQL存储引擎MyISAM和InnoDB底层索引结构 深入理解MySQL索引底层数据结构与算法 (各种索引结构优缺点) Myisam和Innodb索引实现的不同(存储结构) 存储引擎作用于什么对象 ...
- MyISAM 和 InnoDB 索引结构及其实现原理
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询.更新数据库表中数据. 索引的实现通常使用B_TREE. B_TREE索引加速了数据访问,因为存储引擎不会再去扫描整张表得到需要的数据; ...
- 面试官:为什么Mysql中Innodb的索引结构采取B+树?
前言 如果面试官问的是,为什么Mysql中Innodb的索引结构采取B+树?这个问题时,给自己留一条后路,不要把B树喷的一文不值.因为网上有些答案是说,B树不适合做文件存储系统的索引结构.如果按照那种 ...
- Mysql高级操作学习笔记:索引结构、树的区别、索引优缺点、创建索引原则(我们对哪种数据创建索引)、索引分类、Sql性能分析、索引使用、索引失效、索引设计原则
Mysql高级操作 索引概述: 索引是高效获取数据的数据结构 索引结构: B+Tree() Hash(不支持范围查询,精准匹配效率极高) 树的区别: 二叉树:可能产生不平衡,顺序数据可能会出现链表结构 ...
- 一天五道Java面试题----第七天(mysql索引结构,各自的优劣--------->事务的基本特性和隔离级别)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1 .mysql索引结构,各自的优劣 2 .索引的设计原则 3 .mysql锁的类型有哪些 4 .mysql执行计划怎么看 ...
随机推荐
- android 获取uri的正确文件路径的办法
private String getRealPath( Uri fileUrl ) { String fileName = null; if( fileUrl != null ) { if( file ...
- java中super的几种用法,与this的区别
1. 子类的构造函数如果要引用super的话,必须把super放在函数的首位. class Base { Base() { System.out.println("Base"); ...
- Linux基础命令---nfsstat显示nfs信息
nfsstat nfsstat指令用来显示nfs客户端和服务器的活动信息. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.Fedora. 1.语法 nfsstat ...
- LoadRunner中怎么设置密码参数化与用户名关联
对密码参数化时从parameter里的"Select next row"列表中选择Same Line As这一选项,意思就是每一个密码参数化取值与对应行的用户名关联起来了
- ganglia -api
setup 命令: virtualenv ve source ve/bin/activate pip install -r requirements.txt python ganglia/gangli ...
- 超过三张表禁止join
一. 问题提出 <阿里巴巴JAVA开发手册>里面写超过三张表禁止join,这是为什么? 二.问题分析 对这个结论,你是否有怀疑呢?也不知道是哪位先哲说的不要人云亦云,今天我设计sql,来验 ...
- Nginx日志分析脚本
目录 一.简介 二.脚本 一.简介 运维工作是一个比较复杂的工作,有时候面对上万条的日志,如何作分析?难道一条条的分析? 这估计看两眼就要打哈欠了吧?聪明的人会选择脚本,这就是为什么现在提倡自动化运维 ...
- react-hook简单使用
一.函数式组件创建 function HelloComponent(props, /* context */) { return <div>Hello {props.name}</d ...
- 资源工作表中与资源有关的操作(Project)
<Project2016 企业项目管理实践>张会斌 董方好 编著 这个内容,我需要专门写一篇吗? 不写吧,好像对不起我那股学习的劲:写吧,实在是--一句话就够了:所有与任务有关的新建.修改 ...
- CF78B Easter Eggs 题解
Content 有一个有 \(n\) 个点的环,你可以将其染成一种颜色.一共有 \(7\) 种颜色(R,O,Y,G,B,I,V)可以选择.你的染色方案应该满足下面的要求: 每一个点都要被染色,且 \( ...