MySQL的服务架构

clent 客户端 采用数据库连接池,减少频繁的开关连接
server 服务器:
连接器:控制用户的连接
分析器:词法分析、语法分析
优化器:优化SQL语句,规定执行流程
执行器:SQL语句的实际执行组件

存储引擎(I’nnoDB、Myisam、Memory):不同的存放位置,不同的文件格式
image.png

一、性能监控

show profile

使用show profile 查询剖析工具
MySQL官网文档:https://dev.mysql.com/doc/refman/8.0/en/show-profile.html
image.png

  1. use day27;
  2. show tables;
  3. set profiling = 1;
  4. select * from student;
  5. show profiles; //默认是最近一个查询
  6. show profile for query 3; # 查询queryID为3 的执行时间
  7. show profile cpu;

image.png
image.png
image.png

performance schema

官方文档:https://dev.mysql.com/doc/refman/8.0/en/performance-schema-quick-start.html
默认打开,只读属性,无法修改,只能从配置文件进行修改。
主要关注server 运行时的一些元数据信息
表中的数据不会持久化。

use performance_schema; # 切换数据库
show tables;# 查看当前数据库下的所有表,表中数据无法持久化,重启后丢失

show variables like 'performance_schema';# 查看performance_schema属性 默认开启,只读无法修改 on 表示开启 off关闭。
[mysqld]
performance_schema=ON

image.png
insteument:
consumer

show precesslist

官方文档:https://dev.mysql.com/doc/refman/8.0/en/show-processlist.html

 show processlist; #查看连接
 SHOW FULL PROCESSLIST\G

image.png

数据库连接池
c3p0
druid https://github.com/alibaba/druid

二、通过数据类型优化

2.1 更小的数据类型通常更快

应该尽量使用可以正确存储数据的最小数据类型,更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期更少,但是要确保没有低估需要存储的值的范围,如果无法确认哪个数据类型,就选择你认为不会超过范围的最小类型
案例:设计两张表,设计不同的数据类型,查看表的容量

2.2 简单数据类型

简单数据类型的操作通常需要更少的CPU周期,例如,
1、整型比字符操作代价更低,因为字符集和校对规则是字符比较比整型比较更复杂,
2、使用mysql自建类型而不是字符串来存储日期和时间
3、用整型存储IP地址
案例:创建两张相同的表,改变日期的数据类型,查看SQL语句执行的速度

2.3 尽量避免设计成可为null的列

如果查询中包含可为NULL的列,对mysql来说很难优化,因为可为null的列使得索引、索引统计和值比较都更加复杂,坦白来说,通常情况下null的列改为not null带来的性能提升比较小,所有没有必要将所有的表的schema进行修改,但是应该尽量避免设计成可为null的列

2.4 实际细则

整数

可以使用的几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT分别使用8,16,24,32,64位存储空间。
尽量使用满足需求的最小数据类型

varchar

  1. varchar可变程度,可以设置最大长度;最大空间是65535个字节,适合用在长度可变的属性
  2. 使用最小的符合需求的长度。
  3. varchar(n) n小于等于255使用额外一个字节保存长度,n>255使用额外两个字节保存长度。
  4. varchar(5)与varchar(255)保存同样的内容,硬盘存储空间相同,但内存空间占用不同

char

  1. 按照查询速度:char>varchar>text
  2. char长度固定,即每条数据占用等长字节空间;最大长度是255个字符,存储长度波动不大的数据,如:md5摘要
  3. 会自动删除末尾的空格
  4. 检索效率、写效率 会比varchar高,以空间换时间

    BLOG和TEXT类型

    MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。
    两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。

    date、datetime、timestamp

  5. datetime 占用8个字节,与时区无关,数据库底层时区配置,对datetime无效,可保存到毫秒,可保存时间范围大,不要使用字符串存储日期类型,占用空间大,损失日期类型函数的便捷性。

  6. timestamp 占用4个字节,时间范围:1970-01-01到2038-01-19,精确到秒,采用整形存储,依赖数据库设置,自动更新timestamp列的值的时区。
  7. date 占用的字节数比使用字符串、datetime、int存储要少,使用date类型只需要3个字节,使用date类型还可以利用日期时间函数进行日期之间的计算,date类型用于保存1000-01-01到9999-12-31之间的日期。

    枚举类型

    使用枚举代替字符串类型:
    有时可以使用枚举类代替常用的字符串类型,mysql存储枚举类型会非常紧凑,会根据列表值的数据压缩到一个或两个字节中,mysql在内部会将每个值在列表中的位置保存为整数,并且在表的.frm文件中保存“数字-字符串”映射关系的查找表
    create table enum_test(e enum('fish','apple','dog') not null);
    insert into enum_test(e) values('fish'),('dog'),('apple');
    select e+0 from enum_test;-- 使用整形存储
    

特殊类型数据

人们经常使用varchar(15)来存储ip地址,然而,它的本质是32位无符号整数不是字符串,可以使用INET_ATON()和INET_NTOA()函数在这两种表示方法之间转换
案例:

select INET_ATON('127.0.0.1');
select INET_NTOA('2130706433');

image.png

image.png

2.5 范式

第一范式:列不可分
第二范式:不能存在传递依赖
第三范式:表里的其他字段必须唯一依赖主键

主键选择

代理主键:与业务无关的,无意义的数字序列
自然主键:事物属性中的自然唯一标识
推荐使用代理主键:它们不与业务耦合,因此更容易维护,一个大多数表,最好是全部表,通用的键策略能够减少需要编写的源码数量,减少系统的总体拥有成本

字符集的选择

  1. 纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间。
  2. 如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费。
  3. MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率。
  4. 选择utf8mb4比较合适

    存储引擎的选择

    MySQL 默认是InnoDB
    InnoDB 文件类型为idb :数据和索引一起存储
    MyIsAM 文件类型分为 MYD 存储数据 和 MYI 存储索引

InnoDB 和 MyIsam 的区别
image.png

适当的数据冗余

适当的数据冗余,可以避免两表join关联查询

适当拆分

将表中查询不频繁的大数据字段进行拆分成单独表,需要的时候再关联查询。
当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。

三、通过explain执行计划分析进行优化

为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。
可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的。

explain  select * from user;

官方文档:https://dev.mysql.com/doc/refman/5.7/en/explain.html
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
image.png

id

select查询的序列号,包含一组数字,表示查询中执行select子句或者操作表的顺序

1、如果id相同,那么执行顺序从上到下
2、如果id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3、id相同和不同的,同时存在:相同的可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行

select_type

主要用来分辨查询的类型,是普通查询还是联合查询还是子查询:

  1. sample:简单的查询,不包含子查询和union
  2. primary:查询中若包含任何复杂的子查询,最外层查询则被标记为Primary
  3. union:若第二个select出现在union之后,则被标记为union
  4. dependent union:跟union类似,此处的depentent表示union或union all联合而成的结果会受外部表影响
  5. union result:从union表获取结果的select
  6. subquery:在select或者where列表中包含子查询
  7. dependent subquery:subquery的子查询要受到外部表查询的影响
  8. DERIVED: from子句中出现的子查询,也叫做派生类,
  9. UNCACHEABLE SUBQUERY:表示使用子查询的结果不能被缓存
  10. uncacheable union:表示union的查询结果不能被缓存:sql语句未验证

    table

    对应行正在访问哪一个表,表名或者别名,可能是临时表或者union合并结果集

type

type显示的是访问类型,访问类型表示我是以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下,访问的类型有很多,效率从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般情况下,得保证查询至少达到range级别,最好能达到ref。

  1. all:全表扫描,一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。
  2. index:全索引扫描这个比all的效率要好,主要有两种情况,一种是当前的查询时覆盖索引,即我们需要的数据在索引中就可以索取,或者是使用了索引进行排序,这样就避免数据的重排序。
  3. range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描,适用的操作符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN() 。
  4. index_subquery:利用索引来关联子查询,不再扫描全表。
  5. unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引。
  6. index_merge:在查询过程中需要多个索引组合使用,没有模拟出来。
  7. ref_or_null:对于某个字段即需要关联条件,也需要null值的情况下,查询优化器会选择这种访问方式。
  8. ref:使用了非唯一性索引进行数据的查找。
  9. eq_ref :使用唯一性索引进行数据查找。
  10. const:这个表至多有一个匹配行。
  11. system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现。

possible_key

显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

key

实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠。

key_len

表示索引中使用的字节数,可以通过key_len计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数

rows

根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好

extra

包含额外的信息:

  1. using index:这个表示当前的查询时覆盖索引的,直接从索引中读取数据,而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找,如果没有,表面索引被用来读取数据,而不是真的查找。
  2. using where:使用where进行条件过滤。
  3. using filesort:说明mysql无法利用索引进行排序,只能利用排序算法进行排序,会消耗额外的位置。
  4. using temporary:建立临时表来保存中间结果,查询完成之后把临时表删除。
  5. using join buffer:使用连接缓存。
  6. impossible where:where语句的结果总是false。

    MVCC

四、通过索引优化

官方文档:https://dev.mysql.com/doc/refman/5.7/en/mysql-indexes.html
参考文章:

4.1 五种基本索引类型

索引的优点:

  1. 大大减少了服务器需要扫描的数据量
  2. 帮助服务器避免排序和临时表
  3. 将随机io变成顺序io

索引的作用:

  1. 快速查找匹配WHERE子句的行
  2. 从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引
  3. 如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行
  4. 当有表连接的时候,从其他表检索行数据
  5. 查找特定索引列的min或max值
  6. 如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组
  7. 在某些情况下,可以优化查询以检索值而无需查询数据行

    4.1.1. 主键索引 primary

    主键索引是一种特殊的唯一索引,主键索引即不允许值为空,也不允许值重复。一般在建表的时候都会建主键索引primary key。

4.1.2. 唯一索引 unique

唯一索引限制哪一列(或哪些列组合后)的值是唯一的、不重复的,通常用于保存数据前的唯一性校验。

alter table table_name add unique index_name (column1, ... columnN);

4.1.3. 普通索引 normal

alter table table_name add index index_name on column_name;

4.1.4. 全文索引 fulltext

全文索引用于查询文本列的值里面的关键字,而不是直接与值进行匹配,其效果就相当于like %xx%,但是针对大量文本数据时,全文索引的查询效率会比like模糊查询快很多倍。
只有数据类型为char, varchar, text, bigtext才可以创建全文索引。
全文索引的使用有自己的语法,使用match和against.

-- 创建全文索引
alter table table_name add fulltext index_name(text1, ...);
-- 使用全文索引
select * from table_name match(text1) against ('abc');
-- 注意:match()函数中的列必须和全文索引完全保存一致,否则无法使用全文索引。

4.1.5. 组合索引

组合索引 是多列字段组合起来的索引,组合索引使用时遵循最左前缀集合,也就是查询条件使用到了组合索引中的第一个字段时才会使用该索引。

4.2 实现索引的数据结构

4.2.1 哈希表

基于哈希表的实现,只有精确匹配索引所有列的查询才有效。
在mysql中,只有memory的存储引擎显式支持哈希索引。
哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快。

哈希索引的限制:

  1. 哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行。
  2. 哈希索引数据并不是按照索引值顺序存储的,所以无法进行排序。
  3. 哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值。
  4. 哈希索引支持等值比较查询,也不支持任何范围查询。
  5. 访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行。
  6. 哈希冲突比较多的话,维护的代价也会很高。

如何解决哈希冲突

扰动函数

4.2.2 B+ 树

二叉树:
BST(二叉搜索树):排序,二分查找
AVL树(平衡树):通过自旋操作保持树平衡(最长和最短高度相差不能超过1)(自旋消耗内存,插入和删除效率低,)
红黑树(RBT):弱平衡树,最长子树不超过最短子树的两倍即可。是AVL树的一个变种,通过损失部分部分查询性能来获得插入性能的提升。

B-Tree(B 树 横杠)
https://www.cs.usfca.edu/~galles/visualization/BTree.html
image.png
B+Tree
https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
image.png
MySQL innoDB 和 MyisAm 索引默认使用的数据类型是 B+树

B树和B+树两者个区别:

  1. B-Tree 中,所有节点都会带有指向具体记录的指针;B+Tree 中只有叶子结点会带有指向具体记录的指针。
  2. B-Tree 中不同的叶子之间没有连在一起;B+Tree 中所有的叶子结点通过指针连接在一起。
  3. B-Tree 中可能在非叶子结点就拿到了指向具体记录的指针,搜索效率不稳定;B+Tree 中,一定要到叶子结点中才可以获取到具体记录的指针,搜索效率稳定。

总结:

  1. B+Tree 中,由于非叶子结点不带有指向具体记录的指针,所以非叶子结点中可以存储更多的索引项,这样就可以有效降低树的高度(深度)进而提高搜索的效率
  2. B+Tree 中,叶子结点通过指针连接在一起,这样如果有范围扫描的需求,那么实现起来将非常容易,而对于 B-Tree,范围扫描则需要不停的在叶子结点和非叶子结点之间移动。

4.3 技术名词

回表

非主键索引树搜索回到主键索引树搜索的过程称为:回表

覆盖索引

就是把单列的非主键索引 修改为 多字段 的联合索引, 在一棵索引数上。 就找到了想要的数据, 不需要去主键索引树上,再检索一遍 这个现象,称之为 索引覆盖.
覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。
以name和age两个字段建立组合索引
覆盖索引避免了回表现象的产生,从而减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是性能优化的一种手段。

最左匹配

索引下推

4.4 索引优化

聚簇索引

按照物理存储方式,可以分为聚簇索引和非聚簇索引。
我们日常所说的主键索引,其实就是聚簇索引(Clustered Index);
主键索引之外,其他的都称之为非主键索引,非主键索引也被称为二级索引(Secondary Index),或者叫作辅助索引。
对于主键索引和非主键索引,使用的数据结构都是 B+Tree,唯一的区别在于叶子结点中存储的内容不同:

  • 主键索引的叶子结点存储的是一行完整的数据
  • 非主键索引的叶子结点存储的则是主键值。叶子结点不包含行记录的全部数据;非主键的叶子结点中,除了用来排序的key还包含一个bookmark;该书签存储了聚集索引的key。

当我们需要查询的时候:

  1. 如果是通过主键索引来查询数据,例如 select * from user where id=100,那么此时只需要搜索主键索引的 B+Tree 就可以找到数据。
  2. 如果是通过非主键索引来查询数据,例如 select * from user where username=’javaboy’,那么此时需要先搜索 username 这一列索引的 B+Tree,搜索完成后得到主键的值,然后再去搜索主键索引的 B+Tree,就可以获取到一行完整的数据。

对于第二种查询方式而言,一共搜索了两棵 B+Tree,第一次搜索 B+Tree 拿到主键值后再去搜索主键索引的 B+Tree,这个过程就是所谓的回表。
从上面的分析中我们也能看出,通过非主键索引查询要扫描两棵 B+Tree,而通过主键索引查询只需要扫描一棵 B+Tree,所以如果条件允许,还是建议在查询中优先选择通过主键索引进行搜索。
但是不用主键索引也不一定需要回表查询:如果查询的列本身就存在于索引中,那么即使使用二级索引,一样也是不需要回表的。

优点:

  1. 1、可以把相关数据保存在一起
  2. 2、数据访问更快,因为索引和数据保存在同一个树中
  3. 3、使用覆盖索引扫描的查询可以直接使用页节点中的主键值

缺点:

  1. 1、聚簇数据最大限度地提高了IO密集型应用的性能,如果数据全部在内存,那么聚簇索引就没有什么优势
  2. 2、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
  3. 3、更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置
  4. 4、基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题
  5. 5、聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候

InnoDb
MyIsAM

非聚簇索引

数据文件跟索引文件分开存放

前缀索引

有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的选择性,索引的选择性是指不重复的索引值和数据表记录总数的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性更高的索引可以让mysql在查找的时候过滤掉更多的行。<br />        一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对应BLOB,TEXT,VARCHAR类型的列,必须要使用前缀索引,因为mysql不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以保证较高的选择性,通过又不能太长。
--计算完成之后可以创建前缀索引
alter table citydemo add key(city(7));

--注意:前缀索引是一种能使索引更小更快的有效方法,但是也包含缺点:mysql无法使用前缀索引做order by 和 group by。

利用索引进行排序

mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序。<br />    扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行,这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢。(如果索引不能覆盖查询所需的全部列,使用索引的话,会导致每一条记录都需要回表,这样的代价是要比不走索引,直接扫描全表数据要大的)<br />    mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。<br />    只有当索引的**列顺序**和**order by子句的顺序**完全一致,并且所有列的排序方式都一样时(ASC/DESC),mysql才能够使用索引来对结果进行排序,如果查询需要关联多张表,则只有当orderby子句引用的字段全部为第一张表时,才能使用索引做排序。order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则,mysql都需要执行顺序操作,而无法利用索引排序

union all 、in 和 or 查询

union all,in,or都能够使用索引,但是推荐使用in

范围列可以用到的索引

范围条件是:<、>
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列

强制类型转换会全表扫描

explain select * from user where phone=13800001234; --不会触发索引
explain select * from user where phone='13800001234'; --触发索引

更新十分频繁,数据区分度不高的字段上不宜建立索引

  1. 更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能。
  2. 类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据。
  3. 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算。

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

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

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

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

单索引字段数不允许超过5个(组合索引)

组合索引字段数不要超过5个

索引不是越多越好

4.4 索引的匹配方式

只要使用了索引就一定会用索引吗?

全值匹配

全值匹配指的是和索引中的所有列进行匹配

匹配最左前缀

只匹配前面的几列

匹配列前缀

可以匹配某一列的值的开头部分,在进行like模糊匹配的时候如果%在前,则不会走索引,%在后则会走索引。

explain select * from staffs where name like 'J%';
explain select * from staffs where name like '%y';

匹配范围值

可以查找某一个范围的数据

explain select * from staffs where name > 'Mary';

精确匹配某一列并范围匹配另外一列

可以查询第一列的全部和第二列的部分

explain select * from staffs where name = 'July' and age > 25;

只访问索引的查询

查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引

4.5 索引监控

show status like 'Handler_read%';

五、查询优化