目录
(本篇博客的学习是通过黑马的MySQL课程进行学习,文中有部分图是课程配图)
一.存储引擎
在介绍索引之前我们先了解认识一下存储引擎
1.MySQL体系结构
-
连接层:顾名思义是用来接收客户端的连接,完成一些连接的处理,还需要做对应的授权处理,管理最大连接数等(登录MySQL时输入的用户名密码就是由连接层进行校验的(能够操作哪些数据库,哪些表))
-
服务层:绝大部分的核心功能都是在服务层完成的,SQL接口,查询解析器,查询优化器,缓存(所有跨存储引擎的实现都是在服务层完成的)
-
引擎层:图中的一个一个圆柱体就是一个一个存储引擎,MySQL中提供了很多存储引擎进行选择,并且不满意可以对其进行扩展,存储引擎就是控制MySQL中数据的存储和提取的方式,服务器通过api和存储引擎进行通信,并且还有一个很重要的Index(索引)就是在存储引擎实现的,那么就意味着不同的存储引擎的索引是不一样的
-
存储层:引擎层是控制数据的存储和提取的方式,那么存储层就是存储数据库的相关数据
2.存储引擎简介
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式,存储引擎是基于表的,而不是库的,所以存储引擎也被称为表类型
但是我们之前写的表好像并没有给其设置存储引擎,那么到底是怎么一回事,我们可以查看某表的建表语句
show create table account
我们可以看到即使我们创建表的时候,没有设置存储引擎,那么MySQL也会提供一个默认的存储引擎(MySQL5.5之后默认为InnoDB)
我们也可以根据一条sql语句查看当前数据库支持哪些存储引擎,从而之后可以设置
show engines
我们重点看InnoDB,可以看到他是MySQL默认的存储引擎,并且支持事务,行级锁,支持外键
接下来使用sql语句创建表并且设置上存储引擎
3.存储引擎特点
(1.InnoDB
InnoDB是一种兼顾高可靠性,高性能的通用存储引擎,在MySQL5.5后,InnoDB是默认的MySQL存储引擎
特点:
- DML操作(增删改)遵循ACID模型,支持
事务
行级锁
,提高并发访问性能- 支持
外键
foreign key约束,保证数据的完整性和正确性
文件:xxx.ibd:xxx表示的是表名,innoDB引擎的每张表都会对应一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引
(2.MyISAM
MyISAM是MySQL早期默认存储引擎
特点:
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
文件:xxx.sdi表结构信息、xxx.MYD存储数据、xxx.MYI存储索引
(3.Memory
Memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或者缓存使用
特点:
- 内存存放
- hash索引(默认)
文件:xxx.sdi:存储表结构信息
4.存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择适合的存储引擎,对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合
InnoDB:是mysql默认的存储引擎,支持事务,外键,如果应用对于事务的完整性有比较高的要求,在并发条件下要求数据一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么innoDB存储引擎是合适的选择
MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的
Memory:将所有数据保存在内存中,访问速度快,通常用于临时表以及缓存,Memory的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
二.索引
1.索引概述
索引(Index)是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引
如果数据库中没有索引,让我们想想看,要去找一个数据,只能一个一个的去找,也就是我们所说的全表扫描(效率极低),如果有索引呢,我们知道索引是一个数据结构,这里我们先理解为是树,那么我们找到这个数据会变快许多
索引优缺点:
- 优点:提高数据检索的效率,降低数据库的IO成本、通过索引对数据进行排序,降低数据排序的成本,降低CPU的消耗
- 缺点:索引列需要占用磁盘空间、索引大大提高了查询效率,同时降低了更新表的速度,比如对表进行insert、update、delete需要维护索引
2.索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包括以下几种
- B+Tree索引 :最常见的索引类型,大部分引擎都支持B+树索引
- Hash索引:底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
- R-tree(空间索引) :空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型
- Full-text(全文索引):是一种通过建立倒排序索引,快速匹配文档的方式,类似于Lucene、Solr、ES
二叉树:
缺点:顺序插入时,会形成一个链表,查询性能大大下降,大数据情况下,层级较深,检索速度慢
红黑树能自平衡二叉树,但是还是有着大数据下层级较深的问题
B-Tree:
是如何得到这样的结构的呢?这里我们使用一个网站进行分析
[数据结构可视化工具:](Data Structure Visualization (usfca.edu))
因为我们此时的B-Tree是五阶的那么再插入900的时候就会发送裂变,中间元素向上分裂
接着我们继续看看分裂过程
我们继续插入215的时候,那么发现下面右边的节点又要发生裂变
就一直这样插入、裂变得到最后的结构
B+Tree:
乍一看感觉和B-Tree很相似,但是我们注意看,B+Tree的所有数据都是在叶子节点的,上方的数据只是做一个索引,并且叶子节点形成了一个单向链表
同样我们使用可视化工具进行分析
接着我们看看插入900后和B-Tree有什么区别
我们发现裂变是一样的,但是B+Tree的所有元素都存在在叶子节点上,并且叶子节点形成了单向链表
B+Tree相对于B-Tree的区别:1.所有的数据都会出现在叶子节点上,2.叶子节点形成一个单向链表
B+Tree:MySQL索引数据结构对经典的B+Tree进行了优化,在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能
hash:
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中,如果两个或多个键值映射到相同的槽位,那么就产生了hash冲突,可以通过链表来解决
特点:
- hash索引只用于对等比较(=、in),不支持范围查询(between,>,<,...)
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次检索就可以了(无hash冲突),效率通常高于B+Tree索引
在MySQL中,支持hash索引的是Memory引擎,而innoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的
为什么innoDB存储引擎选择使用B+Tree索引结构?
1.相对于二叉树,层级更少,搜索效率高
2.相对于B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加数的高度,导致性能降低
3.相对Hash索引,B+Tree能支持范围匹配和排序操作
3.索引分类
在innoDB中,根据索引的形式,又可以给索引分为:
- 聚集索引: 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据(必须有,且只有一个)
- 二级索引: 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键(可以存在多个)
既然聚集索引必须要有,那么如何定义聚集索引呢?
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一索引作为聚集索引
- 如果表没有主键,没有合适的唯一索引,则innoDB会自动生成一个rowid作为隐藏的聚集索引
接下来用一个例子来聚集索引和二级索引
在图中我们可以看到,聚集索引是id这个主键的索引,聚集索引中的叶子节点的row存放的是这一行的数据,二级索引是name对应的索引,而二级索引中叶子节点中存放的数据是对应的字段和对应的主键
接下来根据这个表执行如下sql语句
select * from user where name = 'Arm';
首先我们知道是根据name字段进行查询,那么走的是name的索引,也就是二级索引,根据上方我们分析的,这个二级索引我们可以拿到name,和id,但是我们需要select的是*(全部字段),所以我们就需要根据二级索引得到的主键id,进行聚集索引,得到行数据(而根据二级索引拿到主键,进行聚集索引就是常说的回表查询)
4.索引语法
(1.创建索引
create [unique|fulltext] index 索引名称 on 表名 (字段名,...)
字段名这里可以指定多字段,也就是联合索引,如果只关联一个字段称为单列索引
(2.查看索引
show index from 表名
查看当前表的所有索引
(3.删除索引
drop index 索引名称 on 表名
5.SQL性能分析
(1.SQL执行频率
MySQL客户端连接成功后,通过show [session/global] status命令可以提供服务器状态信息,通过如下指令,可以查看当前数据库insert、update、delete、select的访问频次
show global status like 'Com_______';
一般来说我们需要对SQL进行优化的操作存在在select中,在平时的数据库操作中,我们用到的查询语句也是最多的,所以我们对其进行索引优化
(2.慢查询日志
在上面的分析中,我们已经知道查询语句是我们最常用的,那么我们就可以根据慢查询日志查询具体要优化哪些SQL语句
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句日志,MySQL的慢查询日志默认没有开启,我们需要将其修改
#开启MySQL慢查询开关:slow_query_log=1
#设置慢查询日志的默认时间为2秒:long_query_time=2
接着我们就可以根据慢查询日志看具体是哪条SQL语句用时较长,就可以具体去进行优化
(3.profile详情
show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了,通过hava_profiling参数,能够看到当前MySQL是否支持profile操作
show variables like 'have_pro%';
再查看profiling是否打开,如果没打开就将其打开
select @@profiling;
set profiling = 1;
我事先准备好了一张表,表的数据为
接着我们使用一些sql查询语句,并且使用profile操作查看sql语句的具体耗时
#查看每一条SQL的耗时基本情况
show profiles;
#查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query_id;
确实我们这条SQL语句的用时全在sleep上
#还可以查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
(4.explain执行计划
上方的SQL性能分析都是通过时间进行,但是这显然是不够的,我们要从多索引的角度对SQL语句进行分析,这就要使用到explain执行计划
explain或者desc命令获取MySQL如何执行select语句中的信息,包括select语句执行过程中表如何连接和连接顺序
#直接在select语句之前加上关键字explain/desc
explain select 字段列表 from 表名 where 条件;
接下来介绍一下explain执行计划查询到的每一列代表什么意思:
- id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序(多表查询)(执行顺序从上到下;id不同,值越大,越先执行)
- select_type:表示select的类型,常见的取值有simple(简单表,即不使用表连接或者子查询)、primary(主查询,即外层的查询)、union(union中的第二个或者后面的查询语句)、subquery(select/where之后包括了子查询)等
- type:表示连接类型,性能由好到差的连接类型为null、system、const、eq_ref、ref、range、index、all(进行优化的时候尽量往前优化,这里的all就是代表全表扫描性能极差)(如果我们使用主键或者唯一索引进行查询时会出现const,而如果是使用联合索引那么就是ref)
- possible_key:显示可能应用在这张表上的索引,一个或多个
- key:实际用的索引(如果为null表示没有使用索引)
- key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确度的前提下,长度越短越好
- rows:MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的
- filtere:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
- extra:额外信息
6.索引使用
在讲解索引的使用之前我们先看看索引对查询效率的提升到底有多大
现在我们有一张有1000w数据的表tb_sku,接着我们使用一些查询语句看看
这里的速度是0.00秒,因为此处我们查询根据的是主键(默认有索引)进行查询也就是聚集索引
接着我们根据这个表的另一个字段进行查询(无索引),发现用时20.78秒
为了验证索引对查询效率的提升,我们对sn字段加上一个索引,然后再次执行相同的sql语句看看查询时间
#针对sn一个字段创建索引
create index idx_sku_sn on tb_sku(sn)
可以看到此时我们查询的时间是0.01秒(之前为20.78),这个时候我们使用sn这个字段进行查询都会基于这个创建的索引,大大缩短查询时间
最后再看看他这个sql的explain执行计划
(1.最左前缀法则
最左前缀法则主要是针对联合查询的,我们先在tb_user表中创建两个单列索引和一个联合索引
最左前缀法则:如果索引了多列(联合索引),要遵守最左前缀法则,指的是查询从索引的最左列开始,并且不跳过索引中的列,如果跳过某一列,索引将部分失效(后面字段的索引失效)
在此联合索引中,profession字段必须存在,如果不存在那么该联合索引整个失效(profession位于最左)
接下来让我们看看案例
这里我们将三个字段都写入了查询语句,那么在执行计划中也可以看到联合索引生效了
接下来我们只对联合索引的一二个字段进行查询,发现索引也生效了(最左边的字段存在,中间没有跳过某一列:符合最左前缀法则)
我们根据profession这个字段进行查询显然他也是符合最左前缀法则的索引生效了,这里的执行计划几乎没有什么变动,只有key_len有发生变化(这个索引长度是索引字段所属的表创建时的对应的字段字节数)
接着根据age和status进行查询,不满足最左前缀法则故没有走索引(最左的字段不存在)
根据profession字段和status字段进行查询,我们发现也走索引了,但是这里的索引长度(key_len)只有82,和之前的只使用profession字段查询的效果是一样的(因为中间跳过了age字段导致status字段的索引也失效)
最后我们还是根据这三个字段进行查询,但是顺序进行了变动,发现还是能走索引,并且这里的索引长度是92说明三个字段都走了索引(最左前缀法则只要求最左的字段存在,且中间不跳过某字段即可生效,没有要求顺序)
(2.范围查询
联合索引中,出现范围查询(>,<),查询右侧的列索引失效
这条查询语句,可以看到走索引了,但是key_len的长度是87,我们可以知道是status没有走索引,因为age使用了范围查询
但是我们同样是这条sql查询语句,我们将>30改为>=30
可以发现这时status又走了索引,那么如果在业务允许的情况下,尽量使用>=这样的运算符
(3.索引列运算
不要在索引列上进行运算,索引将失效
接下来我们的需求是需要查询手机尾号为01的数据
我们发现没有走索引type是all表示为全表扫描
(4.字符串不加单引号
字符串类型字段使用时,不加引号,索引将失效
可以看到索引也是没有生效
(5.模糊查询
如果仅仅是尾部模糊匹配,索引不会失效,如果是头部模糊匹配,索引失效 ,
(6.or连接的条件
用or分开的条件,如果or前条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
由于age是没有索引的(age存在于联合索引但是这里不会生效),所以用or连接,即使id有索引也不会被用到(这里的possible_keys为primary可能用到的索引,实际为null)
(7.数据分布影响
如果MySQL评估使用索引比全表扫描还慢,则不使用索引
这里我们看到我只是改动了phone的值,那么同样的sql语句一个使用索引,一个走全表扫描,因为第二个sql语句发现几乎所有的数据都满足这个条件,那么MySQL评估觉得不需要走索引,所以使用了全表扫描
(8.SQL提示
在上面的数据中我们给profession字段设置了一个联合索引idx_pro_age_sta,此时我们再给其设置一个单列索引,那么使用该字段进行查询的时候是走哪个索引呢?
可以看到这里使用的是联合索引(MySQL优化器自动选择的结果)
那么SQL提示就是优化数据库的重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的(告诉MySQL你要用哪个索引)
#在from表名之后 use index(索引名) :建议MySQL使用这个索引
在form表名之后 ignore index(索引名) :忽视某索引(不要用这个索引)
在form表名之后 force index(索引名) :强制使用某索引
这个和use index的区别就在于,use index只是建议使用这个索引,至于最终要不要用,需要看mysql优化器的想法,而force index就是让mysql优化器不要有自己的想法,一定用这个索引
(9.覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *
看这个例子,我们在返回查询的字段中加入了一个name,执行计划中的extra就发生了变化
using index condition :查找使用到了索引,但是需要回表查询操作
using where;using index:查找使用了索引,但是需要的数据在索引列中能够找到,不需要回表查询数据
那么为什么加上name就需要回表了呢?
因为联合索引是二级索引,这个联合索引里包括profession、age、status并且二级索引的数据就是id,所以上方的两条sql语句都不需要进行回表操作,需要返回的数据都可以找到,而一旦加上name,只能根据二级索引获取到的id进行聚集索引找到对应的数据,故进行了回表操作(性能更差)
(10.前缀索引
当字段;类型为字符串(varchar、text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率
此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
create index idx_xxx on table_name(column(n));
如何确定前缀长度呢? · 可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高(唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的)
select count(distinct email)/count(*) from tb_user;
select count(distinct substring(email,1.5))/count(*) from tb_user;
也就是说对于email字段,索引选择性是最好的(没有重复的),这个时候对其进行前缀索引,降低索引占用空间,缓解系统IO压力
接着再根据substring来决定前缀索引的长度,这里我们发现直到截取前3个字符选择性都是1,而截取前2个选择性变为了0.7,所以我们选择前缀索引的长度为3
最后我们就对email字段设置前缀索引,长度为3
前缀索引的执行过程,是一定要走回表操作的,因为在前缀索引B+Tree中,每个节点存放的是该字段的部分,还有主键,那么就一定需要再经过聚集索引找需要的字段
(11.单列索引和联合索引
- 单列索引:一个索引只包含单个列
- 联合索引:一个索引包含多个列
让我们来看看这一条SQL语句的执行计划
我们可以看到,phone和name字段都有单列索引,但是这条SQL语句的执行计划告诉我们,只走了name字段的索引,那么我们就不能通过二级索引拿到phone字段,这就要涉及到了回表
我们给phone和name字段创建一个联合索引,再看看下面这个SQL语句的执行计划
即使我们创建了联合索引,MySQL优化器还是选择了使用name字段的单列索引,那么还是会走回表操作
接着我们使用SQL提示,显示告诉MySQL我们要用联合索引
由于联合索引不需要走回表查询的操作,如果存在多个查询条件,建议使用联合索引
7.索引设计规则
- 1.针对数据量较大,且查询比较频繁的表建立索引
- 2.针对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 3.尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用所有的效率越高
- 4.如果是字符串类型的字段,字段的长度较长,可以针对字段的特点,建立前缀索引
- 5.尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
- 6.要控制索引的数量,索引并不是越多越好,索引越多,维护索引结构的代价也越大,会影响增删改的效率
- 7.如果索引列不能存储null值,请在创建表时,使用Not null约束它,当优化器知道每列是否包含null值时,它可以更好地确定哪个索引最有效地用于查询