引子mysql
什么是索引算法
为何须要索引sql
使用索引数据库
索引优化原理数据结构
正确使用索引测试
在关系数据库中,索引是一种单独的、物理层面的对数据库表中一列或多列的值进行排序的一种存储结构; 也称之为key
大数据
有如下几种:优化
unique key操作系统
primary key设计
index key
索引的做用至关于图书的目录,能够根据目录中的页码快速找到所需的内容。
思考:一个项目正常运行后,对数据库的操做中,哪些操做是最频繁的?
对数据库的写操做(增长 删除 修改)频繁吗?
对数据库的读操做(查询)频繁吗?
相比较下,对数据的读操做会更加频繁,比例在10:1左右,也就是说对数据库的查询操做是很是频繁的
随着时间的推移,表中的记录会愈来愈多,此时若是查询速度太慢的话对用户体验是很是不利的
索引是提高查询效率最有效的手段!
简单的说索引的就是用帮咱们加快查询速度的
须要注意的是:在数据库中插入数据会引起索引的重建
既然索引如此神奇,那之后只要速度慢了就加索引,
这种想法是很是low的,
索引是否是越多越好,而且有了索引后还要考虑索引是否命中
加上索引后对数据的写操做速度会下降
来看一个例子:
初版的新华字典共800页,那时没有检字表,每一个字的详细信息,随机的罗列在书中,一同窗买回来查了一次,在也没用过,由于没有任何的数据结构,查字只能一页一页日后翻,反了两小时没翻着,只能放弃了!
后来出版社发现了这个问题,他们将书中全部字按照拼音音节顺序进行了排序,拼音首字母为a的排在最前,首字母为z的排在最后:
如此一来再再也不须要一页一页的去查字了,而是先查看索引,找出字的拼音首字母到索引中进行对照,例如:找搭
字其拼音首字母为d,因此直接找到D对应的索引目录,很快就能定位到要找的搭
字在79页,查询速度获得数量级的提高!
须要注意的是,原来内容为800页如今由于多了索引数据,总体页数必然增长了
数据库中的索引,实现思路与字典是一致的,须要一个独立的存储结构,专门存储索引数据
本质上索引是经过不断的缩小查询范围来提升查询效率
数据库的数据最终存储到了硬盘上
机械硬盘因为设计原理,致使查找数据时须要有一个寻道时间与平均延迟时间,常规硬盘寻道为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次数也会增长,因此:
应该尽量的将数据量小的字段做为索引,这样一个叶子节点能存储的数据就更多,从而下降树的高度;
例如:name
和id
,应当将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 key
与 unique
辅助索引的特色:
其叶子节点保存的是索引数据与所在行的主键值,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;
1.使用占用空间最小的字段来做为索引
2.不要再一行中存储太多的数据,例如小说,视频,若是字段太多能够分表
3.尽可能使用覆盖查询
4.若是字段区分度低(重复度高),创建索引是没有意义,反过来讲应该将区分度高的字段做为索引
5.模糊匹配中,百分号尽可能不要写在前面
6.不要再等号的左边作运算
例如:select count() from usr where id 3 = 6; 也会遍历全部记录
7.and语句中会自动找一个具有缩印的字段优先执行,因此咱们应该在and语句中至少包含一个具有索引的字段
8.or语句要避免使用,若是要用则保证全部字段都有索引才能加速
9.联合索引中,顺序应该将区分度最高的放到左边,最低的放右边,
查询语句中必须保证最左边的索引出如今语句中
另外须要注意:若是要查询的数据量很是大 索引没法加速
总结: 不是添加了索引就能提速,须要考虑索引添加的是否合理,sql语句是否使用到了索引
额外的优化手段:
1.explain 查询sql的执行计划 找出全表扫描发生的缘由 ,从而修改sql语句
2.启用慢查询记录,设置最大时间,将执行时间过长的sql记录到日志中,从而加以分析