索引的概念:
在关系数据库中,索引是一种对数据库表中一列或多列的值进行排序的一种数据结构,可以提高查询速度。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,以此快速定位查询数据。
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,根本原因就在于索引减少了查询过程中的IO次数。
索引的数据结构:
索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引.
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.B+树底层实现是多路平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据.
相对于cpu和内存操作,磁盘IO开销很大,非常容易成为系统的性能瓶颈,所以每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。B+树应运而生。
B树,即平衡多路查找树(B-Tree),是为磁盘等外存储设备设计的一种平衡查找树。
B树简略示意图:
观察上图可见B树的两个特点:
- 树内的每个节点都存储数据
- 叶子节点之间无指针连接
B+树简略示意图:
再看B+树相对于B树的两个特点:
- 数据只出现在叶子节点
- 所有叶子节点增加了一个链指针
叶子结点是离散数学中的概念。一棵树当中没有子结点(即度为0)的结点称为叶子结点,简称“叶子”。 叶子是指出度为0的结点,又称为终端结点。
但是,为什么是B+树而不是B树呢?原因有两点:
- B树每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点能存储的key的数量很小,要保存同样多的key,就需要增加树的高度。树的高度每增加一层,查询时的磁盘I/O次数就增加一次,进而影响查询效率。而在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+树的高度,减少磁盘IO次数。
- B+树的叶子节点上有指针进行相连,因此在做数据遍历的时候,只需要对叶子节点进行遍历即可,这个特性使得B+树非常适合做范围查询。
索引的种类:
以InnoDB
引擎为例,Mysql索引可以做如下区分。
首先,索引可以分为聚集索引和非聚集索引
聚簇索引:
定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。
就像拼音目录对应的A-Z的字顺序,和新华字典实际存储的字的顺序A-Z也是一样的,如果我们中文新出了一个字,拼音开头第一个是B,那么他插入的时候也要按照拼音目录顺序插入到A字的后面
非聚簇索引:
定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。
其实按照定义,除了聚集索引以外的索引都是非聚集索引,只是人们想细分一下非聚集索引,分成普通索引,唯一索引,全文索引。如果非要把非聚集索引类比成现实生活中的东西,那么非聚集索引就像新华字典的偏旁字典,他结构顺序与实际存放顺序不一定一致。
其次,从逻辑上,索引可以区分为:
- 普通索引:普通索引是 MySQL 中最基本的索引类型,它没有任何限制,唯一任务就是加快系统对数据的访问速度。普通索引允许在定义索引的列中插入重复值和空值。
- 唯一索引:唯一索引与普通索引类似,不同的是创建唯一性索引的目的不是为了提高访问速度,而是为了避免数据出现重复。唯一索引列的值必须唯一,允许有空值。如果是组合索引,则列值的组合必须唯一。创建唯一索引通常使用
UNIQUE
关键字。例如在student
表中的id
字段上建立名为index_id
的索引CREATE UNIQUE INDEX index_id ON tb_student(id);
- 主键索引:主键索引就是专门为主键字段创建的索引,也属于索引的一种。主键索引是一种特殊的唯一索引,不允许值重复或者值为空。创建主键索引通常使用
PRIMARY KEY
关键字。不能使用CREATE INDEX
语句创建主键索引。 - 空间索引:空间索引是对空间数据类型的字段建立的索引,空间索引主要用于地理空间数据类型 ,很少用到。
- 全文索引:全文索引主要用来查找文本中的关键字,只能在
CHAR、VARCHAR 或 TEXT
类型的列上创建。在MySQL
中只有InnoDB,MyISAM
,存储引擎支持全文索引。全文索引允许在索引列中插入重复值和空值。
索引在实际使用上分为单列索引和多列索引(最左前缀原则)。
单列索引:单列索引就是索引只包含原表的一个列。在表中的单个字段上创建索引,单列索引只根据该字段进行索引。
**多列索引也称为复合索引或组合索引。**相对于单列索引来说,组合索引是将原表的多个列共同组成一个索引。
多列索引是在表的多个字段上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询。但是,只有查询条件中使用了这些字段中第一个字段时,索引才会被使用。
使用索引需要的注意事项
索引的优缺点:
索引的优点如下:
- 通过创建唯一索引可以保证数据库表中每一行数据的唯一性。
- 可以大大加快数据的查询速度,这是使用索引最主要的原因。
- 在实现数据的参考完整性方面可以加速表与表之间的连接。
- 在使用分组和排序子句进行数据查询时也可以显著减少查询中分组和排序的时间。
既然索引这么好,那么我们是不是尽情使用索引呢?非也,索引优点明显,但相对应,也有缺点:
- 创建和维护索引组要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
- 索引需要占磁盘空间,除了数据表占数据空间以外,每一个索引还要占一定的物理空间。
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态维护,这样就降低了数据的维护速度。
什么情况下应不建或少建索引
- 表记录太少
- 经常插入、删除、修改的表
- 数据重复且分布平均的表字段,假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
- 经常和主字段一块查询但主字段索引值比较多的表字段
什么样的字段适合创建索引
1、表的主键、外键必须有索引;外键是唯一的,而且经常会用来查询
2、经常与其他表进行连接的表,在连接字段上应该建立索引;
3、数据量超过300的表应该有索引;
4、重要的SQL或调用频率高的SQL,比如经常出现在where
子句中的字段,order by
,group by
, distinct
的字段都要添加索引
5、经常用到排序的列上,因为索引已经排序。
6、经常用在范围内搜索的列上创建索引,因为索引已经排序了,其指定的范围是连续的
索引失效场景
创建了索引并不意味着高枕无忧,在很多场景下,索引会失效。下面列举了一些导致索引失效的情形,是我们写SQL语句时应尽量避免的。
1、条件字段原因
- 单字段有索引,WHERE条件使用多字段(含带索引的字段),例如
SELECT * FROM student WHERE name ='张三' AND addr = '北京市'
语句,如果name
有索引而addr
没索引,那么SQL语句不会使用索引。 - 多字段索引,违反最佳左前缀原则。例如,
student
表如果建立了(name,addr,age
)这样的索引,WHERE
后的第一个查询条件一定要是name
,索引才会生效。
2、<>、NOT、in、not exists
当查询条件为等值或范围查询时,索引可以根据查询条件去找对应的条目。否则,索引定位困难(结合我们查字典的例子去理解),执行计划此时可能更倾向于全表扫描,这类的查询条件有:<>、NOT、in、not exists
3、查询条件中使用OR
如果条件中有or,即使其中有条件带索引也不会使用(因此SQL
语句中要尽量避免使用OR
)。要想使用OR
,又想让索引生效,只能将OR
条件中的每个列都加上索引。
4、查询条件使用LIKE通配符
SQL语句中,使用后置通配符会走索引,例如查询姓张的学生(SELECT * FROM student WHERE name LIKE '张%'
),而前置通配符(SELECT * FROM student WHERE name LIKE '%东'
)会导致索引失效而进行全表扫描。
5、索引列上做操作(计算,函数,(自动或者手动)类型装换)
有以下几种例子:
- 在索引列上使用函数:例如
select * from student where upper(name)='ZHANGFEI';
会导致索引失效,而select * from student where name=upper('ZHANGFEI');
是会使用索引的。 - 在索引列上计算:例如
select * from student where age-1=17;
6、在索引列上使用mysql的内置函数,索引失效
例如,SELECT * FROM student WHERE create_time
7、索引列数据类型不匹配
例如,如果age字段有索引且类型为字符串(一般不会这么定义,此处只是举例)但条件值为非字符串,索引失效,例如SELECT * FROM student WHERE age=18
会导致索引失效。
sql优化 涉及到索引失效和慢查询
1.对慢查询的优化:
在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们.
慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写.
- MySQL提供了explain命令来查看语句的执行计划,MySQL在执行某个语句之前,会将该语句过一遍查询优化器,之后会拿到对语句的分析,也就是执行计划,其中包含了许多信息. 可以通过其中和索引有关的信息来分析是否命中了索引,例如possilbe_key,key,key_len等字段,分别说明了此语句可能会使用的索引,实际使用的索引以及使用的索引长度.分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引.
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表.
参考资料:
MySQL中IS NULL、IS NOT NULL、!=不能用索引?胡扯! - 掘金
史上最详细的mysql底层和explan type和type中index和all的区别_explain type index_依然这么溜的博客-CSDN博客
https://blog.csdn.net/mu_wind/article/details/110128016
https://zhuanlan.zhihu.com/p/104811487