MySQL索引及优化技巧

1、Mysql的执行计划

1.1、为什么需要执行计划?

有的sql语句执行效率高,有的执行效率低,需要对sql语句做调整和优化,所以就会涉及到执行计划

1.2、执行计划是什么?

执行计划具体来说就是一条sql语句的执行过程

可以看到执行过程中用到了哪些关键的信息,并根据这些信息做判断

1.3、如何使用执行计划?

就是在sql语句前面加上关键字==explain==,在sql语句前面加上explain之后,它会输出n多个列

explain:美 [ɪkˈspleɪn] 解释;说明;阐明;说明(…的)原因;解释(…的)理由

1.4、案例

1)数据表

testnd5

在这里插入图片描述

2)执行计划

explain SELECT * FROM testnd5

3)执行结果

在这里插入图片描述

4)结果分析

  • id

    当sql语句非常复杂的时候,会有一个id序号的排列,根据序号的排列能显示出来哪个子查询或者子句优先执行,哪个字句后执行,仅此而已,有时候需要看,有时候不需要看,它不是一个关键信息

  • select_type

    查询的类型(简单查询、联合查询、子查询),一般没什么用

  • table

    sql语句执行的表的名称

  • type

    很重要,表示查询对应的类型,mysql默认的是ALL

    有这几种

    • system
    • const
    • ref
    • range
    • index
    • all

    从前往后,效率依次降低,即system的效率是最高的,all要进行全表扫描,效率低,所以我们最少要保证type在range这个级别(通过加索引、调整当前子句…),达到ref更好但是某些情况是没法优化的,优化知识为了在一定程度上解决问题,并不是一定有解。可以优化但并不是优化完一定有效果

    官方文档地址
    https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain_type
    在这里插入图片描述

    在这里插入图片描述

    有的sql语句非常简单,sql语句越简单,优化的程度就越低

  • possible_keys

    可能用到的索引

    把可能用到的索引都列出来,意义不大

  • key

    很重要,表示当前的sql语句中到底有没有用到索引,这个值尽量不要为空

  • rows

    过滤的行数,只是预估值,不是精确值

  • Extra

    比较重要,表示额外的信息

    当出现的是using index:表示使用了索引覆盖
    using index condition表示使用了索引下推
    using filesort表示使用了临时空间进行排序,没有使用索引进行排序

2、索引介绍

2.1、什么是索引?

  • 索引是数据结构,可以高效地获取数据
  • 索引存储在文件系统中
  • 索引的文件存储形式与存储引擎有关
  • 索引文件结构
    • hash
    • 二叉树
    • B树
    • B+树 (MySql索引文件结构)

2.2、索引分类

  • 主键索引

    主键是一种唯一性索引,它必须指定为PRIMARY KEY,不能为空每个表只能有一个主键

    一个主键并非一定只有一个列,也可以是多个列组成的联合主键

    MySql会自动为主键创建索引

  • 唯一索引

    索引列的所有值都只能出现一次,即必须唯一,值可以为空一张表可以在不同的字段建多个唯一索引

  • 普通索引

    基本的索引类型,值可以为空,没有唯一性的限制

  • 全文索引

    全文索引的索引类型为FULLTEXT。全文索引可以在varchar、char、text类型的列上创建

  • 组合索引

    多列值组成一个索引,专门用于组合搜索

    又称:复合索引、联合索引

3、索引 增删查改

3.1、增加(创建)

  • ALTER TABLE

推荐

适用于表创建完毕之后再添加

alter [ˈɔːltər] 修改、更改

ALTER TABLE 表名 ADD 索引类型 (unique,primary key,fulltext,index) [索引名](字段名)

-- 索引名,可要可不要;如果不要,当前的索引名就是该字段名
ALTER TABLE `table_name` ADD INDEX `index_name` (`column_list`) 

ALTER TABLE `table_name` ADD UNIQUE (`column_list`) 
ALTER TABLE `table_name` ADD PRIMARY KEY (`column_list`) 
ALTER TABLE `table_name` ADD FULLTEXT KEY (`column_list`)
  • CREATE INDEX

适用于表创建完毕之后再添加

CREATE INDEX 可对表 增加 普通索引UNIQUE索引

-- 只能添加  普通索引 或 UNIQUE索引
CREATE INDEX index_name ON table_name (column_list) 
CREATE UNIQUE INDEX index_name ON table_name (column_list)
  • 建表时添加

不推荐

CREATE TABLE `test1` ( 
  `id` smallint(5) UNSIGNED AUTO_INCREMENT NOT NULL, -- 注意,下面创建了主键索引,这里就不用创建了 
  `username` varchar(64) NOT NULL COMMENT '用户名', 
  `nickname` varchar(50) NOT NULL COMMENT '昵称/姓名', 
  `intro` text, 
  PRIMARY KEY (`id`),  
  UNIQUE KEY `unique1` (`username`), -- 索引名称,可要可不要,不要就是和列名一样 
  KEY `index1` (`nickname`), 
  FULLTEXT KEY `intro` (`intro`) 
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='后台用户表';

3.2、删除

DROP INDEX `index_name` ON `talbe_name`  
ALTER TABLE `table_name` DROP INDEX `index_name` 
-- 这两句都是等价的,都是删除掉 table_name 中的索引 index_name

ALTER TABLE `table_name` DROP PRIMARY KEY -- 删除主键索引,注意主键索引只能用这种方式删除

3.3、查看

show index from `table_name`;

3.4、更改

删掉重建一个既可

3.5、创建索引的技巧

  • 维度高的列创建索引
    • 数据列中不重复值出现的个数,这个数量越高,维度就越高
    • 如数据表中存在8行数据a,b,c,d,a,b,c,d这个表的维度为4
    • 要为维度高的列创建索引,如性别和年龄,那年龄的维度就高于性别
    • 性别这样的列不适合创建索引,因为维度过低
  • 对 where,on,group by,order by 中出现的列使用索引
  • 对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键
  • 为较长的字符串使用前缀索引
  • 不要过多创建索引,除了增加额外的磁盘空间外,对于DML(数据库管理语言,如增、删、改)操作的速度影响很大,因为其每增删改一次就得重新建立索引
  • 使用组合索引,可以减少文件索引大小,在使用时速度要优于多个单列索引
  • 更新频繁,数据区分度不高的字段,不宜建立索引

4、存储引擎

MyIsam、InnoDB、Memory

Memory 底层数据结构为哈希表

比较MYISAMINNODB==(默认)==
索引类型非聚簇索引
数据和索引不在一起存储 (只存地址)
聚簇索引
数据和索引在一起存储
事务不支持支持
数据表锁定支持支持
数据行锁定不支持支持
外键约束不支持支持
全文检索支持支持(5.6及以后)
所占空间大,约2倍
底层数据结构B + 树B + 树
适合操作类型大量select大量insert、delete、update
  • 聚簇索引
    • 数据跟索引存储在一起
  • 非聚簇索引
    • 数据跟索引不存储在一起

5、索引 技术点

在这里插入图片描述

索引系统设计要点

1)索引应该存哪些信息

2)索引和数据存储位置

对于InnoDB

索引和实际的数据都是存储在磁盘上的,只不过在进行数据读取的时候会优先把索引加载到内存中

存储引擎:不同的数据文件在磁盘中有不同的组织形式

  • MyIsam存储引擎

    .frm 表结构

    .MYD 数据文件

    .MYI 索引
    在这里插入图片描述

  • InnoDb存储引擎

    .frm 表结构

    .ibd 索引文件+数据文件

    在这里插入图片描述

3)索引存储什么格式的数据?

K-V格式(键-值对)

类似于查字典,根据页数定位要查找的内容

4)选择合理的数据结构进行存储

为什么是B+数(为什么不是B树或者hash表)

当表非常大的时候,索引会不会一起变大?
因为往表里存数据的时候,是没法判断这个表能够存多少数据的,表中数据量在增大的时候,索引也在增大
索引在变大的过程中,没办法直接加载到内存怎么办?
可能内存只有8G,但是mysql数据的索引达到了16G,则可以分块读取,1G 1G地读,分而治之
尽可能多地提高IO效率
1:减少IO量
2:减少IO次数

在这里插入图片描述

6、操作系统基础知识

6.1、局部性原理

  • 时间局部性

    之前被访问过的数据很有可能再次被访问

  • 空间局部性

    数据和程序都有聚集成群的倾向

6.2、磁盘预读

内存跟磁盘在进行交互的时候有一个最小的逻辑单位,这个单位称之为页,或者datapage,一般是4kb或者8kb,由操作系统决定,我们在进行数据读取的时候,一般是读取页的整数倍,也就是4k,8k,16k;Innodb存储引擎在进行数据读取的时候读取的是16kb的数据

举例:如下图,我们可以看到,实际的文件大小是758字节,但是占用了4kb的大小(可以把磁盘看成一个一个的小格子,每一个格子都是4kb的大小,不管你有没有占满,都是4kb)

在这里插入图片描述

7、MySql 为什么选择B+树?

7.1、hash表

缺点:

  • hash存储需要将所有的数据文件添加到内存,浪费内存空间
  • 如果是等值查询,hash很快;但实际工作中范围(range)查找的更多,而不是等值查询,所以hash就不合适了
  • 哈希索引

简要说下,类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,也是对索引列计算一个散列值(类似md5、sha1、crc32),然后对这个散列值以顺序(默认升序)排列,同时记录该散列值对应数据表中某行的指针,当然这只是简略模拟图

在这里插入图片描述

比如对姓名列建立hash索引,生成hash值按顺序排列,但是顺序排列的hash值并不对应表中记录,从地址指针可反应出来,而且,hash索引可能建立在两列或者更多列上,取得是多列数据后的hash值,它不存储表中数据。它先计算列数据的hash值,与索引中的hash值比较,找到了然后比对列数据是否相等,可能涉及其他列条件,然后返回数据。hash当然会有冲突,即碰撞,除非有很多冲突,一般hash索引效率很高,否则hash维护成本较高,因此哈希索引通常用在选择性较高的列上面。哈希索引的结构决定了它的特点:

1. hash索引只是hash值顺序排列,跟表数据没有关系,无法应用于order by

2. hash索引是对它的所有列计算哈希值,因此在查询时,必须带上所有列,比如有(a, b)哈希索引,查询时必须 where a = 1 and b = 2,少任何一个不行;

3. hash索引只能用于比较查询 = 或 IN,其他范围查询无效,本质还是因不存储表数据;

4. 一旦出现碰撞,hash索引必须遍历所有的hash值,将地址所指向数据一一比较,直到找到所有符合条件的行。

7.2、二叉树

缺点:

  • 会因为树的深度过深而造成IO次数变多,影响读取效率

7.3、B树

缺点:

  • 会因为树的深度过深而造成IO次数变多,影响读取效率
    在这里插入图片描述

在这里插入图片描述

7.4、B+树

在这里插入图片描述

  • B+Tree每个节点可以包含更多的节点,这样做的原因有两个:
    • 为了降低树的高度,减少IO次数
    • 将数据范围变为多个区间,区间越多,数据检索越快
  • 非叶子节点存储key,叶子节点存储key和数据
  • 叶子节点两两指针相互连接(符合磁盘的预读特性),顺序查询性能更高
  • 在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:
    • 对于主键的范围查找和分页查找
    • 从根节点开始,进行随机查找
  • 一般情况下3-4层的B+树足以支撑千万级的数据量存储

在这里插入图片描述

  • Innodb是通过B+Tree结构对主键创建索引,然后叶子节点中存储记录,如果没有主键,选择唯一键,如果没有唯一键,选择6字节的row_id来进行存储

  • 如果创建的索引是其它字段(不是主键),那么在叶子节点中存储的是该记录的主键(不是数据),然后再通过主键索引查找对应的记录

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

MyIsam 叶子节点中只存储地址,不存具体记录数据,通过地址再查找数据

8、

8.1、常见名词

案例表:

id(主键)name(普通索引)age
1张三22
2李四25
3王五28

1)回表

如果创建的索引是其它字段(普通索引,不是主键),那么在叶子节点中存储的是该记录的主键(不是数据),然后再通过主键去 主键索引表 查找对应的记录

经过了两个表的查询:

  1. 普通索引表:查到主键
  2. 主键索引表:查询记录数据
-- name是普通索引,在普通索引表中存储的是主键id的值,即1
-- 要想得到记录的值,需经过二次查询
select * from table where name='张三'

2)索引覆盖

-- 一次查询就可以得到结果,不需要回表
select id from table where name='张三'
  • 根据name的值去name的B+树检索对应的记录,能获取到id的属性值,索引的叶子结点中包含了查询的所有列,此时不需要回表,这个过程叫做索引覆盖,会有using index的提示,推荐使用
  • 在某些场景中,可以考虑将要查询的所有列都变成组合索引 ,此时会使用索引覆盖(不会回表),加快查询效率。

3)最左匹配

-- id主键   (name,age)组合索引/联合索引/复合索引

-- 会用组合索引
select * from user where name = '张三' and age = '22';

-- 会用组合索引
select * from user where name = '张三';

-- 不会用组合索引,因为没有 name
select * from user where age = '22';

-- 会用组合索引,因为 age 和 name 都有,优化器会调整 age 和 name 的顺序
select * from user where age = '22' and name = '张三';

对于复合索引来说,不总是匹配所有字段列,但是可以匹配索引中靠左的列

4)索引下推

  • 把原来在server层进行的条件过滤下推到存储引擎层,索引下推是默认开启的
select * from user where name = '张三' and age = '22';
  • 没有索引下推前

    先根据 name 从存储引擎中拉取数据到 server 层,然后在 server 层中对 age 进行数据过滤

  • 有索引下推后

    根据 name 和 age,直接在存储引擎中做数据过滤,把结果返给 server 层

    可以减少返给 server 层的数据量

5)前缀索引

如果索引列长度过长,这种列索引时将会产生很大的索引文件,不便于操作,可以使用前缀索引方式进行索引;前缀索引应该控制在一个合适的点,控制在0.31黄金值即可(大于这个值就可以创建)。

-- 这个值大于0.31就可以创建前缀索引,Distinct去重复
SELECT COUNT(DISTINCT(LEFT(title,10)))/COUNT(*) FROM Arctic; 

-- 增加前缀索引SQL,将人名的索引建立在10,这样可以减少索引文件大小,加快索引查询速度
ALTER TABLE user ADD INDEX `uname`(title(10)); 

8.2、优化小细节

  • 当使用索引列进行查询的时候,尽量不要使用表达式,把计算逻辑放到业务层,减轻数据库运算压力

  • 尽量使用主键查询,而不是其它索引,因为主键索引不会触发回表查询

  • 如果 索引列 长度过长,可以使用前缀索引,否则会产生很大的索引文件,不便于操作

  • 使用索引扫描来排序

    参考:https://www.cnblogs.com/YC-L/p/14461561.html

    • mysql有两种方式可以生成有序的结果:通过 排序操作 或者 按索引顺序扫描

      • 如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序
      • 扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录
      • 但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行
      • 这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢

      mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务

      • 只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一样时,mysql才能够使用索引来对结果进行排序
      • 如果查询需要关联多张表,则只有当order by子句引用的字段全部为第一张表时,才能使用索引做排序
      • order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求

      否则,mysql都需要执行顺序操作,而无法利用索引排序

  • union all,in,or 都可以使用索引

    • 对于 索引列 最好使用 union all

      因为复杂的查询【包含运算等】将使 or、in 放弃索引而全表扫描,除非确定 or、in 会使用索引

    • 对于 非索引字段 用 or 或者 in,因为要全表扫描,而 union all 会成倍增加表扫描的次数

    • 对于既有 索引字段【索引字段有效】又包含 非索引字段,使用三者都可以,推荐使用 or、in

      参考:

      博客园:https://www.cnblogs.com/maohuidong/p/10478356.html

  • 范围列可以用到索引

    • 范围条件是:<、<=、>、>=、between
    • 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
  • 强制类型转换会全表扫描

    -- 数据库中的电话号码是  字符串形式(varchar),并建立了索引
    
    -- 全表扫描,不会触发索引;因为以 数值 的形式查询,强制进行了类型转换
    explain select * from user where phone = 13800001234;
    
    -- 触发了索引,不会全表扫描
    explain select * from user where phone = '13800001234';
    
  • 更新频繁,数据区分度不高的字段,不宜建立索引

  • 创建索引的列,不允许为null,可能会得到不符合预期的结果

  • 当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致

  • 能使用 limit 的时候,尽量使用 limit

  • 单表索引建议控制在5个以内

  • 组合索引中单索引字段在5个以内

  • 索引的正确概念:

    • 索引不是越多越好;索引也是要维护的,过多时会降低性能
    • 在不了解系统的情况下,不要过早优化

参考:

博客园:https://www.cnblogs.com/yyjie/p/7486975.html

博客园:https://www.cnblogs.com/itdragon/p/8146439.html

菜鸟教程:https://www.runoob.com/w3cnote/mysql-index.html

知乎:https://zhuanlan.zhihu.com/p/197884014

感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

土味儿~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值