深入理解MySQLⅠ -- 存储引擎与索引

存储引擎

我们先来看看Mysql的体系结构;
在这里插入图片描述

  • 连接层:最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
  • 服务层第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
  • 引擎层:存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。
  • 存储层:数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。

存储引擎简介

  • 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式
  • 存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
  • 我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎。
    在这里插入图片描述

相关语法;

①建表时指定存储引擎:

-- 建表时指定存储引擎
CREATE TABLE 表名(
	...
) ENGINE=INNODB;

②查询当前数据库支持的存储引擎

show engines;

存储引擎特点

InnoDB

InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的MySQL 存储引擎。

特点

  • DML操作遵循ACID模型,支持事务
  • 行级锁,提高并发访问性能
  • 支持外键FOREIGN KEY约束,保证数据的完整性和正确性

涉及的磁盘文件

xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm-早期的 、sdi-新版的)、数据和索引。

例如:
在这里插入图片描述
可以看到里面有很多的ibd文件,每一个ibd文件就对应一张表,比如:我们有一张表 account,就有这样的一个account.ibd文件,而在这个ibd文件中不仅存放表结构、数据,还会存放该表对应的索引信息。 而该文件是基于二进制存储的,不能直接基于记事本打开,我们可以使用mysql提供的一个指令:

ibd2sdi xxx.ibd

通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表的表结构:
在这里插入图片描述

相关参数

innodb_file_per_table:决定多张表共享一个表空间还是每张表对应一个表空间

在MySQL8中,这个开关是默认打开的。我们也可以使用如下命令查看:

show variables like 'innodb_file_per_table';

在这里插入图片描述

逻辑存储结构
在这里插入图片描述

  • 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个Segment段。
  • : 表空间是由各个段组成的, 常见的段有数据段(用于存储表的数据记录)、索引段(用于存储索引的数据)、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
  • : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
  • : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
  • : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段:事务id和指针。

MyISAM

MyISAM是MySQL早期的默认存储引擎。

特点

  • 不支持事务,不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快

文件

  • xxx.sdi: 存储表结构信息
  • xxx.MYD: 存储数据
  • xxx.MYI: 存储索引

在这里插入图片描述

Memory

Memory 引擎的表数据是存储在内存中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用

特点:

  • 存放在内存中,速度快
  • hash索引(默认)

文件:

  • xxx.sdi: 存储表结构信息

在这里插入图片描述

无需存储数据的文件,因为数据在内存中

区别

特点InnoDBMyISAMMemory
存储限制64TB
事务安全支持--
锁机制行锁表锁表锁
B+tree索引支持支持支持
Hash索引--支持
全文索引支持(5.6版本之后)支持-
空间使用N/A
内存使用中等
批量插入速度
支持外键支持--

InnoDB引擎与MyISAM引擎的区别主要在于:

  • InnoDB引擎, 支持事务, 而MyISAM不支持
  • InnoDB引擎, 支持行锁和表锁, 而MyISAM仅支持表锁, 不支持行锁
  • InnoDB引擎, 支持外键, 而MyISAM是不支持的

存储引擎的选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
  • MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
    • 比如业务系统中日志相关的数据、电商项目中足迹和评论的相关数据
    • 使用较少,被NoSQL系列的数据库取代,例如mongodb
  • MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
    • 使用较少,被redis取代

索引*

索引概述

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

我们来看一个例子:

表结构及其数据如下
在这里插入图片描述
假如我们要执行的SQL语句为 :

select * from user where age = 45;
  • 如果无索引

    • 就需要从第一行开始扫描,一直扫描到最后一行,我们称之为 全表扫描,性能很低。
      在这里插入图片描述
  • 如果有索引

    • 假设索引结构就是二叉树,那么也就意味着,会对age这个字段建
      立一个二叉树的索引结构。此时我们在进行查询时,只需要扫描三次就可以找到数据了,极大的提高的查询的效率。

      注:这里我们只是假设索引的结构是二叉树,介绍一下索引的大概原理,只是一个示意图,并不是索引的真实结构

      在这里插入图片描述

索引的优缺点

优点

  • 提高数据检索效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

缺点

  • 索引列也是要占用空间的
  • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:

索引结构描述
B+Tree最常见的索引类型,大部分引擎都支持B+树索引
Hash底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询
R-Tree(空间索引)空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-Text(全文索引)是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES

上述是MySQL中所支持的所有的索引结构,接下来,我们再来看看不同的存储引擎对于索引结构的支持情况:

索引InnoDBMyISAMMemory
B+Tree索引支持支持支持
Hash索引不支持不支持支持
R-Tree索引不支持支持不支持
Full-text5.6版本后支持支持不支持

假如说MySQL的索引结构采用二叉树的数据结构,比较理想的结构如下:

在这里插入图片描述
如果主键是顺序插入的,则会形成一个单向链表,结构如下:
在这里插入图片描述
所以,如果选择二叉树作为索引结构,会存在以下缺点:

  • 顺序插入时,会形成一个链表,查询性能大大降低。
  • 大数据量情况下,层级较深,检索速度慢。

此时大家可能会想到,我们可以选择红黑树,红黑树是一颗自平衡二叉树,那这样即使是顺序插入数据,最终形成的数据结构也是一颗平衡的二叉树,结构如下:
在这里插入图片描述
但是,即使如此,由于红黑树也是一颗二叉树,所以也会存在一个缺点:

  • 大数据量情况下,层级较深,检索速度慢。

所以,在MySQL的索引结构中,并没有选择二叉树或者红黑树,而选择的是B+Tree,那么什么是B+Tree呢?在详解B+Tree之前,先来介绍一个B-Tree

B-Tree

B-Tree,B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:

树的度数指的是一个节点的子节点个数。

在这里插入图片描述

为了更好的理解B树,我们可以借助一个网站了解B树的构建过程:
https://www.cs.usfca.edu/~galles/visualization/BTree.html

我们来看一个例子:插入一组数据: 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250 。然后观察一些数据插入过程中,节点的变化情况。

在插入900的时候,此时的B树是这样的:
在这里插入图片描述
这个时候我们继续在此节点插入900,但是因为这是一个5阶树,只能有5个节点也就是5个指针。所以此时该节点的中间元素会向上分裂在这里插入图片描述
在这里插入图片描述

以此类推,当我们在下图情况下:
在这里插入图片描述
再往其中添加2456,右下角的1980会向上分裂:
在这里插入图片描述

1980向上分裂之后又发现根节点满了,于是中间元素继续向上分裂:
在这里插入图片描述

我们总结一下:

  • 5阶的B树,每一个节点最多存储4个key,对应5个指针。
  • 一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。
  • 在B树中,非叶子节点和叶子节点都会存放数据。

B+Tree

B+Tree是B-Tree的变种,我们以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一下其结构示意图:
在这里插入图片描述

我们可以看到,两部分:

  • 绿色框框起来的部分,是索引部分,仅仅起到索引数据的作用,不存储数据。
  • 红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据。

B+Tree 与 B-Tree相比,主要有以下三点区别

第一点:B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)

如下所示B-树/B+树查询节点 key 为 50 的 data。

在这里插入图片描述
从上图可以看出,key 为 50 的节点就在第一层,B-树只需要一次磁盘 IO 即可完成查找。所以说B-树的查询最好时间复杂度是 O(1)。

在这里插入图片描述
由于B+树所有的 data 域都在根节点,所以查询 key 为 50的节点必须从根节点索引到叶节点,时间复杂度固定为 O(log n)。

第二点:B+树叶节点两两相连可大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起,则无法区间查找。

在这里插入图片描述
由于B+树的叶子节点的数据都是使用链表连接起来的,而且他们在磁盘里是顺序存储的,所以当读到某个值的时候,磁盘预读原理就会提前把这些数据都读进内存,使得范围查询和排序都很快

第三点:B+树在一次IO里面,能读出的索引值更多。从而减少查询时候需要的IO次数

这个很好理解,由于B-树节点内部每个 key 都带着 data 域,而B+树节点只存储 key 的副本,真实的 key 和 data 域都在叶子节点存储。前面说过磁盘是分 block 的,一次磁盘 IO 会读取若干个 block,具体和操作系统有关,那么由于磁盘 IO 数据大小是固定的,在一次 IO 中,单个元素越小,量就越大。这就意味着B+树单次磁盘 IO 的信息量大于B-树,从这点来看B+树相对B-树磁盘 IO 次数少。

我们还是来看看B+Tree的构建过程:

在这里插入图片描述
在此情况下我们往里面添加890,按照B-Tree的思路,567会向上分裂,而在B+Tree中思路差不多,但是在中间元素向上分裂的时候,中间元素仍然存在于叶子节点中(所有的数据都会出现在叶子节点),并且叶子节点形成单向链表:
在这里插入图片描述

以此类推,在下面这种情况下,插入2345:
在这里插入图片描述
B+Tree变成下图所示:
在这里插入图片描述

关于分裂:
在这里插入图片描述

上述我们所看到的结构是标准的B+Tree的数据结构,接下来,我们再来看看MySQL中优化之后的B+Tree。

MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序

在这里插入图片描述

Hash

MySQL中除了支持B+Tree索引,还支持一种索引类型—Hash索引

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中

在这里插入图片描述

如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

在这里插入图片描述

特点

  • Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,…)
  • 无法利用索引完成排序操作
  • 查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索引

在MySQL中,支持hash索引的是Memory存储引擎。 而InnoDB中具有自适应hash功能,在一定条件下将B+Tree索引自动构建为hash索引。

热点:为什么InnoDB存储引擎选择使用B+tree索引结构?

  • 相对于二叉树(包括了红黑树),层级更少,搜索效率高;
  • 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少(因为页固定16k大小),指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
  • 相对Hash索引,B+tree支持范围匹配及排序操作;

索引分类

在MySQL数据库,将索引的具体类型主要分为以下几类:

  • 主键索引
  • 唯一索引
  • 常规索引
  • 全文索引
分类含义特点关键字
主键索引针对于表中主键创建的索引默认自动创建,只能有一个PRIMARY
唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE
常规索引快速定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个FULLTEXT

这个地方和数据库的约束有点类似,但是两者的意义是完全不同的。约束是对加入表的数据进行限制,而索引是帮助数据库更好的搜寻数据。

而在在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放一块,索引结构的叶子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

聚集索引和二级索引的具体结构如下:
在这里插入图片描述

  • 聚集索引的叶子节点下挂的是这一行的数据
  • 二级索引的叶子节点下挂的是该字段值对应的主键值

接下来,我们来分析一下,当我们执行如下的SQL语句时,具体的查找过程是什么样子的。
在这里插入图片描述

具体过程如下:

  1. 由于是根据name字段进行查询,所以先根据name='Arm’到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
  2. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。
  3. 最终拿到这一行的数据,直接返回即可。

回表查询这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询

思考

以下两条SQL语句,那个执行效率高? 为什么?

A. select * from user where id = 10 ;
B. select * from user where name = 'Arm' ;

A 语句的执行性能要高于B 语句。因为A语句直接走聚集索引,直接返回数据。 而B语句需要先查询name字段的二级索引,然后再查询聚集索引,也就是需要进行回表查询。

索引语法

创建索引:

CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ;
  • UNIQUE代表创建唯一索引,可选选项
  • FULLTEXT代表创建全文索引,可选选项
  • 两个都不选则创建常规索引

一个索引是可以关联多个字段的,如果一个索引只关联一个字段这种索引叫单列索引,如果一个索引关联了多个字段这种索引叫做联合索引(或者组合索引)

查看索引:

SHOW INDEX FROM table_name ;

删除索引:

DROP INDEX index_name ON table_name ;

我们来看一个例子:

现在有一个tb_user表:
在这里插入图片描述
需求:

①name字段为姓名字段,该字段的值可能会重复,为该字段创建常规索引。

CREATE INDEX idx_user_name ON tb_user(name);

②phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。

CREATE UNIQUE INDEX idx_user_phone ON tb_user(phone);

③为profession、age、status创建联合索引。

CREATE INDEX idx_user_pro_age_sta ON tb_user(profession,age,status);

④为email建立合适的索引来提升查询效率

CREATE INDEX idx_email ON tb_user(email);

然后我们来查看一下此时表中的索引:
在这里插入图片描述

SQL性能分析

SQL执行频率

MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:

SHOW GLOBAL STATUS LIKE 'Com_______';
  • session 是查看当前会话
  • global 是查询全局数据

在这里插入图片描述

  • Com_delete: 删除次数
  • Com_insert: 插入次数
  • Com_select: 查询次数
  • Com_update: 更新次数

通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据:

  • 如果是以增删改为主,我们可以考虑不对其进行索引的优化
  • 如果是以查询为主,那么就要考虑对数据库的索引进行优化了

那假如说是以查询为主,我们又该如何定位针对于那些查询语句进行优化呢? 次数我们可以借助于慢查询日志。

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢查询日志默认没有开启,我们可以查看一下系统变量slow_query_log。

在这里插入图片描述
如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

# 开启MySQL慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

配置完之后记得重启服务。

测试

执行如下SQL语句;

select * from tb_user; -- 这条SQL执行效率比较高, 执行耗时 0.00sec
select count(*) from tb_sku; -- 由于tb_sku表中, 预先存入了1000w的记录, count一次,耗时13.35sec

在这里插入图片描述

然后我们再看看慢日志:
在这里插入图片描述
我们发现,在慢查询日志中,只会记录执行时间超多我们预设时间(2s)的SQL,执行较快的SQL是不会记录的。

就这样我们可以通过慢查询日志,定位出执行效率比较低的SQL,从而有针对性的进行优化

profile详情

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:

SELECT @@have_profiling ;

在这里插入图片描述

可以看到,当前MySQL是支持 profile操作的,但是开关是关闭的。可以通过set语句在session/global级别开启profiling:

SET profiling = 1;

开关已经打开了,接下来,我们所执行的SQL语句,都会被MySQL记录,并记录执行时间消耗到哪儿去了。 我们直接执行如下的SQL语句

select * from tb_user;
select * from tb_user where id = 1;
select * from tb_user where name = '白起';
select count(*) from tb_sku;

执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:

-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;

在这里插入图片描述

在这里插入图片描述

explain执行计划

前面我们介绍了三种用于SQL性能分析的方法:

  • 通过执行频率
  • 通过慢日志得到耗时多的sql语句
  • 通过profile得到sql语句的具体耗时

这些都是从时间的层面来判断一条sql语句的效率,比较粗糙,并不能真正的判定一条sql语句的性能,所以我们还要借助于第四个工具 — explain

EXPLAIN 或者 DESC 命令可以获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序

语法:

-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;

在这里插入图片描述
Explain 执行计划中各个字段的含义:

  • id:select 查询的序列号,表示查询中执行 select 子句或者操作表的顺序(id相同,执行顺序从上到下;id不同,值越大越先执行)
  • select_type:表示 SELECT 的类型,常见取值有 SIMPLE(简单表,即不适用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
  • type:表示连接类型,性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all
  • possible_key:可能应用在这张表上的索引,一个或多个
  • Key:实际使用的索引,如果为 NULL,则没有使用索引
  • Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
  • rows:MySQL认为必须要执行的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的
  • filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好

索引使用

在说明索引的使用原则之前,先通过一个简单的案例,来验证一下索引,看看是否能够通过索引来提升数据查询性能。在演示的时候,我们还是使用之前准备的一张表 tb_sku , 在这张表中准备了1000w的记录。

这张表中id为主键,有主键索引,而其他字段是没有建立索引的。 我们先来查询其中的一条记录,看看里面的字段情况,执行如下SQL:

select * from tb_sku where id = 1\G;  //花费0.00seconds

可以看到即使有1000w的数据,根据id进行数据查询,性能依然很快,因为主键id是有索引的。 那么接下来,我们再来根据 sn 字段进行查询,执行如下SQL:

SELECT * FROM tb_sku WHERE sn = '100000003145001'; //花费20.78sec

这是因为sn没有索引,而造成查询效率很低。那么我们可以针对于sn字段,建立一个索引:

create index idx_sku_sn on tb_sku(sn) ;

建立了索引之后,我们再次根据sn进行查询,再来看一下查询耗时情况:

SELECT * FROM tb_sku WHERE sn = '100000003145001';  //0.01sec

我们明显会看到,sn字段建立了索引之后,查询性能大大提升。建立索引前后,查询耗时都不是一个数量级的。

最左前缀法则

在说最左前缀法则之前我们先说说联合索引

  • 单列索引和联合索引都是常规索引的一种形式
  • 单列索引指的是一个索引只包含一个列,联合索引是一个索引中包含了多个列。单列索引有一个不好的地方,如果我们查询的多个字段都是单列索引,那么此时只有一个索引会生效,其余的字段还需要 回表查询只才
    能拿到结果
    ,不建议频繁使用单列索引。
  • 在业务场景中,如果存在多个查询条件时,并且返回的字段也是查询条件中的字段,那么可以考虑针对这些查询的字段建立一个联合索引,尽量不是使用单列索引。
  • 当创建(a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及(a,b,c)联合索引。想要索引生效的话,只能使用 a和a,b和a,b,c三种组合,a,c组合也可以,但实际上只用到了a的索引,c并没有用到!

如果索引了多列(联合索引),要遵守最左前缀法则

最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。

以 tb_user 表为例,我们先来查看一下之前 tb_user 表所创建的索引。

在这里插入图片描述
在 tb_user 表中,有一个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession,age,status。

对于最左前缀法则指的是,查询时,最左边的列,也就是profession必须存在,否则索引全部失效。而且中间不能跳过某一列,否则该列后面的字段索引将失效

注意 :
最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。
也就是说在上面的例子中,我们编写以下SQL:
explain select * from tb_user where age = 31 and status = '0' and profession = '软件工程';也是完全满足最左前缀法则的。

范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。

我们执行下面的SQL语句:

explain select * from tb_user where profession = '软件工程' and age > 30 and status= '0';

在这里插入图片描述

profession字段索引长度为47、age字段索引长度为2、status字段索引长度为5

当范围查询使用> 或 < 时,走联合索引了,但是索引的长度为49,就说明范围查询右边的status字段是没有走索引的。

我们再执行一个SQL语句;

explain select * from tb_user where profession = '软件工程' and age >= 30 and status = '0';

在这里插入图片描述

当范围查询使用>= 或 <= 时,走联合索引了,但是索引的长度为54,就说明所有的字段都是走索引的。

所以,在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 <。

索引失效情况

索引失效情况分为以下几种:

  1. 在索引列上进行运算操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';
  2. 字符串类型字段使用时,不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。
  4. 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
  5. 如果 MySQL 评估使用索引比全表更慢,则不使用索引。

SQL提示

SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

有的时候你的操作中可能存在几种不同的索引使用情况,而这个时候MySQL会内部评估帮你选择,而你也可以指定使用哪个索引,或者忽略哪个索引。

语法规则:

use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估

explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';

ignore index : 忽略指定的索引。

explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';

force index:强制使用索引

explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';

覆盖索引&回表查询

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *。

接下来,我们来看一组SQL的执行计划,看看执行计划的差别:

explain select id, profession from tb_user where profession = '软件工程' and age =31 and status = '0' ;
explain select id,profession,age, status from tb_user where profession = '软件工程'and age = 31 and status = '0' ;
explain select id,profession,age, status, name from tb_user where profession = '软件工程' and age = 31 and status = '0' ;
explain select * from tb_user where profession = '软件工程' and age = 31 and status= '0';

上述这几条SQL的执行结果为:
在这里插入图片描述
explain 中 extra 字段含义:

  • using index condition:查找使用了索引,但是需要回表查询数据
  • using where; using index;:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询

我们来解释一下原因:

因为,在tb_user表中有一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据了,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。

我们给一个明确的定义:
通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表
覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 。

为了大家更清楚的理解,什么是覆盖索引,什么是回表查询,我们一起再来看下面的这组SQL的执行过程:

表结构及索引示意图:
在这里插入图片描述
id是主键,是一个聚集索引。 name字段建立了普通索引,是一个二级索引(辅助索引)

如果执行SQL : select * from tb_user where id = 2;
在这里插入图片描述
根据id查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。

如果执行SQL:selet id,name from tb_user where name = 'Arm';
在这里插入图片描述

虽然是根据name字段查询,查询二级索引,但是由于查询返回字段为 id,name,在name的二级索引中,这两个值都是可以直接获取到的,因为覆盖索引,所以不需要回表查询,性能高。

如果执行SQL:selet id,name,gender from tb_user where name = 'Arm';
在这里插入图片描述
由于在name的二级索引中,不包含gender,所以,需要两次索引扫描,也就是需要回表查询,性能相对较差一点。

思考:
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对
以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = 'itcast';
答案: 针对于 username, password建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password);
这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。

前缀索引

当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

相关语法:

create index idx_xxxx on table_name(column(n)) ;

例如:

为tb_user表的email字段,建立长度为5的前缀索引。

create index idx_email_5 on tb_user(email(5));

在这里插入图片描述

前缀长度

可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

例如:

select count(distinct substring(email,1,5)) / count(*) from tb_user ;
  • count(*)代表数据表的记录总数
  • count(distinct substring(email,x,y))代表截取x到y作为前缀,不重复的索引值个数

在这里插入图片描述
比较选择性,所以我们选择截取前5个作为前缀

前缀索引的查询流程
在这里插入图片描述
我们要明确无论如何前缀索引肯定是要回表的。在辅助索引里面的前缀匹配成功之后,它会回表完整的比较where后面的条件。

  • 无论是否匹配成功,则都会借用叶子节点的链表获取下一个元素,再进行比较
    • 如果匹配成功,还是从链表获取下一个元素
    • 如果匹配失败,则停止返回结果集

单列索引&联合索引

  • 单列索引:即一个索引只包含单个列
  • 联合索引:即一个索引包含了多个列
  • 在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

例子:

  • explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';
  • 在and连接的两个字段 phone、name上都是有单列索引的,但是
    最终mysql只会选择一个索引,也就是说,只能走一个字段的索引,此时是会回表查询的
  • 如果创建一个phone和name字段的联合索引
  • 此时,查询时,就走了联合索引,而在联合索引中包含 phone、name的信息,在叶子节点下挂的是对应的主键id,所以查询是无需回表查询的。
  • 具体的结构示意图如下:
    在这里插入图片描述

索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段 长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十八岁讨厌编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值