MySQL性能优化

在这里插入图片描述

MySQL性能优化


影响MySQL性能的因素

要知道如何优化MySQL得先知道影响MySQL性能的因素有哪些

  • 商业需求对性能的影响
  • 系统架构及实现对性能的影响
    • 过度依赖sql语句。能在应用层(代码)优化就不要在MySQL才优化
    • 使用cache不合理
    • 在mysql中存放不合理的数据。该放在其他数据库就不要放在mysql中,如日志记录应放在mongodb中
  • 硬件环境对系统性能的影响
  • Query查询语句对系统性能的影响。包括:避免全表查询、合理使用索引、减少无效数据的查询
  • 合理的需求和架构及业务。需求发起者可能不了解mysql性能会有什么影响,在确定需求时应该衡量这点,架构的选择和业务流程的代码也是很重要的

需求和架构以及业务占55%

sql优化占30%

数据库自身优化占15%


为什么要对MySQL优化?为了优化MySQL,给老板省钱的最好办法就是优化SQL

优化成本:硬件>系统配置>数据库表结构>SQL及索引。

优化效果:硬件<系统配置<数据库表结构<SQL及索引。


Query语句的执行流程

sql请求到MySQL,首先通过连接管理模块进行权限检查,接着到连接进程模块申请一个线程,然后由命令分发处理器会将命令记录在日志模块中记录命令日志、如果开启二级缓存了也会将命令分发至二级缓存中,接着分发到命令解析器解析命令,最后由其执行Query语句


MyISAM和InnoDB的区别
特点MyISAMInnoDB
事务不支持支持(ACID)
表锁表锁、行锁
磁盘文件.frm:表结构/表定义
.MYD:表数据
.MYI:表索引
.frm:表结构/表定义
.idb:表数据、表索引
存储限制无限制64TB
外键不支持支持

索引

索引就像字典里的目录,能帮助我们更快地查询数据。用官方话来说就是:高效获取数据的数据结构

索引独立于原数据,每个索引都将是额外的开销。


索引的数据结构

索引支持的数据结构有Hash和B+Tree,但是MySQL中不提供Hash给用户使用,即便是提供选择,都将默认选择使用B+Tree作为索引的数据结构,Hash是MySQL用于优化其他方面的性能的


Hash

Hash结构适合命中性地搜索,不适合范围式的搜索,而我们业务几乎不会碰到存在selectOne而没有selectList的情况,因此MySQL只提供B+Tree作为数据结构供选择。另一方面,Hash是无法进行排序的


B-Tree

先来了解B+Tree的优化前身B-Tree(读作B树,不读作B减树)

在这里插入图片描述

从图中可以看出,B树的每个节点都存储了元素数据(data)及元素值(17、35),而非叶子节点还存储了元素导航指针(Pn)。所谓n阶树就有n个元素值,n+1个元素导航指针

元素导航指针:如P1、P2、P3分别存储的是指向<17、<35且>17、>35的元素的磁盘位置


树的搜索过程:

每个节点都是使用数组存储数据,因此每个节点内的元素值遍历都是使用二分法查找。当Query发起查询的时候,第一次IO遍历第一层即根节点的数据,节点内使用二分法查找,如果找到数据则返回,否则根据条件继续向下IO操作

那B树有什么缺点导致出现优化后的B+树呢?

每个索引树(无论B+树或B树)的IO次数都是O(log2 n)次,因此树的高度决定了IO操作的次数,所以我们要尽量保证树的高度越矮越好。而一个节点在磁盘中就是一页数据,也即一次IO操作。而一个节点默认存储量是16384bit即16kb,如果我们将每个节点最合理使用的话,也即尽可能多的存储元素导航指针是不是可以降低树的高度了?


B+Tree

B+树将所有元素数据都存储到了叶子节点上,叶子节点是双向链表结构,存储了指向邻磁盘的元素导航指针,这也是优化的一点,便于范围查询。在这里插入图片描述
在这里插入图片描述

根节点存储的只有元素值,并且MySQL都将其存储在了内存中,方便查询

普通节点存储了父节点的元素值和指向子节点的元素导航指针

来用数据说明B+Tree的魅力

每个元素值占8bit、元素导航指针占6bit,那么一个节点大概能存储16384/14≈1200个元素

假设树有三层,那第一层就有1200个元素,第二层有1200*1200≈144w个元素

假设第三层每个元素存储的数据平均撑死也就1kb,那第三层就有1440000*16≈2kw条数据,也就是说,仅三层,2kw条数据最多只要3次IO操作就能得到结果

B-Tree和B+Tree的区别
  • B-Tree每个元素值都对应着一条数据,B+Tree将所有数据存储在叶子结点,其他节点腾出空间主要存储元素值
  • B-Tree不适合范围查找数据,而B+Tree的叶子节点存储了邻节点的指针,方便范围查找

索引的类型
主索引(一级索引、主键索引)

主索引是MySQL默认设置的。如果表中有主键则默认选其为主索引,如果没有主键则默认选择其中一个非空唯一列作为主索引,如果条件都不成立则默认创建一个独立于列的主索引

因为MyISAM和InnoDB存储数据和索引的文件类型不同,因此它们的主索引有些区别

MyISAM:其索引的叶子节点存储的是数据文件.MYD的地址指针,因此它需要比InnoDB多一步,再根据地址指针到.MYD文件中找到对应的元素数据

InnoDB:索引和数据同文件,找到元素数据即是元素数据


聚簇索引和非聚簇索引

聚簇索引就是索引与元素数据存储在一起,反之就是非聚簇索引。InnoDB的主索引使用的是聚簇索引(但不能说聚簇索引就是主索引),MyISAM的主索引使用的是非聚簇索引

严格来说聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于其实现方式。


辅助索引(二级索引)

辅助索引是我们手动设置的。其叶子节点存储的是其所在列的值和主索引的值

辅助允许对应的列为空,辅助索引B+Tree会额外维护一个个元素为null的链表(维护需要额外开销,不建议将辅助索引用在允许为空或者存在空值的列中),该链表存在于叶子节点的头节点中,Query的时候,查找空值的数据都将额外在该空值链表中从头遍历


回表和索引覆盖

由于辅助索引存储的数据只有列值和主索引值,因此使用辅助索引的时候都会查询到主索引值再去主索引中Query找到元素数据,这个过程就称作回表(这是InnoDB的情况,MyISAM回表之后还要再根据地址指针跨文件获取元素数据)

为解决回表问题,从回表的特性就可以得出结论——使用辅助索引的时候避免非索引的查找。就比如我们只select两个列,主索引和辅助索引所在的列,但是这是不可能滴,因为业务没有这么纯粹,因此我们可以使用联合索引解决这个问题


单值索引

单值索引就是一个列,非单值索引即联合索引

联合(复合、组合)索引

联合索引即选择多列作为索引。用于当Query操作时的列大概率甚至一直会作为同时存在的条件进行查询。前面说到,使用联合索引就是实现索引覆盖,避免回表影响性能。


最左原则

这是使用联合索引必须考虑的问题。因为复合索引的排序规则由最左边即第一个索引决定的(后面其他索引无序),因此如果where后面没有第一个索引的话,这个联合索引将失效,具体看下面的例子。

以index(a,b,c)复合索引为例:

语句索引是否生效
where a = 1是,字段 a 索引生效
where a = 1 and b = 2是,字段 a 和 b 索引生效
where a = 1 and b = 2 and c = 3是,全部生效
where b = 2 or c = 3
where a = 1 or d = 3
where a = 1 and b > 2 and c = 3字段 a生效,字段 b 和 c 失效
where a = 1 and b like ‘xxx%’ and c = 3是,全部生效
where a = 1 and b like %xxx’ and c = 3字段 a 生效,字段 b 和 c 失效
where a = 1 and b like %xxx%’ and c = 3字段 a 生效,字段 b 和 c 失效
where a + 1 = 2 and b = 3字段 a 失效,字段 b生效
where a between 1 and 100是,字段 a 索引生效
where a in (1,100)是,字段 a 索引生效

从上表中可以得出“导致索引失效的原因”有:

  • 复合索引不满足最左原则
  • 使用函数将失效(包括+、-、*、/,建议将函数运算后的列作为一个新列然后建立索引)
  • 存在左模糊查询(%xxx[%]后缀匹配)将失效
  • <、>、%模糊查询会导致其后面的索引无论什么原因都失效
  • mysql会将between转成in语法,in语法是等值匹配,区别于模糊匹配

索引使用建议

由于索引需要存储在磁盘空间,DML操作都要维护索引树,虽然DQL性能提高,但是业务是不可能仅操作DQL的,因此建议

  • 一张表建议使用索引个数5个(一张表最多可以建立16个索引)

  • 非频繁查询条件的字段不建议建立索引

  • 索引选择性不高的列不适合作为索引(索引的选择性是指,不重复的索引值和数据表的记录总数(T)的比值,范围从1/T 到 1 之间。如性别列,这种一只手都能算出各种值在表中所占比)

  • 更新频繁的字段不适合建立索引

  • 不会出现在where语句中的字段不适用建立索引

  • 索引所在字段不应太长(每个节点存储的元素少,会导致索引树高度变高)

  • 能使用联合索引就尽量不用多个单值辅助索引

  • 能使用一个联合索引覆盖就不使用多个联合索引

  • 使用非业务的自增列作为索引

补充:

索引选择性。由于索引选择性低,因此索引树存在很多重复的元素值,这会导致叶子节点的链表中相同元素值集中分布在某块区域,辅助索引的索引树在进行范围性搜索的时候得到的都是主索引id值,而这些id值是无序的(根据辅助索引列排序的),回表之后就不能像辅助索引那样范围性搜索了,而是变成一条条地无序搜索。

有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当**前缀长度合适(注意一定要兼顾好列长度)**时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。

以city_demo表为例,表中存在city的列,如果全city的索引选择性太低,可以尝试使用部分前缀

#计算长度
-- 查询重复次数最多的1000条完整城市名称及其数量
SELECT COUNT(*) cnt, city FROM city_demo GROUP BY city ORDER BY cnt DESC LIMIT 1000;
-- 查询重复次数最多的1000条城市名称(前3个字符)及其数量,可以发现:前3个字符的相同数量过大,不适合做前缀索引
SELECT COUNT(*) cnt, LEFT(city,3) pref FROM city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 1000;
-- 查询重复次数最多的1000条城市名称(前7个字符)及其数量,可以发现:前7个字符的相同数量和完整城市名称很相近了,可以考虑作为做前缀索引
SELECT COUNT(*) cnt, LEFT(city,7) pref FROM city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 1000;

为什么强烈建议使用非业务的自增列作为索引:

业务列通常都是varchar类型的,而一般索引的大小都得控制在8bit也就是Integer及以下,太多长的话每个节点存储的元素少,会导致索引树高度变高,IO频率太高。

自增列作为索引建立索引树的时候,每次新增节点时需要在磁盘空间申请页空间作为新节点,当节点空间溢出就会再申请页空间作为新节点,这是一个从无到有的过程。而使用非自增列作为索引建立索引树时,因为插入元素不是根据递增排序的,也就是无序插入,这样出现新节点的时候,由于索引树的特性,可能此时新旧节点中的元素都没有占满,这就导致了节点空间中存在浪费的情况,浪费的多创建的节点也会更多,CPU性能会下降,由于全表扫描也会由于节点分裂太多而导致IO次数变多。而自增索引树除了最后一个节点都是满内容的,其空间利用率是最高的,因此节点数也是最少的,树的高度相比之下最低

用自定义Integer类型的自增id类最好。前面虽然没有说非得顺序紧凑递增,但是从上面也可以分析出,同等范围,id散列越好越能保证Integer的可用性


explain执行计划

使用explain语法可以查看Query的执行情况从而有根据性地进行sql调优

#用法
explain select * from table where id = 3

其结果生成一个表,其中的列表示的意思如下表所示

信息描述
id查询的序号,包含一组数字,表示查询中执行select子句或操作表的顺序 两种情况 id相同,执行顺序从上往下 id不同,id值越大,优先级越高,越先执行
select_type查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询
1、simple ——简单的select查询,查询中不包含子查询或者UNION
2、primary ——查询中若包含任何复杂的子部分,最外层查询被标记
3、subquery——在select或where列表中包含了子查询
4、derived——在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表中
5、union——如果第二个select出现在UNION之后,则被标记为UNION,如果union包含在from子句的子查询中,外层select被标记为derived
6、union result:UNION 的结果
table输出的行所引用的表
type显示联结类型,显示查询使用了何种类型,按照从最佳到最坏类型排序
1、system:表中仅有一行(即系统表)这是const联结类型的一个特例。
2、const:表示通过索引一次就找到,const用于比较primary key或者unique索引。因为只匹配一行数据,所以如果将主键置于where列表中,mysql能将该查询转换为一个常量
3、eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于唯一索引或者主键扫描
4、ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体
5、range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是where语句中出现了between,in等范围的查询。这种范围扫描索引扫描比全表扫描要好,因为它开始于索引的某一个点,而结束另一个点,不用全表扫描
6、index:index 与all区别为index类型只遍历索引树。通常比all快,因为索引文件比数据文件小很多。
7、all:遍历全表以找到匹配的行
注意:一般保证查询至少达到range级别,最好能达到ref。
possible_keys指出MySQL能使用哪个索引在该表中找到行
key表示MySQL实际决定使用的字段(索引)。如果没有选择索引则是NULL。查询中如果使用覆盖索引,则该索引和查询的select字段重叠。
key_len表示索引中使用的字节数,该列计算查询中使用的索引的长度在不损失精度的情况下,长度越短越好。如果键是NULL,则长度为NULL。该字段显示为索引字段的最大可能长度,并非实际使用长度。
ref表示索引命中的列或者常量Const,比如有些联表查询的时候驱动表会使用on中的列,被驱动表就会使用Const,通俗地讲就是where后的条件筛选或on中,对应的实际决定的key中的取值,为NULL可能表示对索引key使用了between或on
rows根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数
Extra包含不适合在其他列中显示,但是十分重要的额外信息
1、Using filesort:说明mysql会对数据适用一个外部的索引排序。而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作称为“文件排序”
2、Using temporary:使用了临时表保存中间结果,mysql在查询结果排序时使用临时表。常见于排序order by和分组查询group by。
3、Using index:表示相应的select操作用使用覆盖索引,避免访问了表的数据行。如果同时出现using where,表名索引被用来执行索引键值的查找;如果没有同时出现using where,表名索引用来读取数据而非执行查询动作。
4、Using where :表明使用where过滤
5、using join buffer:使用了连接缓存
6、impossible where:where子句的值总是false,不能用来获取任何元组
7、select tables optimized away:在没有group by子句的情况下,基于索引优化Min、max操作或者对于MyISAM存储引擎优化count(*),不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
8、distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。


举个栗子

在这里插入图片描述

补充:还可以使用show profiles语法查看本次查询消耗的时间、IO次数等详情。

#用法
explain select * from table where id = 3
show profiles

举个栗子
在这里插入图片描述


Query语句优化

Join算法

Simple Nested-Loop Join算法,也叫SNL算法

普通join语法就是使用SNL算法,联表查询需要经过count(table1)*count(table2)次行联匹配,也就是在table1全表查询的每一条数据再到table2全表查询匹配

SNL

Index Nested-Loop Join算法,也叫INL算法

其实就是往被匹配的内层表/驱动表的关联的(join on后面的消除笛卡尔积的条件)列添加索引,这样主动匹配的外层表/被驱动表查询之后,每一个条件到table2都将使用索引进行log2 n 次遍历。因此联表查询只需要经过count(table1)*(log2 count(table2))次行联匹在这里插入图片描述

INL

Block Nested-Loop Join算法,也叫BNL算法

大致思想就是建立一个缓存区join buffer,一次从驱动表中取多条记录,然后扫描被驱动表,被驱动表的每一条记录都尝试与缓冲区中的多条记录匹配,如果匹配则连接并加入结果集。缓冲区越大,驱动表一次取出的记录就越多。这个算法的优化思路就是减少内循环的次数从而提高表连接效率。

通过join buffer缓存,只需要经过count(table1) + (count(table1) / size(join buffer)) * count(table2) 次行联匹配,并且缓存的匹配操作比IO匹配操作更快

在这里插入图片描述

注意:join的所有算法要保证小结果集驱动大结果,其效率比大结果集驱动小结果集更快,所谓大结果集和小结果集就是count(table)的大小。

做个比较看一下就明白了:假设table1有50条数据,table2有100条数据,join buffer固定大小为20。按照BNL算法,小结果集驱动大结果集的匹配次数=50+50÷20x100=300,大结果集驱动小结果集的匹配次数=100+100÷20x50=350,当join buffer的容量越大时差距越明显。

另外,MySQL中不会因为join的先后顺序决定哪个是驱动表哪个是被驱动表,它会根据结果数而决定,少的作为驱动表,多的作为被驱动表。

另外,不仅是join,where和inner join(全内连接查询)都是跟join一样的特点。


Order by的单双路排序

当对sql进行order by排序的时候,需要尽可能的使用索引排序,如果无法使用索引排序的话,mysql就会使用文件排序。

文件排序出现的几种情况:

  • order by 字段不是索引字段

  • order by 字段是索引字段,但是 select 中没有使用覆盖索引,如:select * from employee order by age asc;

  • order by 中同时存在 ASC 升序排序和 DESC 降序排序,如:select weight, height from employee order by weight desc, height asc;

  • order by 多个字段排序时,不是按照索引顺序进行 order by,即不是按照最左前缀法则,如建立索引index(weight, height ),sql:select weight, height from employee order by height asc, weight asc;

文件排序(FileSort)分为两种:

  • 双路排序(又叫回表排序模式):先根据相应的条件取出相应的排序字段和可以直接定位行数据的行ID,然后在sort buffer中进行排序,排序完后需要再次取回其它需要的字段,即一次顺序读和一次回表乱序读;
  • 单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序随即返回结果集,仅一次顺序读;

显然单路排序比双路排序高效,但是什么原因会造成双路排序呢?

MySQL根据max_length_for_sort_data变量来确定使用哪种排序,其默认大小1024bit(可以在MySQL的my.ini文件中的[mysqlId]节点下更改max_length_for_sort_data的默认值),如果返回的列的总长度大于max_length_for_sort_data默认值,就会使用双路排序,这也印证了为什么要避免使用select *(甚至有些公司明令禁止使用select *)


Group by优化理念跟Order by是相同的,不再赘述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

肥肥肥柯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值