欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

数据库-索引

程序员文章站 2023-10-19 08:50:41
引子 什么是索引 为什么需要索引 使用索引 索引优化原理 正确使用索引 什么是索引 在关系数据库中,索引是一种单独的、物理层面的对数据库表中一列或多列的值进行排序的一种存储结构; 也称之为 有以下几种: unique key primary key index key 索引的作用相当于图书的目录,可 ......

引子

什么是索引

为什么需要索引

使用索引

索引优化原理

正确使用索引

什么是索引

在关系数据库中,索引是一种单独的、物理层面的对数据库表中一列或多列的值进行排序的一种存储结构; 也称之为key

有以下几种:

unique key

primary key

index key

索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。

数据库-索引

为什么需要索引

思考:一个项目正常运行后,对数据库的操作中,哪些操作是最频繁的?

对数据库的写操作(增加 删除 修改)频繁吗?

对数据库的读操作(查询)频繁吗?

相比较下,对数据的读操作会更加频繁,比例在10:1左右,也就是说对数据库的查询操作是非常频繁的

随着时间的推移,表中的记录会越来越多,此时如果查询速度太慢的话对用户体验是非常不利的

索引是提升查询效率最有效的手段!

简单的说索引的就是用帮我们加快查询速度的

需要注意的是:在数据库中插入数据会引发索引的重建

小白的误区

既然索引如此神奇,那以后只要速度慢了就加索引,

这种想法是非常low的,

索引是不是越多越好,并且有了索引后还要考虑索引是否命中

加上索引后对数据的写操作速度会降低

索引的实现原理

如何能实现加快查询的效果呢?

来看一个例子:

第一版的新华字典共800页,那时没有检字表,每个字的详细信息,随机的罗列在书中,一同学买回来查了一次,在也没用过,因为没有任何的数据结构,查字只能一页一页往后翻,反了两小时没翻着,只能放弃了!

后来出版社发现了这个问题,他们将书中所有字按照拼音音节顺序进行了排序,拼音首字母为a的排在最前,首字母为z的排在最后:

数据库-索引

如此一来再不再需要一页一页的去查字了,而是先查看索引,找出字的拼音首字母到索引中进行对照,例如:找字其拼音首字母为d,所以直接找到d对应的索引目录,很快就能定位到要找的字在79页,查询速度得到数量级的提升!

需要注意的是,原来内容为800页现在因为多了索引数据,整体页数必然增加了

数据库中的索引,实现思路与字典是一致的,需要一个独立的存储结构,专门存储索引数据

本质上索引是通过不断的缩小查询范围来提高查询效率

磁盘io问题(了解)

数据库的数据最终存储到了硬盘上

机械硬盘由于设计原理,导致查找数据时需要有一个寻道时间与平均延迟时间,常规硬盘寻道为5ms,平均延迟按照每分钟7200转来计算,7200/60 = 120 ; 1000/120/2 = 4ms 总共为9ms,那么9毫秒对于cpu而言已经非常非常的长了,足够做很多运算操作,目前最新的处理器每秒能处理数万亿次运算,拿一个非常垃圾的处理器来举例子,假设处理器每秒处理5亿次计算,每毫秒是50万次运算,9ms可以进行450万次运算,数据库中成千上万的数据,每条数据9ms显然慢到不行!

操作系统预读取(了解)

考虑到磁盘io是非常高昂的操作,计算机操作系统做了一些优化,当一次io时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次io读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次io,这个理论对于索引的数据结构设计非常有帮助。

索引数据结构剖析

在字典的例子中我们知道了,索引是独立于真实数据的一个存储结构,这个结构到底是什么样的?

数据库-索引

索引最终的目的是要尽可能降低io次数,减少查找的次数,以最少的io找到需要的数据,此时b+树闪亮登场

光有数据结构还不行,还需要有对应的算法做支持,就是二分查找法

有了b+数据结构后查找数据的方式就不再是逐个的对比了,而是通过二分查找法来查找(流程演示)

另外,其实大多数文件系统都是使用b+是来完成的!

应该尽可能的将数据量小的字段作为索引

通过分析可以发现在上面的树中,查找一个任何一个数据都是3次io操作, 但是这个3次并不是固定的,它取决于树结构的高度,目前是三层,如果要存储新的数据比99还大的数据时,发现叶子节点已经不够了必须在上面加一个子节点,由于树根只能有一个所以,整个数的高度会增加,一旦高度增加则 查找是io次数也会增加,所以:

应该尽可能的将数据量小的字段作为索引,这样一个叶子节点能存储的数据就更多,从而降低树的高度;

例如:nameid,应当将id设置为索引而不是name

最左匹配原则*

当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候(多字段联合索引),b+树会按照从左到右的顺序来建立搜索树,比如当(张三,20,f)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,f)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,f)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是f的数据了, 这个是非常重要的性质,即索引的最左匹配特性。

聚集索引*

mysql官方文档原文: 插入了解 或折叠

mysql为表把它的数据词典信息以.frm文件的形式存在数据库目录里,这对所有mysql存储引擎都是真的。但 是每个innodb表在表空间内的innodb内部数据词典里有它自己的条目。当mysql移除表或数据库,它不得不 删除.frm文件和innodb数据词典内的相应条目。这就是为什么你不能在数据库之间简单地移动.frm文件来移 动innodb表。

每个innodb表有专门索引,被称为clustered index,对行的数据被存于其中。如果你对你的表定义一 个primary key, 主键的索引是集束索引。

如果你没有为表定义primary key,mysql拾取第一个仅有not null列的unique索引作为主键,并 且innodb把它当作集束索引来用。如果表中没有这样一个索引,innodb内部产生一个集束索引,其中 用innodb在这样一个表内指定给行的行id来排序行。行id是一个6字节的域,它在新行被插入的时候简单地增加。因此被行id排序的行是物理地按照插入顺序排的。

通过集束索引访问一个行是较快的,因为行数据是在索引搜索引导的同一页面。如果表是巨大的,当对比于传 统解决方案,集束索引构架经常节约磁盘i/o。(在许多数据库,数据传统地被存在与索引记录不同的页)。

在innodb中,非集束索引里的记录(也称为第二索引)包含对应行的主键值。innodb用这个 主键值来从集束索 引中搜索行。注意,如果主键是长的,第二索引使用更多空间。

简单总结:

聚焦索引的特点:

​ 叶子节点保存的就是完整的一行记录,如果设置了主键,主键就作为聚集索引,

​ 如果没有主键,则找第一个not null 且qunique的列作为聚集索引,

​ 如果也没有这样的列,innodb会在表内自动产生一个聚集索引,它是自增的

​ 聚集索引中包含了完整的记录

辅助索引*

除了聚集索引之外的索引都称之为辅助索引或第二索引,包括 foreign keyunique

辅助索引的特点:

​ 其叶子节点保存的是索引数据与所在行的主键值,innodb用这个 主键值来从聚集索引中搜查找数据

覆盖索引

​ 覆盖索引指的是需要的数据仅在辅助索引中就能找到:

#假设stu表的name字段是一个辅助索引
select name from stu where name = "jack";

​ 这样的话则不需要在查找聚集索引数据已经找到

回表

​ 如果要查找的数据在辅助索引中不存在,则需要回到聚集索引中查找,这种现象称之为回表

# name字段是一个辅助索引 而sex字段不是索引 
select sex from stu where name = "jack";

​ 需要从辅助索引中获取主键的值,在拿着主键值到聚集索引中找到sex的值

查询速度对比:

聚集索引 > 覆盖索引 > 非覆盖索引

正确使用索引

案例:

首先准备一张表数据量在百万级别

create table usr(id int,name char(10),gender char(3),email char(30));
#准备数据
delimiter //
create procedure adddata(in num int)
begin 
declare i int default 0;
while  i < num do
    insert into usr values(i,"jack","m",concat("xxxx",i,"@qq.com"));    
set i  = i + 1;
end while;
end//
delimiter ;

#执行查询语句 观察查询时间
select count(*) from usr where id = 1;
#1 row in set (3.85 sec)
#时间在秒级别 比较慢


1.
#添加主键
alter table usr add primary key(id);
#再次查询
select count(*) from usr where id = 1;
#1 row in set (0.00 sec)
#基本在毫秒级就能完成 提升非常大

2.
#当条件为范围查询时
select count(*) from usr where id > 1;
#速度依然很慢 对于这种查询没有办法可以优化因为需要的数据就是那么多
#缩小查询范围 速度立马就快了
select count(*) from usr where id > 1 and id < 10;




#当查询语句中匹配字段没有索引时 效率测试
select count(*) from usr where name = "jack";
#1 row in set (2.85 sec)
# 速度慢


3.
# 为name字段添加索引
create index name_index on usr(name);
# 再次查询
select count(*) from usr where name = "jack";
#1 row in set (3.89 sec)
# 速度反而降低了 为什么?
#由于name字段的区分度非常低 完全无法区分 ,因为值都相同 这样一来b+树会没有任何的子节点,像一根竹竿每一都匹配相当于,有几条记录就有几次io ,所有要注意 区分度低的字段不应该建立索引,不能加速查询反而降低写入效率,
#同理 性别字段也不应该建立索引,email字段更加适合建立索引

# 修改查询语句为
select count(*) from usr where name = "aaaaaaaaa";
#1 row in set (0.00 sec) 速度非常快因为在 树根位置就已经判断出树中没有这个数据 全部跳过了

# 模糊匹配时
select count(*) from usr where name like "xxx"; #快
select count(*) from usr where name like "xxx%"; #快
select count(*) from usr where name like "%xxx"; #慢
#由于索引是比较大小 会从左边开始匹配 很明显所有字符都能匹配% 所以全都匹配了一遍



4.索引字段不能参加运算
select count(*) from usr where id * 12 = 120;
#速度非常慢原因在于 mysql需要取出所有列的id 进行运算之后才能判断是否成立
#解决方案
select count(*) from usr where id = 120/12;
#速度提升了 因为在读取数据时 条件就一定固定了 相当于
select count(*) from usr where id = 10;
#速度自然快了

5.有多个匹配条件时 索引的执行顺序  and 和 or
#先看and
#先删除所有的索引
alter table usr  drop primary key;
drop index name_index on usr;

#测试
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
#1 row in set (1.34 sec) 时间在秒级 

#为name字段添加索引
create index name_index on usr(name);
#测试
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
#1 row in set (17.82 sec) 反而时间更长了

#为gender字段添加索引
create index gender_index on usr(gender);
#测试
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
#1 row in set (16.83 sec) gender字段任然不具备区分度 

#为id加上索引
alter table usr add primary key(id);
#测试
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx1@qq.com";
#1 row in set (0.00 sec) id字段区分度高 速度提升
#虽然三个字段都有索引 mysql并不是从左往右傻傻的去查 而是找出一个区分度高的字段优先匹配

#改为范围匹配
select count(*) from usr where name = "jack" and gender = "m" and id > 1 and email = "xxxx1@qq.com";
#速度变慢了

#删除id索引 为email建立索引
alter table usr drop primary key;
create index email_index on usr(email);
#测试
select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
#1 row in set (0.00 sec) 速度非常快

#对于or条件 都是从左往右匹配 
select count(*) from usr where name = "jackxxxx" or email = "xxxx0@qq.com";

#注意 必须or两边都有索引才会使用索引 

and 语句中只要有一个存在索引就能提高速度  但是如果对方并没有使用


6.多字段联合索引
为什么需要联合索引
案例:
select count(*) from usr where name = "jack" and gender = "m" and id  > 3 and email = "xxxx2@qq.com";
假设所有字段都是区分度非常高的字段,那么除看id为谁添加索引都能够提升速度,但是如果sql语句中没有出现所以字段,那就无法加速查询,最简单的办法是为每个字段都加上索引,但是索引也是一种数据,会占用内存空间,并且降低写入效率
此处就可以使用联合索引,

联合索引最重要的是顺序 按照最左匹配原则 应该将区分度高的放在左边 区分度低的放到右边
#删除其他索引
drop index name_index on usr;
drop index email_index on usr;
#联合索引
create index mul_index on usr(email,name,gender,id);
# 查询测试
select count(*) from usr where name = "xx" and id = 1 and email = "xx";
只要语句中出现了最左侧的索引(email) 无论在前在后都能提升效率 

drop index mul_index on usr;