官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引比如是一本书前面的目录,能加快数据库的查询速度。mysql
通常来讲索引自己也很大,不可能所有存储在内存中,所以索引每每是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一块儿存储在数据文件中)。web
咱们一般所说的索引,包括汇集索引、覆盖索引、组合索引、前缀索引、惟一索引等,没有特别说明,默认都是使用B+树结构组织(多路搜索树,并不必定是二叉的)的索引。sql
优点:数据库
能够提升数据检索的效率,下降数据库的IO成本,相似于书的目录。缓存
经过索引列对数据进行排序,下降数据排序的成本,下降了CPU的消耗。数据结构
劣势:并发
索引会占据磁盘空间svg
索引虽然会提升查询效率,可是会下降更新表的效率。好比每次对表进行增删改操做,MySQL不只要保存数据,还有保存或者更新对应的索引文件。性能
索引列中的值必须是惟一的,不容许有空值。优化
MySQL中基本索引类型,没有什么限制,容许在定义索引的列中插入重复值和空值。
索引列中的值必须是惟一的,可是容许为空值。
只能在文本类型CHAR,VARCHAR,TEXT类型字段上建立全文索引。字段长度比较大时,若是建立普通索引,在进行like模糊查询时效率比较低,这时能够建立全文索引。 MyISAM和InnoDB中均可以使用全文索引。
MySQL在5.7以后的版本支持了空间索引,并且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。
在文本类型如CHAR,VARCHAR,TEXT类列上建立索引时,能够指定索引列的长度,可是数值类型不能指定。
单列索引
组合索引
组合索引的使用,须要遵循最左前缀匹配原则(最左匹配原则)。通常状况下在条件容许的状况下使用组合索引替代多个单列索引使用。
Hash表,在Java中的HashMap,TreeMap就是Hash表结构,以键值对的方式存储数据。咱们使用Hash表存储表数据Key能够存储索引列,Value能够存储行记录或者行磁盘地址。Hash表在等值查询时效率很高,时间复杂度为O(1);可是不支持范围快速查找,范围查找时仍是只能经过扫描全表方式。
显然这种并不适合做为常常须要查找和范围查找的数据库索引使用。
二叉树,我想你们都会在内心有个图。
二叉树特色:每一个节点最多有2个分叉,左子树和右子树数据顺序左小右大。
这个特色就是为了保证每次查找均可以这折半而减小IO次数,可是二叉树就很考验第一个根节点的取值,由于很容易在这个特色下出现咱们并发想发生的状况“树不分叉了”,这就很难受很不稳定。
显然这种状况不稳定的咱们再选择设计上必然会避免这种状况的
平衡二叉树是采用二分法思惟,平衡二叉查找树除了具有二叉树的特色,最主要的特征是树的左右两个子树的层级最多相差1。在插入删除数据时经过左旋/右旋操做保持二叉树的平衡,不会出现左子树很高、右子树很矮的状况。
使用平衡二叉查找树查询的性能接近于二分查找法,时间复杂度是 O(log2n)。查询id=6,只须要两次IO。
就这个特色来看,可能各位会以为这就很好,能够达到二叉树的理想的状况了。然而依然存在一些问题:
时间复杂度和树高相关。树有多高就须要检索多少次,每一个节点的读取,都对应一次磁盘 IO 操做。树的高度就等于每次查询数据时磁盘 IO 操做的次数。磁盘每次寻道时间为10ms,在表数据量大时,查询性能就会不好。(1百万的数据量,log2n约等于20次磁盘IO,时间20*10=0.2s)
平衡二叉树不支持范围查询快速查找,范围查询时须要从根节点屡次遍历,查询效率不高。
MySQL的数据是存储在磁盘文件中的,查询处理数据时,须要先把磁盘中的数据加载到内存中,磁盘IO 操做很是耗时,因此咱们优化的重点就是尽可能减小磁盘 IO 操做。访问二叉树的每一个节点就会发生一次IO,若是想要减小磁盘IO操做,就须要尽可能下降树的高度。那如何下降树的高度呢?
假如key为bigint=8字节,每一个节点有两个指针,每一个指针为4个字节,一个节点占用的空间16个字节(8+4*2=16)。
由于在MySQL的InnoDB存储引擎一次IO会读取的一页(默认一页16K)的数据量,而二叉树一次IO有效数据量只有16字节,空间利用率极低。为了最大化利用一次IO空间,一个简单的想法是在每一个节点存储多个元素,在每一个节点尽量多的存储数据。每一个节点能够存储1000个索引(16k/16=1000),这样就将二叉树改形成了多叉树,经过增长树的叉树,将树从高瘦变为矮胖。构建1百万条数据,树的高度只须要2层就能够(1000*1000=1百万),也就是说只须要2次磁盘IO就能够查询到数据。磁盘IO次数变少了,查询数据的效率也就提升了。
这种数据结构咱们称为B树,B树是一种多叉平衡查找树,以下图主要特色:
B树的节点中存储着多个元素,每一个内节点有多个分叉。
节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在全部的节点都储存数据。
父节点当中的元素不会出如今子节点中。
全部的叶子结点都位于同一层,叶节点具备相同的深度,叶节点之间没有指针链接。
举个例子,在b树中查询数据的状况:
假如咱们查询值等于10的数据。查询路径磁盘块1->磁盘块2->磁盘块5。
第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,10<15,走左路,到磁盘寻址磁盘块2。
第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<10,到磁盘中寻址定位到磁盘块5。
第三次磁盘IO:将磁盘块5加载到内存中,在内存中从头遍历比较,10=10,找到10,取出data,若是data存储的行记录,取出data,查询结束。若是存储的是磁盘地址,还须要根据磁盘地址到磁盘中取出数据,查询终止。
相比二叉平衡查找树,在整个查找过程当中,虽然数据的比较次数并无明显减小,可是磁盘IO次数会大大减小。同时,因为咱们的比较是在内存中进行的,比较的耗时能够忽略不计。B树的高度通常2至3层就能知足大部分的应用场景,因此使用B树构建索引能够很好的提高查询的效率。
过程如图:
看到这里必定以为B树就很理想了,可是前辈们会告诉你依然存在能够优化的地方:
B树不支持范围查询的快速查找,你想一想这么一个状况若是咱们想要查找10和35之间的数据,查找到15以后,须要回到根节点从新遍历查找,须要从根节点进行屡次遍历,查询效率有待提升。
若是data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。
B+树,做为B树的升级版,在B树基础上,MySQL在B树的基础上继续改造,使用B+树构建索引。B+树和B树最主要的区别在于非叶子节点是否存储数据的问题
- B树:非叶子节点和叶子节点都会存储数据。
- B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。叶子节点之间使用双向指针链接,最底层的叶子节点造成了一个双向有序链表。
B+树的最底层叶子节点包含了全部的索引项。从图上能够看到,B+树在查找数据的时候,因为数据都存放在最底层的叶子节点上,因此每次查找都须要检索到叶子节点才能查询到数据。因此在须要查询数据的状况下每次的磁盘的IO跟树高有直接的关系,可是从另外一方面来讲,因为数据都被放到了叶子节点,因此放索引的磁盘块锁存放的索引数量是会跟这增长的,因此相对于B树来讲,B+树的树高理论上状况下是比B树要矮的。也存在索引覆盖查询的状况,在索引中数据知足了当前查询语句所须要的所有数据,此时只须要找到索引便可马上返回,不须要检索到最底层的叶子节点。
举个例子:
- 等值查询:
假如咱们查询值等于9的数据。查询路径磁盘块1->磁盘块2->磁盘块6。
第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,9<15,走左路,到磁盘寻址磁盘块2。
第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<9<12,到磁盘中寻址定位到磁盘块6。
第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,在第三个索引中找到9,取出data,若是data存储的行记录,取出data,查询结束。若是存储的是磁盘地址,还须要根据磁盘地址到磁盘中取出数据,查询终止。(这里须要区分的是在InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址。)
过程如图:
- 范围查询:
假如咱们想要查找9和26之间的数据。查找路径是磁盘块1->磁盘块2->磁盘块6->磁盘块7。
首先查找值等于9的数据,将值等于9的数据缓存到结果集。这一步和前面等值查询流程同样,发生了三次磁盘IO。
查找到15以后,底层的叶子节点是一个有序列表,咱们从磁盘块6,键值9开始向后遍历筛选全部符合筛选条件的数据。
第四次磁盘IO:根据磁盘6后继指针到磁盘中寻址定位到磁盘块7,将磁盘7加载到内存中,在内存中从头遍历比较,9<25<26,9<26<=26,将data缓存到结果集。
主键具有惟一性(后面不会有<=26的数据),不需再向后查找,查询终止。将结果集返回给用户。
能够看到B+树能够保证等值和范围查询的快速查找,MySQL的索引就采用了B+树的数据结构。
介绍完了索引数据结构,那确定是要带入到Mysql里面看看真实的使用场景的,因此这里分析Mysql的两种存储引擎的索引实现:MyISAM索引和InnoDB索引
以一个简单的user表为例。user表存在两个索引,id列为主键索引,age列为普通索引
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, KEY `idx_age` (`age`) USING BTREE ) ENGINE = MyISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
MyISAM的数据文件和索引文件是分开存储的。MyISAM使用B+树构建索引树时,叶子节点中存储的键值为索引列的值,数据为索引所在行的磁盘地址。
表user的索引存储在索引文件user.MYI
中,数据文件存储在数据文件 user.MYD
中。
简单分析下查询时的磁盘IO状况:
根据主键等值查询数据:
select * from user where id = 28;
磁盘IO次数:3次索引检索+记录数据检索。
根据主键范围查询数据:
select * from user where id between 28 and 47;
先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO)
将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO)
检索到叶节点,将节点加载到内存中遍历比较16<28,18<28,28=28<47。查找到值等于28的索引项。
根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO)
咱们的查询语句时范围查找,须要向后遍历底层叶子链表,直至到达最后一个不知足筛选条件。
向后遍历底层叶子链表,将下一个节点加载到内存中,遍历比较,28<47=47,根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO)
最后获得两条符合筛选条件,将查询结果集返给客户端。
磁盘IO次数:4次索引检索+记录数据检索。
**备注:**以上分析仅供参考,MyISAM在查询时,会将索引节点缓存在MySQL缓存中,而数据缓存依赖于操做系统自身的缓存,因此并非每次都是走的磁盘,这里只是为了分析索引的使用过程。
在 MyISAM 中,辅助索引和主键索引的结构是同样的,没有任何区别,叶子节点的数据存储的都是行记录的磁盘地址。只是主键索引的键值是惟一的,而辅助索引的键值能够重复。
查询数据时,因为辅助索引的键值不惟一,可能存在多个拥有相同的记录,因此即便是等值查询,也须要按照范围查询的方式在辅助索引树中检索数据。
每一个InnoDB表都有一个聚簇索引 ,聚簇索引使用B+树构建,叶子节点存储的数据是整行记录。通常状况下,聚簇索引等同于主键索引,当一个表没有建立主键索引时,InnoDB会自动建立一个ROWID字段来构建聚簇索引。InnoDB建立索引的具体规则以下:
- 在表上定义主键PRIMARY KEY,InnoDB将主键索引用做聚簇索引。
- 若是表没有定义主键,InnoDB会选择第一个不为NULL的惟一索引列用做聚簇索引。
- 若是以上两个都没有,InnoDB 会使用一个6 字节长整型的隐式字段 ROWID字段构建聚簇索引。该ROWID字段会在插入新行时自动递增。
除聚簇索引以外的全部索引都称为辅助索引。在中InnoDB,辅助索引中的叶子节点存储的数据是该行的主键值都。 在检索时,InnoDB使用此主键值在聚簇索引中搜索行记录。
这里以user_innodb为例,user_innodb的id列为主键,age列为普通索引。
CREATE TABLE `user_innodb` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, KEY `idx_age` (`age`) USING BTREE ) ENGINE = InnoDB;
InnoDB的数据和索引存储在一个文件t_user_innodb.ibd中。InnoDB的数据组织方式,是聚簇索引。
主键索引的叶子节点会存储数据行,辅助索引只会存储主键值。
等值查询数据:
select * from user_innodb where id = 28;
先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO)
将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO)
检索到叶节点,将节点加载到内存中遍历,比较16<28,18<28,28=28。查找到值等于28的索引项,直接能够获取整行数据。将改记录返回给客户端。(1次磁盘IO)
磁盘IO数量:3次。
除聚簇索引以外的全部索引都称为辅助索引,InnoDB的辅助索引只会存储主键值而非磁盘地址。
以表user_innodb的age列为例,age索引的索引结果以下图。
底层叶子节点的按照(age,id)的顺序排序,先按照age列从小到大排序,age列相同时按照id列从小到大排序。
使用辅助索引须要检索两遍索引:首先检索辅助索引得到主键,而后使用主键到主索引中检索得到记录。
画图分析等值查询的状况:
select * from t_user_innodb where age=19;
根据在辅助索引树中获取的主键id,到主键索引树检索数据的过程称为回表查询。
磁盘IO数:辅助索引3次+获取记录回表3次
仍是以本身建立的一个表为例:表 abc_innodb,id为主键索引,建立了一个联合索引idx_abc(a,b,c)。
CREATE TABLE `abc_innodb` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` varchar(10) DEFAULT NULL, `d` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, KEY `idx_abc` (`a`, `b`, `c`) ) ENGINE = InnoDB;
select * from abc_innodb order by a, b, c, id;
组合索引的数据结构:
组合索引的查询过程:
select * from abc_innodb where a = 13 and b = 16 and c = 4;
最左匹配原则:
最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。
在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,可是b列和c列是无序的,b列只有在a列值相等的状况下小范围内递增有序,而c列只能在a,b两列相等的状况下小范围内递增有序。
就像上面的查询,B+树会先比较a列来肯定下一步应该搜索的方向,往左仍是往右。若是a列相同再比较b列。可是若是查询条件没有a列,B+树就不知道第一步应该从哪一个节点查起。
能够说建立的idx_abc(a,b,c)索引,至关于建立了(a)、(a,b)(a,b,c)三个索引。、
组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就中止匹配。
覆盖索引并非说是索引结构,覆盖索引是一种很经常使用的优化手段。由于在使用辅助索引的时候,咱们只能够拿到主键值,至关于获取数据还须要再根据主键查询主键索引再获取到数据。可是试想下这么一种状况,在上面abc_innodb表中的组合索引查询时,若是我只须要abc字段的,那是否是意味着咱们查询到组合索引的叶子节点就能够直接返回了,而不须要回表。这种状况就是覆盖索引。
能够看一下执行计划:
覆盖索引的状况:
未使用到覆盖索引:
看到这里,你是否是对于本身的sql语句里面的索引的有了更多优化想法呢。好比:
在InnoDB的存储引擎中,使用辅助索引查询的时候,由于辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引若是须要获取当前记录完整数据就必然须要根据主键值从主键索引继续查询。这个过程咱们成位回表。想一想回表必然是会消耗性能影响性能。那如何避免呢?
使用索引覆盖,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)
若是在一个场景下,select id,name,sex from user where name ='zhangsan';
这个语句在业务上频繁使用到,而user表的其余字段使用频率远低于它,在这种状况下,若是咱们在创建 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是否是根据辅助索引查询到的结果就能够获取当前语句的完整数据。这样就能够有效地避免了回表再获取sex的数据。
这里就是一个典型的使用覆盖索引的优化策略减小回表的状况。
联合索引,在创建索引的时候,尽可能在多个单列索引上判断下是否可使用联合索引。联合索引的使用不只能够节省空间,还能够更容易的使用到索引覆盖。试想一下,索引的字段越多,是否是更容易知足查询须要返回的数据呢。好比联合索引(a_b_c),是否是等于有了索引:a,a_b,a_b_c三个索引,这样是否是节省了空间,固然节省的空间并非三倍于(a,a_b,a_b_c)三个索引,由于索引树的数据没变,可是索引data字段的数据确实真实的节省了。
联合索引的建立原则,在建立联合索引的时候因该把频繁使用的列、区分度高的列放在前面,频繁使用表明索引利用率高,区分度高表明筛选粒度大,这些都是在索引建立的须要考虑到的优化场景,也能够在常须要做为查询返回的字段上增长到联合索引中,若是在联合索引上增长一个字段而使用到了覆盖索引,那我建议这种状况下使用联合索引。
联合索引的使用