MySQL优化从哪些方面谈?

前言

MySQL优化绝对是面试中必问的一个知识点,也是一个没有标准答案的问题。但是看问题的角度和对问题认知的维度确实是一个很具有考察性的问题。
了解MySQL的存储引擎、主从复制、读写分离、索引、分库分表、SQL调优这些知识以后,对这个问题建立一个比较完整的知识体系才能回答好。
在这里插入图片描述

存储引擎

存储引擎其实本质上来说是一种数据存取的方案。MySQL现在主流的存储引擎由两种,InnoDB和MyISAM。

MyISAM

1、MyISAM在设计的时候目标就是提供一个快速读取数据的方案。它是不支持事务的,并且也不支持外键关联。
采用的也是一个非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。
2、因为使用的是表锁,在更新删除操作的时候会锁表。
3、MyISAM的快速读取数据性能适用于一些写操作少,读操作多,数据计算等场景。

InnoDB

InnoDB是目前使用的比较多的一种存储引擎。它的设计出发点是在处理大量数据的时候能提供一种高效的解决方案。
1、它最大的特点是支持事务,也就是在一些报错需要回滚的业务中能提供很好的事务支持,也就是对于数据一致性、完整性要求高的系统中使用是很合适的。
2、InnoDB是能提供行级别的锁,那么对比锁表与锁行,InnoDB能提供更大的并发度支撑。
3、默认主键自增,并且在建表的时候由主键使用B+树生成一个聚集索引。也就是这个B+树上的叶子节点上就存储了这个行的数据。也支持其他非主键字段组合成非聚集索引,提升查询效率。

主从复制

简单盘点一下,如果MySQL配置了集群化的主从复制会有什么好处:

  • 读写分离,将读操作交给从机,写操作交给主机,之后再由主机将数据同步到各个从机;
  • 数据分布,备份,有了更多的操作性;
  • 故障切换和高可用,数据库得到保障提升;
  • 将数据备份的工作交给从机来做…

实现

MySQL是一种不用借助于其他中间件就能完成主从(Master-Slave)复制的数据库,采用的是一种二进制文件的办法去实现集群之间数据的一致性。

  • Master将操作记录到二进制文件中,也就是对数据的操作会形成一次二进制形式的日志事件。
  • Slave首先把自己的日志拷贝到缓存中,Slave开启一个工作线程,连接Master,然后读取Master中日志信息,如果发现自己的日志跟Master的是一样的,就代表数据已经同步,之后这个线程就会睡眠,等待Master的一次新的日志事件触发。
  • Slave发现不一样就把从Master上读取到的操作记录在自己本地执行,保证与Master上的数据一致。

数据库层面的主从复制完成之后,就可以在项目中配置多个数据源,对于不同的操作就有了不同的数据源作为可选项。

数据表的优化

水平、垂直拆分

垂直拆分
把系统按照功能模块拆分成为多个数据库,尤其是在微服务化的框架中,垂直拆分应该是最常见、最实用的一种数据表优化方案。拆分之后系统边界清晰、各个业务模块内部聚合,对外则是一个单一的功能、系统模块之间的耦合减少,降低数据库压力。

但是在涉及到一些分布式事务的场景则显得力不从心,也就是单个业务涉及两个以上数据库的读写操作,在事务回滚上目前没有非常标准的解决方案。(目前比较成熟的解决方案有:二阶段TC提交、TCC补偿机制、借助于MQ的消息通知)

水平拆分
水平拆分针对的是大数据表的拆分。有时候会有两种情况。
一种是数据表的字段过多,这样在数据行写到磁盘页上的时候就会跨页,这就会增加一次IO操作,但是这种情况比较少见,在设计数据表字段结构的时候是完全可以避免的,也比较少。
另外一种则很常见,就是一张表的数据量过于庞大,选择将表中的所有记录拆分到相同表结构的多个表中,这些表可能不在一个数据库里,也可能在同一个数据库里。
在这里插入图片描述
怎么拆呢?最理想的方案是分布式ID跟取模随机分配数据。

分布式ID生成器——雪花算法

在水平拆分的过程中,需要一种全是数字作为数据表主键的方案来配合,并且这个ID在系统中是唯一存在的。

为什么是自增数字?
数据表的索引最后都是以B+树的形式存在,假设不是自增数字,而是随机的两个UUID,那么在B+树的叶子节点上,两个原本相邻的数据在叶子节点上就是一个天涯海角的距离,因为B+树是会对索引字段进行排序的。而当查询去走索引的时候,就会进行多次不必要的遍历操作,影响查询的效率。
而自增数据作为主键就不一样了,底层的链表完全是按照顺序放置的。在查询的时候,只需要找链表的后面几个节点即可。

为什么不使用数据库的自增策略?
因为在系统中不唯一,当分库分表之后,采用数据库自带的自增策略,会有很多个表的id一致,在系统中作为参数传递的时候不容易辨别。

雪花算法
在这里插入图片描述
雪花算法是推特公司的一套生成分布式ID的服务。最初Twitter把存储系统从MySQL迁移到Cassandra(Facebook开发的一套分布式NoSQL数据库系统) ,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一的ID生成服务。SnowFlake能每秒产生26万个自增可排序的ID,并且满足能够按照时间有序生成。

因为Java语言中long类型的最大长度是64位,并且大多数CPU的缓存行也是64位。(Intel)

  • 1位符号位 表示正数
  • 41位时间戳,也就是最大能代表的时间是41个1,大约是69年。Java语言的时间是从1970年开始,所以雪花算法大概能标识从1970年到2039年9月7日的时间戳
  • 10位工作进程单位分为5位机房编码和5位机器编码,那么就是2的10次方,也就是支持由1024个节点构成的集群
  • 12位序列号位 同一个毫秒之内产生的不同id,2的12次方就是4095个id,那么就是一毫秒内一个机器可以产生4095个id

雪花算法是一种可靠的分布式ID生成算法,不依赖于第三方,性价比很高,也可以根据业务灵活分配比特数位。
但是对于一些情况,比如:时钟回拨会导致id重复生成,时钟不同步会导致集群整体的id不是完全绝对自增的,只能说是趋势递增。

有了雪花算法再来看数据表的水平拆分,假设现在需要将一张数据量很大的表拆分成三张子表,就对3取模就好了,然后结果不同就将数据存入到不同的表中。
对于水平拆分还有很多种方案,个人觉得这是最靠谱的一种办法。

拆分之后的数据查询

  • 这是一个业务痛点,假如拆分之后的表在同一个库下,那么可以使用UNION、UNION ALL来连接两个不同的表。
    UNION 操作符用于合并两个或多个 SELECT 语句的结果集。UNION 内部的 SELECT 语句必须拥有相同数量的列,列也必须拥有相似的数据类型,同时,每条 SELECT 语句中的列的顺序必须相同。
    UNION 不包括重复行,同时进行默认规则的排序,UNION ALL 对两个结果集进行并集操作,包括重复行,不进行排序;

  • 但是如果是在不同的数据库中,可以配置多个数据源去做查询的统一定义,自定义注解。

  • 在不同数据源中的查询也可以在每一个库都查询出来之后在业务代码里面(Java8的Stream流很香啊)计算拼接想要的结果集合。

  • (或者有什么更好的方案我不知道)

索引

给数据表的字段添加索引的目的就是为了高效的查询数据库中的数据

  1. 索引能很有效的减少扫描磁盘的次数,索引像一个字典的目录一样把数据进行数据的区间提前找出来。
  2. 索引能帮助快速排序和避免临时表的生成。当一个带排序的查询语句交给MySQL执行的时候,MySQL会把数据都加载到内存中,然后按照排序字段生成一张临时表。如果这个临时表的默认大小超过了16M,那内存的临时表就会转变为磁盘的临时表,这时候性能会很差。由于索引本身就是有序的,那么从磁盘读取到的数据也就是有序的,也就不用去创建临时表。
  3. 索引能将随机IO变成瞬息IO。数据是存在了每一个磁盘页的扇区上,如果没有索引,数据就会是随机的分散在各个扇区的不同磁道上。那么由于磁盘IO的瓶颈就会影响这次查询的效率。
    有索引的时候就不一样,因为数据在磁盘页上是连续的,在读写的时候就变成了顺序IO。

数据库索引的本质其实说的是数据结构,其实大部分情况下说的是B+树。

B+树索引

全网找下来觉得这是一篇写B+树写的比较好的文章。https://mp.weixin.qq.com/s/oVpiU16PCHQhHr00G3OK-g
首先,B+树的树高不是很高,在非叶子节点上存储的都是数据的区间,并且在最底层的叶子节点上才存储数据信息。在插入和删除的时候就会对这个B+树进行维护,保证有序,从而在查询的时候能极大的提升效率。
当叶子节点存放的完整的行记录数据的时候就成为聚集索引,MySQL中唯一的一个聚集索引是由主键生成的。
如果叶子节点存放的不是完整的行记录数据,那么这个索引就叫做普通索引 或者 非聚集索引。

思考一个问题,假设数据库的主键不是数字自增,会发生什么情况?
也就是在新记录插入的时候,很有可能是随机的插入到B+树叶子节点的一个随机位置,那么整个B+树都要以为这个数据的加入而改变,需要调整为平衡树。这个过程就叫做页分裂与页合并
为了避免这样的情况,我们更加希望插入的新数据对整个B+树的影响不大,所以用雪花算法来生成数据的主键是一种非常可靠的解决方案。

Hash索引

MySQL中,除了B+树索引之外,还有Hash索引。Hash索引是一种哈希表的实现,通过key值进行哈希算法之后就能很快的找到value的值,在value里面一般就存储了指向数据行的指针。
这种索引结构查询起来比较快,也很紧凑。但是缺点也很明显,就是区间查找,排序,分组这些情况就不支持了。

回表

找下来,这应该是最易懂的图了。
在这里插入图片描述
首先左边是一个由自增主键构成的B+树索引的图,可以看到在叶子节点上存放的是这行记录。而右边则是一个普通索引构成的索引B+树,叶子节点只有这个记录的主键,而不是全部的行数据信息。

假设右边是用a字段构成的索引,但是查询的时候却需要查出整个表的信息。很明显,从右边的B+树上是得不到所有的字段信息的,所以会在得到主键之后,走一遍左边的索引树,把查询列表中的 * 补全。这个过程就叫做回表。
虽然索引帮助避免了全表的扫描,但是在写SQL语句的时候,也要尽量避免回表次数过多。(所以 select * 这种语句只有在开发的时候会写,在生产上是不可能使用的)

覆盖索引

假设是用a,b,c三个字段建立索引,而在写SQL语句的时候,查询列表只有a,b,c其中的几个,也就是索引字段把查询列表的字段全部覆盖了,这样的索引就叫做覆盖索引,能避免查询之后字段不全而导致的回表操作。

索引选择性

建立索引,能帮助在查询数据的时候极大的提高查询的效率,但是索引建立的不好,区分度不大,也会导致维护的这个B+树的成本太大,反而得不偿失。怎么样选择索引字段呢?
如果一个字段,在整个表中不重复的值与整个记录数的比值越高,则越适合做索引。
比如数据表的主键,整个表不重复的主键数目就是整个表的行数,比值是1。

select  count (distinct colum_a) / count(*) as  a   from table_test
# 查找 colum_a 字段在整个table-tast表中 不重复记录数占整个表记录数的比例,比值越高 越适合作为索引字段

前缀索引

假设要对长字符串建立索引,最优的办法是选择字符串的前几位去建,而不是对整个长字符串建立。这种索引就叫做前缀索引,有效的避免索引太大的问题。那么选择多少个字符长度呢?
同样的是使用 索引选择性的理论,运用以下的简单SQL就能解决,结果一看便知,但是也要考虑太长导致索引太庞大的问题,当选择区分度不大的时候就没有必要增加长度了。

select 
count (distinct left(colum_str,3)) / count(*) as char_3,
count (distinct left(colum_str,5)) / count(*) as char_5,
count (distinct left(colum_str,7)) / count(*) as char_7,
from table_a

三星索引

如何判断一个索引是好是坏?如果一个查询满足三星索引中三颗星的所有索引条件,理论上可以认为我们设计的索引是最好的索引。
第一颗星:where 后面参与查询的列可以组成了单列索引或联合索引。
第二颗星:避免排序,即如果 SQL 语句中出现 order by colulmn,那么取出的结果集就已经是按照 column 排序好的,不需要再生成临时表。
第三颗星:select 对应的列应该尽量是索引列,即尽量避免回表查询。

窄索引片
第一条,实际就是让扫描的数据尽量小。也就是在where字句后面的查询列能尽量作为索引字段。

避免排序
第二条,因为索引本身就是有序的,如歌一个排序字段没有建立索引,那么就会生成临时表,所以把order by的字段作为索引列。

宽索引
其实就是避免回表操作。


索引失效的场景

盘点一些比较经典的索引失效场景。
1、索引列进行了计算
使用了含有表达式或者函数 select id from table_a where id + 1 = 5

2、索引发生了类型转化
MySQL在对数字字符的处理的时候会进行自适应的转换,这种是比较隐式的转换。
比如table_a的 age字段在建表的时候是字符串。但是SQL语句这样写就会导致索引失效。
select age from table_a where age = 15

3、多表的字符集不一致
在多表查询的时候,由于表的字符集不是一样的,也会导致索引失效。

4、模糊匹配

#索引字段是  name
#会导致失效  select age from table_a where name  like  '%xxx%'
#不会失效    select age from table_a where name  like  'xxx%'

失效的场景还有很多,但这几个是最经典的。

最左前缀原则

table_a的索引字段是 a,b,c (注意顺序)

# 不失效的场景
select a,b,c from table_a where a='x' and b='y' and c='z'
select a,b,c from table_a where a='x' and b='y' 
select a,b,c from table_a where a='x' 

# 会失效的场景
select a,b,c from table_a where b='y' and c='z'  (带头大哥死)
select a,b,c from table_a where a='x' and c='z'  (中间兄弟掉队)

全值匹配我最爱,最左前缀要遵守。带头大哥不能死,中间兄弟不能掉。索引列上少计算,范围之后全失效
Like百分写最右,覆盖索引不写星。不等空值还有or, 索引失效要少用。字符引号不能丢,SQL高级也easy

----------从周阳老师哪里学来的

SQL写的好不好

explain +SQL 能对这个SQL的执行情况进行分析。
(explain、问题排查、SQL执行顺序)移架另外一篇文章查看详情。
https://blog.csdn.net/XiaoA82/article/details/88852999

猜你喜欢

转载自blog.csdn.net/XiaoA82/article/details/107207876