MySQL 性能优化总结

本小妞迷上赌 提交于 2019-12-02 10:53:54

一、影响mysql的性能因素

  • io: 磁盘IO、随机IO、网络IO
  • 扫描行数
  • 内存、临时表、key cache、query cache
  • cpu:排序、分组查询、distinct查询
  • 事务导致的表锁等

一条慢sql查询消耗的性能 > 1000条快查询消耗的性能。性能优化归根结底就是查询语句的优化,如何写出高效的查询语句呢?
对mysql的索引结构要有一定深度的了解,才能更好的写出高效的查询语句。

二、b+ tree结构索引的特点

  • b+tree 适应于全键值、键值范围或左前缀匹配查找
  • 支持order by排序
  • b+ tree 索引不支持跳过索引中的列查找

三、hash结构索引特点

hash索引是基于hash表实现。

  • 全值匹配、只有全键值精确匹配才可以查到,也就是说只有等值比较,如:=、in
  • 不支持范围、部分匹配查找
  • 不支持排序
  • 当hash冲突较多的话,索引维护代价较高,查询性能也会受到影响。

四、聚簇索引的select,没有union和子查询

MySQL innodb存储引擎是基于聚簇索引构建而来,一般是基于主键id构建的b+tree索引和数据行而来,
如果表不存在主键,那么mysql会自动选择其他的非空的索引字段;如果非空索引字段不存在,而mysql内部会自动生成内部的唯一标识作为主键构建聚簇索引。

  • 插入速度严重依赖于插入顺序。当主键不是数值自增长类型,而是varchar类型,则主键的随机性很大,会导致严重的页分裂。
  • 更新聚簇索引的代价很高。主键一般是不更新的。
  • primary查询中包含复杂的字部分
  • subquery包含子查询
  • union是指有union查询

五、explain分析

是不是explain分析出来,命中索引,扫描行数越少,就不是慢查询呢?

答案:未必。

explain分析出来的是查询的预估值,并不能代表实际的查询性能。mysql通常只是告诉我们生成结果时扫描了多少行数据,而不是实际扫描了多少行数据。
实际查询中受到很多因素的影响,了解索引结构是非常重要。以下我们先剖析以下explain的用法:
在这里插入图片描述

select_type
select类型,它有以下几种值:

  • simple它表示简单的select,没有union和子查询
  • primary查询中包含复杂的字部分
  • subquery包含子查询
  • union是指有union查询

table
查询所用到的表

type
常见类型如下:all、index、range、ref、eq_ref、const、system、null,性能从左到右逐渐提升。
1.all :全表扫描
2.index:只遍历索引树,比全表扫描稍微好一点。
3.range:范围查询
4.ref:使用非唯一索引或者唯一索引的前缀扫描
5.eq_ref:使用唯一索引匹配最多一条数据
6.const、system:是匹配常量。当只有一条数据是使用system。
7.null:性能最高,查询直接在索引上搞定,不需要回表操作。

possible_keys
是指查询字段涉及的索引,但索引实际不一定被使用。

key
显示mysql查询实际使用的索引。如果显示为null,则没有使用索引。

key_len
索引字段最大的可能长度,并非实际长度。

ref
表示表的连接匹配条件,就是那些列被使用索引上查找。

rows
mysql估算找到结果需要扫描的行数,并非实际的行数。

Extra
1.using index 表示查询中使用了索引。
2.using where 表示mysql将在存储引擎检索后进行where条件过滤
3.using temporary表示mysql需要使用临时表
4.using filesort 表示mysql不能使用索引排序,而使用文件排序
5.using join buffer 在join查询时,没有使用索引,而使用连接缓存区来缓存中间结果。
6.impossible where 表示where可能会导致查不到结果
7.select tables optimized away仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
8.index merges 给定的表上使用超过一个索引的时候可能会出现索引合并

explain分析的特点:

1.explain不会告诉你触发器、存储过程的信息或用户自定义函数对查询的影响情况。
2.explain不考虑各种Cache
3.explain不能显示mysql在执行查询时所作的优化工作。
4.部分统计信息是估算的,并非精确值。
5.explain只能解释select操作,其他操作要重写为select后查看执行计划。

六、联合索引

在数据量较大的表,索引的优化显得特别重要,建立一个好的索引,数据库查询效率能够提升n倍。

案例一 联合索引的使用,如表table1有三种查询:

1.select * from table1 where A = ?;
2.select * from table1 where A = ? and B = ?;
3.select * from table1 where A = ? and B = ? and C = ?;

那么这三种查询只需要建立一个联合b+tree索引(A,B,C),相当于建立索引(A),(A,B),再使用
A,AB,ABC条件查询。

案例二:排序(Sort)在索引中的使用
如表table1有查询:

select * from table1 where A = 1 order by B asc;

select * from table1 where A = ? and B =? order by C asc; 

这两种查询也只需要建立一个联合b+tree索引(A,B,C)

反例:
例如查询
select * from table1 where A = ? order by id desc;
通过explain分析到,查询优化器实际并没有使用a索引,而是使用的id索引,如果要优化这类索引,
通过建立A和id的联合索引,删除A单列索引。

案例三
条件顺序

如果表中包含A和B,A和B建立联合索引。条件A=?and B=? 和 B =? and A = ? 都会使用使用到索引,但是B=?and A=?会经过查询分析器的优化。

七、总结

  • 了解b+tree 和 hash索引的特点。 b+tree可以支持范围、前缀、等值查询和排序,而hash只能做等值查询。
  • 聚簇索引是构建innodb的基础,如果主键非自增长,随机主键会带来严重的页分裂,影响插入的速度。
  • 了解explain分析的特点,分析出来的结果只是一个参考,并不代表实际的性能,防止进入explain分析误区。
  • 联合索引防止索引重复。如:有(A,B,C)联合索引,相当于也建立了(A),(A,B)索引。最好写where条件的顺序和联合索引顺序一致,避免查询分析器优化耗损性能 。
  • 排序尽量使用索引,有where条件时,可使用联合索引。出现filesort性能很低。
  • like查询在前缀匹配时是可以使用索引的。如:A like ‘abcd%’。如果是 A like ‘%abcd’则是全表扫描。

注意踩坑

  • 特别注意1: mysql最忌讳的问题,查询值类型和字段类型不匹配,mysql是不能使用索引的。 例如:表table1的字段A是varchar类型,查询条件:select * from table1 where A=1, 把A写出数值类型查询,Mysql不能命中索引(正确写法:select * from table1 where A = ‘1’)
  • 特别注意2: 条件中字段带函数处理是无法命中索引。如: select * from table1 wehre substr(A,0,5)=‘abcde’(正确写法:select * from table1 where A like ‘abcde%’)

八、补充 联合索引详解

比较简单的是单列索引(B+tree),遇到多条件查询时,不可避免会使用到多列索引,联合索引又称复合索引。

b+tree结构图:
webp

每一个磁盘块在mysql中就是一个页,页大小是固定的,mysql innodb的默认的页大小是16k,每个索引会分配在页上的数量是由字段的大小决定。当字段值的长度越长,每一页上的数量就会越少,因此在一定数量的情况下,索引的深度会越深,影响索引的查找效率。

对于复合索引,遵循最左侧原则,从左到右的使用字段中的字段,一个查询可以只使用索引中的一部分,但是只能是最左侧部分。例如索引是 key index(a,b,c ),

可以支持(a),(a,b),(a,b,c)3种组合进行查找,但不支持b,c进行查找,当使用最左侧字段时,索引就十分有效。

创建表test如下:

create table test(
  a int ;
  b int;
  c int;
  key(a,b,c)
)

比如(a,b,c)的时候,b+树是按照从左到右的顺序来建立搜索树的,比如当(a=? and b=? and c=?),

这样的数据来检索的时候,b+树会优先比较a列来确定下一步的所搜方向,如果a列相同再依次比较b列和c列,最后得到检索的数据;

但当(b=? and c=?)这样的没有a列的数据的时候,b+树就不知道下一步该查那个节点,因为建立搜索树的时候a列就是第一个比较因子,必须要先根据a列

来搜索才能知道下一步去哪里查询,比如当(a=? and c=?) 这样的数据来检索时,b+树可以用a列来指定搜索方向,但下一个字段b列的缺失,所以只能把a列的数据找到,

然后再匹配c列的数据列,这个是非常重要的性质,即索引的最左匹配特性

以下通过例子分析索引的使用情况,以便于更好的理解联合索引的查询方式和使用范围。

1、多列索引在and查询中应用
select * fromm test where a=? and b=? and c ? 查询效率最高,索引全覆盖。

select * fromm test where a=? and b=? 索引覆盖a和b。

select * fromm test where b=? and a=? 经过mysql的查询分析器的优化,索引覆盖a和b。

select * fromm test where a=? 索引覆盖a。

select * fromm test b=? and c ? 没有a列,不走索引,索引失效。

select * from test where c =? 没有a列,不走索引,索引失效。

2、多列索引在范围查询中应用
select * from test where a =? and b between ? and ? and = ?;

索引覆盖a和b,因为b列是范围查询,因此c列不能走索引。

select * from test where a between ? and ? and b=?

a列走索引,因为a列是范围查询,因此b列是无法使用索引

select * from test where a between ? and ? and b between ? and ? and c=?

a列走索引,因a列是范围查询,b列是范围查询也不能使用索引。

3、多列索引在排序中应用

select * from test where a = ? and b =? order by c;

a,b,c三列全覆盖索引,查询效率最高。

select * from test where a =? and b between ? and ? order by c

a,b列使用索引查找,因为b列是范围查询,因此c列不能使用索引,会出现file sort

4、总结

  1. 联合索引的使用与写where条件的顺序无关,mysql优化器会进行优化而使用索引。但是减轻查询优化器的压力,最好和索引的从左到右的顺序一致。

  2. 使用等值查询,多列同时查询,索引会一直传递并生效,因此等值查询效率最好。

  3. 索引查询遵循最左侧原则,但是遇到范围查询列之后的列索引失效。

  4. 排序也能使用索引,合理使用索引排序,避免出现file sort。

1.1 MySQL逻辑架构

MySQL逻辑结构

  • 第一层:客户端通过连接服务,将要执行的sql指令传输过来
  • 第二层:分析器和优化器解析并优化sql,生成最终的执行计划由执行器执行
  • 第三层:存储引擎,负责数据的储存和提取

1.2 锁

数据库通过锁机制来解决并发场景-共享锁(读锁)和排他锁(写锁)。读锁是不阻塞的,多个客户端可以在同一时刻读取同一个资源。写锁是排他的,并且会阻塞其他的读锁和写锁。简单提下乐观锁和悲观锁。

  • 乐观锁,通常用于数据竞争不激烈的场景,多读少写,通过版本号和时间戳实现。
  • 悲观锁,通常用于数据竞争激烈的场景,每次操作都会锁定数据。

要锁定数据需要一定的锁策略来配合。

  • 表锁,锁定整张表,开销最小,但是会加剧锁竞争。
  • 行锁,锁定行级别,开销最大,但是可以最大程度的支持并发。

但是MySql的存储引擎的真实实现不是简单的行级锁,一般都是实现了多版本并发控制(MVCC)。MVCC是行级锁的变种,多数情况下避免了加锁操作,开销更低。MVCC是通过保存数据的某个时间点快照实现的。

1.3 事务

事务保证一组原子性的操作,要么全部成功,要么全部失败。一旦失败,回滚之前的所有操作。MySql采用自动提交,如果不是显式的开启一个事务,则每个查询都作为一个事务。

隔离级别控制了一个事务中的修改,哪些在事务内和事务间是可见的。四种常见的隔离级别:

  • 读未提交(Read UnCommitted),事务中的修改,即使没提交对其他事务也是可见的。事务可能读取未提交的数据,造成脏读。
  • 读提交(Read Committed),一个事务开始时,只能看见已提交的事务所做的修改。事务未提交之前,所做的修改对其他事务是不可见的。也叫不可重复读,同一个事务多次读取同样记录可能不同。
  • 可重复读(RepeatTable Read),同一个事务中多次读取同样的记录结果时结果相同。
  • 可串行化(Serializable),最高隔离级别,强制事务串行执行。

1.4 存储引擎

InnoDB引擎,最重要,使用最广泛的存储引擎。被用来设计处理大量短期事务,具有高性能和自动崩溃恢复的特性。

MyISAM引擎,不支持事务和行级锁,崩溃后无法安全恢复。

二、创建时优化

2.1 Schema和数据类型优化

整数

TinyInt,SmallInt,MediumInt,Int,BigInt 使用的存储8,16,24,32,64位存储空间。使用Unsigned表示不允许负数,可以使正数的上线提高一倍。

实数

  • Float,Double , 支持近似的浮点运算。
  • Decimal,用于存储精确的小数。

字符串

  • VarChar,存储变长的字符串。需要1或2个额外的字节记录字符串的长度。
  • Char,定长,适合存储固定长度的字符串,如MD5值。
  • Blob,Text 为了存储很大的数据而设计的。分别采用二进制和字符的方式。

时间类型

  • DateTime,保存大范围的值,占8个字节。
  • TimeStamp,推荐,与UNIX时间戳相同,占4个字节。

可以优化的点

  • 尽量使用对应的数据类型。比如,不要用字符串类型保存时间,用整型保存IP。
  • 选择更小的数据类型。能用TinyInt不用Int。
  • 标识列(identifier column),建议使用整型,不推荐字符串类型,占用更多空间,而且计算速度比整型慢。
  • 不推荐ORM系统自动生成的Schema,通常具有不注重数据类型,使用很大的VarChar类型,索引利用不合理等问题。
  • 真实场景混用范式和反范式。冗余高查询效率高,插入更新效率低;冗余低插入更新效率高,查询效率低。
  • 创建完全的独立的汇总表\缓存表,定时生成数据,用于用户耗时时间长的操作。对于精确度要求高的汇总操作,可以采用 历史结果+最新记录的结果 来达到快速查询的目的。
  • 数据迁移,表升级的过程中可以使用影子表的方式,通过修改原表的表名,达到保存历史数据,同时不影响新表使用的目的。

2.2 索引

索引包含一个或多个列的值。MySql只能高效的利用索引的最左前缀列。索引的优势:

  • 减少查询扫描的数据量
  • 避免排序和零时表
  • 将随机IO变为顺序IO (顺序IO的效率高于随机IO)

B-Tree

使用最多的索引类型。采用B-Tree数据结构来存储数据(每个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的遍历)。B-Tree索引适用于全键值,键值范围,键前缀查找,支持排序。

B-Tree索引限制:

  • 如果不是按照索引的最左列开始查询,则无法使用索引。
  • 不能跳过索引中的列。如果使用第一列和第三列索引,则只能使用第一列索引。
  • 如果查询中有个范围查询,则其右边的所有列都无法使用索引优化查询。

哈希索引

只有精确匹配索引的所有列,查询才有效。存储引擎会对所有的索引列计算一个哈希码,哈希索引将所有的哈希码存储在索引中,并保存指向每个数据行的指针。

哈希索引限制:

  • 无法用于排序
  • 不支持部分匹配
  • 只支持等值查询如=,IN(),不支持 < >

可以优化的点

  • 注意每种索引的适用范围和适用限制。
  • 索引的列如果是表达式的一部分或者是函数的参数,则失效。
  • 针对特别长的字符串,可以使用前缀索引,根据索引的选择性选择合适的前缀长度。
  • 使用多列索引的时候,可以通过 AND 和 OR 语法连接。
  • 重复索引没必要,如(A,B)和(A)重复。
  • 索引在where条件查询和group by语法查询的时候特别有效。
  • 将范围查询放在条件查询的最后,防止范围查询导致的右边索引失效的问题。
  • 索引最好不要选择过长的字符串,而且索引列也不宜为null。

三、查询时优化

3.1 查询质量的三个重要指标

  • 响应时间 (服务时间,排队时间)
  • 扫描的行
  • 返回的行

3.2 查询优化点

  • 避免查询无关的列,如使用Select * 返回所有的列。
  • 避免查询无关的行
  • 切分查询。将一个对服务器压力较大的任务,分解到一个较长的时间中,并分多次执行。如要删除一万条数据,可以分10次执行,每次执行完成后暂停一段时间,再继续执行。过程中可以释放服务器资源给其他任务。
  • 分解关联查询。将多表关联查询的一次查询,分解成对单表的多次查询。可以减少锁竞争,查询本身的查询效率也比较高。因为MySql的连接和断开都是轻量级的操作,不会由于查询拆分为多次,造成效率问题。
  • 注意count的操作只能统计不为null的列,所以统计总的行数使用count(*)。
  • group by 按照标识列分组效率高,分组结果不宜出行分组列之外的列。
  • 关联查询延迟关联,可以根据查询条件先缩小各自要查询的范围,再关联。
  • Limit分页优化。可以根据索引覆盖扫描,再根据索引列关联自身查询其他列。如:
  • 在这里插入图片描述
  • Union查询默认去重,如果不是业务必须,建议使用效率更高的Union All

四、其他优化

  • 条件中的字段类型和表结构类型不一致,mysql会自动加转换函数,导致索引作为函数中的参数失效。
  • like查询前面部分未输入,以%开头无法命中索引。

4.1 explain关键字的使用

在这里插入图片描述

  • select_type,有几种值:simple(表示简单的select,没有union和子查询),primary(有子查询,最外面的select查询就是primary),union(union中的第二个或随后的select查询,不依赖外部查询结果),dependent union(union中的第二个或随后的select查询,依赖外部查询结果)
  • type,有几种值:system(表仅有一行(=系统表),这是const连接类型的一个特例),const(常量查询), ref(非唯一索引访问,只有普通索引),eq_ref(使用唯一索引或组件查询),all(全表查询),index(根据索引查询全表),range(范围查询)
  • possible_keys: 表中可能帮助查询的索引
  • key,选择使用的索引
  • key_len,使用的索引长度
  • rows,扫描的行数,越大越不好
  • extra,有几种值:Only index(信息从索引中检索出,比扫描表快),where used(使用where限制),Using filesort (可能在内存或磁盘排序),Using temporary(对查询结果排序时使用临时表)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!