索引介绍
1.什么是索引?
索引是存储引擎中一种数据结构,或者说数据的组织方式,又称之为键key,是存储引擎用于快速找到记录的
一种数据结构。
为数据建立索引就好比是为书建目录,或者说是为字典创建音序表,如果要查某个字,如果不使用音序表,则
需要从几百页中逐页去查。
2.为什么要用索引?
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,
我们遇到最多的、也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。
说起加速查询,就不得不提到索引了。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
3.正确使用索引
# 先说结论:
索引不是越多越好,并且建议大家最好在提前创建好索引,而不是等问题出现,才想起需要索引
# 详解如下
索引是应用程序设计和开发的一个重要方面。若索引太多,应用程序的性能可能会受到影响。而索引太少,对
查询性能又会产生影响,要找到一个平衡点,这对应用程序的性能至关重要。
当读大于写的时候,构建索引不失为一种提高查询效率的好方法
磁盘相关知识
1)机械磁盘一次IO的时间
机械磁盘一次io的时间 = 寻道时间 + 旋转延迟 + 传输时间
# 寻道时间
道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下
# 旋转延迟
旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转
120次,旋转延迟就是1/120/2 = 4.17ms;
# 传输时间
传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计
所以访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右
这9ms对于人来说可能非常短,但对于计算机来可是非常长的一段时间,长到什么程度呢?
一台500 -MIPS(Million Instructions Per Second)的机器每秒可以执行5亿条指令,因为指令依靠
的是电的性质,换句话说执行一次IO的时间可以执行约450万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。
2)磁盘的预读
# 考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化:
当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k或16k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
3)索引原理
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,用数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段......这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN(冒泡排序,堆排序,选择排序,快速排序),具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
索引分类
BTREE:B+树索引 (等值查询与范围查询都快)
二叉树->平衡二叉树->B树->B+树
HASH:HASH索引(等值查询与范围查询都快)
将数据打散再去查询
FULLTEXT:全文索引(只可以用在MyISAM引擎)
通过关键字的匹配来进行查询,类似于like的模糊匹配
RTREE:R树索引(空间)
索引数据结构
二叉查找树
图解
二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,
右子节点的键值都大于当前节点的键值。
顶端的节点我们称为根节点,没有子节点的节点我们称之为叶节点。
利用二叉查找树我们只需要3次即可找到匹配的数据。
如果在表中一条条的查找的话,我们需要6次才能找到。
缺点
1.当我们去查找数据的时候,是以链表的形式去进行查询,
当数据量过于庞大的时候,使用二叉树去进行查询也相当于查询了很多次,
并没有很明显的优化
2.当我们删除数据或者修改数据的时候,会导致两边的层级不一样
平衡二叉树
图解
平衡二叉树主要突出在平衡二字,当我们去插入新的数据或者删除的时候,
平衡二叉树会去调整节点达到平衡,平衡二叉树相较于二叉树来讲,查找速度更快,效率更高
缺点
1.当数据量过大时,使用平衡二叉树去构建索引也会有二叉树同样的缺点,导致层级太高,降低效率
2.当数据量过大时,会导致磁盘的IO过高,因为这种方式是将节点放在磁盘块
B树
图解
在平衡二叉的树的基础上,把更多的节点放入一个磁盘块中,那么平衡二叉树的弊端也就解决了。
即构建一个单节点可以存储多个键值对的平衡树,这就是B树
B树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点
拥有更多的子节点,子节点的个数一般称为阶,上述图中的B树为3阶B树,高度也会很低。 基于这个特
性,B树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。
缺点
1.B树只擅长做等值查询,而对于范围查询(范围查询的本质就是n次等值查询),
或者说排序操作,B树也会去多次从根节点及叶节点去进行等值查询,对磁盘的IO也会增多
B+树
图解
1、B+树非叶子节点non-leaf node上是不存储数据的,仅存储键,
而B树的非叶子节点中不仅存储键,也会存储数据。
2、B+树的阶数是等于键的数量的。
3、B+树索引的所有数据均存储在叶子节点leaf node,
而且数据是按照顺序排列的。那么B+树使得范围查找,
排序查找,分组查找以及去重查找变得异常简单。
而B树因为数据分散在各个节点,要实现这一点是很不容易的。
而且B+树中各个页之间也是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
4、根节点在数据库服务启动时就就加载在内存中。
查找流程(查找id>=18并且id<40的用户数)
索引管理
索引的类型
1)聚集索引
主键索引,单列索引,唯一性
创表时定义主键,就会自动生成
一般在序列号的列上
一般在创表时就定义了
在项目中,很少会用主键列作为查找条件
2)非聚集索引
辅助索引
单列索引
唯一索引,主键索引外,保持唯一性的列
前缀索引,字段比较长,需要通过限制长度来做索引
3)联合索引 *****
多列索引,需要考虑执行顺序
索引管理的案例练习
环境准备
1.create database world;(有则无需创建)
2.use world;
3.create table stu(id int, name char(20), gender enum('m','f'), age int);
前置知识-怎么查看索引?
1)看表结构,看不清细节
desc stu
+--------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | char(20) | YES | | NULL | |
| gender | enum('m','f') | YES | | NULL | |
| age | int(11) | YES | | NULL | |
+--------+---------------+------+-----+---------+-------+
2)查看索引,能看到细节
show index from 库名.表名\G;
创建索引
案例1:在world.stu表中,对id列创建主键索引(仅用来演示,没有这种创建)
方法一:使用modify
alter table world.stu modify id int primary key;
方法二:使用change(需要先写出要修改的字段名再进行变更)
alter table world.stu change id id int primary key;
案例2:在world.stu表中,对name列创建一个辅助索引
方法一:create创建
create index idx_name on world.stu(name);
方法二:alter创建 (这里使用关键字key 和 index 效果一样)
alter table world.stu add index idx_name(name);
alter table world.stu add key idx_name(name);
***案例3:在world.stu表中,对age列创建一个唯一索引***
方法一:create创建
create unique index uni_age on world.stu(age);
方法二:alter创建
alter table world.stu add unique index uni_age(age);
案例4:在world.stu表中,对name列创建一个前缀索引
方法一:create创建
create index idx_name on world.stu(name(4));
方法二:alter创建
alter table world.stu add index idx_name(name(4));
案例5:在world.stu表中,对name、gender、age列创建一个联合索引
方法一:create创建
create index idx_nga on world.stu(name,gender,age);
方法二:alter创建
alter table world.stu add index idx_nga(name,gender,age);
删除
方法一:删除索引
drop index idx_name on world.stu;
方法二:修改表结构
alter table world.stu drop index idx_name;
优化器匹配联合索引的顺序
a,ab,abc 完整索引
ac 只匹配a
b,ba,bc,... 不走索引