干货!MySQL优化原理分析及优化方案总结

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 说起MySQL优化的话,想必大部分人都不陌生了。在我们的记忆储备里也早已记住了这些关键词:避免使用SELECT*、避免使用NULL值的判断、根据需求适当的建立索引、优化MySQL参数.....

 image.gif

image.gif

说起MySQL优化的话,想必大部分人都不陌生了。在我们的记忆储备里也早已记住了这些关键词:避免使用SELECT*、避免使用NULL值的判断、根据需求适当的建立索引、优化MySQL参数......但是你对于这些优化技巧是否真正的掌握了及其相应的工作原理是否吃透了呢?在我们的实际开发过程中你能充分应用到吗?我觉得还有待考察。所以,本文将详细介绍MySQL优化技巧以及其相应的技术原理,希望大家看完以后,能更清楚直接的了解这些优化方案,并应用到我们的工作岗位中。

01

原理分析

1

MySQL架构原理

在此之前我们需要先了解一下MySQL的各个部分组件的工作结构,便于我们理解MySQL服务器。如下图:

image.gif

MySQL工作架构大致可以分为三层,最上面为客户端,比如:连接处理、授权认证、安全等功能都在这一层处理。

MySQL的核心服务在中间这一层,包括查询解析、分析、优化、缓存、内置函数。所有的跨存储引擎的功能也在这一层实现:存储过程、触发器、视图等。

最下层为存储引擎负责MySQL中的数据存储和提取。和Linux下的文件系统类似,每种存储引擎都有其优势和劣势。中间的服务层通过API与存储引擎通信,这些API接口屏蔽了不同存储引擎间的差异。

2

MySQL查询原理

我们想让MySQL获得更高的查询性能,首先需要先了解MySQL怎样进行优化和执行查询的。先来看一下,当我们向MySQL发送了一个请求的时候,它都做了些什么呢?

image.gif

客户端/服务端通信协议

MySQL客户端/服务端通信协议不是同时发生的,在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。

客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置 max_allowed_packet参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。

与之相反的是,服务器响应给用户的数据通常会很多,由多个数据包组成。但是当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,然后让服务器停止发送。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用 SELECT*以及加上 LIMIT限制的原因之一。

查询缓存

在解析一个查询语句前,如果查询缓存是打开的,那么MySQL会检查这个查询语句是否命中查询缓存中的数据。如果当前查询恰好命中查询缓存,在检查一次用户权限后直接返回缓存中的结果。这种情况下,查询不会被解析,也不会生成执行计划,更不会执行。

MySQL将缓存存放在一个引用表(不要理解成 table,可以认为是类似于 HashMap的数据结构),通过一个哈希值索引,这个哈希值通过查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息计算得来。所以两个查询在任何字符上的不同(例如:空格、注释),都会导致缓存不会命中。

如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,其查询结果 都不会被缓存。比如函数 NOW()或者 CURRENT_DATE()会因为不同的查询时间,返回不同的查询结果,再比如包含 CURRENT_USER或者 CONNECION_ID()的查询语句会因为不同的用户而返回不同的结果,将这样的查询结果缓存起来没有任何的意义。

既然是缓存,就会失效,那查询缓存何时失效呢?MySQL的查询缓存系统会跟踪查询中涉及的每个表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。正因为如此,在任何的写操作时,MySQL必须将对应表的所有缓存都设置为失效。如果查询缓存非常大或者碎片很多,这个操作就可能带来很大的系统消耗,甚至导致系统僵死一会儿。而且查询缓存对系统的额外消耗也不仅仅在写操作,读操作也不例外:

    1. 任何的查询语句在开始之前都必须经过检查,即使这条SQL语句永远不会命中缓存
    2. 如果查询结果可以被缓存,那么执行完成后,会将结果存入缓存,也会带来额外的系统消耗

    基于此,我们要知道并不是什么情况下查询缓存都会提高系统性能,缓存和失效都会带来额外消耗,只有当缓存带来的资源节约大于其本身消耗的资源时,才会给系统带来性能提升。但要如何评估打开缓存是否能够带来性能提升是一件非常困难的事情,也不在本文讨论的范畴内。如果系统确实存在一些性能问题,可以尝试打开查询缓存,并在数据库设计上做一些优化,比如:

      1. 用多个小表代替一个大表,注意不要过度设计
      2. 批量插入代替循环单条插入
      3. 合理控制缓存空间大小,一般来说其大小设置为几十兆比较合适
      4. 可以通过 SQL_CACHE和 SQL_NO_CACHE来控制某个查询语句是否需要进行缓存

      最后的忠告是不要轻易打开查询缓存,特别是写密集型应用。如果你实在是忍不住,可以将 query_cache_type设置为 DEMAND,这时只有加入 SQL_CACHE的查询才会走缓存,其他查询则不会,这样可以非常自由地控制哪些查询需要被缓存。

      当然查询缓存系统本身是非常复杂的,这里讨论的也只是很小的一部分,其他更深入的话题,比如:缓存是如何使用内存的?如何控制内存的碎片化?事务对查询缓存有何影响等等,读者可以自行阅读相关资料,这里权当抛砖引玉吧。

      语法解析和预处理

      MySQL通过关键字将SQL语句进行解析,并生成一颗对应的解析树。这个过程解析器主要通过语法规则来验证和解析。比如SQL中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据MySQL规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。

       

      查询优化

      经过前面的步骤生成的语法树被认为是合法的了,并且由优化器将其转化成查询计划。多数情况下,一条查询可以有很多种执行方式,最后都返回相应的结果。优化器的作用就是找到这其中最好的执行计划。

      MySQL使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。在MySQL可以通过查询当前会话的 last_query_cost的值来得到其计算当前查询的成本。

      mysql> select * from t_message limit 10;...省略结果集
      mysql> show status like 'last_query_cost';+-----------------+-------------+| Variable_name   | Value       |+-----------------+-------------+| Last_query_cost | 6391.799000 |+-----------------+-------------+

      image.gif

      示例中的结果表示优化器认为大概需要做6391个数据页的随机查找才能完成上面的查询。这个结果是根据一些列的统计信息计算得来的,这些统计信息包括:每张表或者索引的页面个数、索引的基数、索引和数据行的长度、索引的分布情况等等。

      有非常多的原因会导致MySQL选择错误的执行计划,比如统计信息不准确、不会考虑不受其控制的操作成本(用户自定义函数、存储过程)、MySQL认为的最优跟我们想的不一样(我们希望执行时间尽可能短,但MySQL值选择它认为成本小的,但成本小并不意味着执行时间短)等等。

      MySQL的查询优化器是一个非常复杂的部件,它使用了非常多的优化策略来生成一个最优的执行计划:

        • 重新定义表的关联顺序(多张表关联查询时,并不一定按照SQL中指定的顺序进行,但有一些技巧可以指定关联顺序)
        • 优化 MIN()和 MAX()函数(找某列的最小值,如果该列有索引,只需要查找B+Tree索引最左端,反之则可以找到最大值,具体原理见下文)
        • 提前终止查询(比如:使用Limit时,查找到满足数量的结果集后会立即终止查询)
        • 优化排序(在老版本MySQL会使用两次传输排序,即先读取行指针和需要排序的字段在内存中对其排序,然后再根据排序结果去读取数据行,而新版本采用的是单次传输排序,也就是一次读取所有的数据行,然后根据给定的列排序。对于I/O密集型应用,效率会高很多)

        随着MySQL的不断发展,优化器使用的优化策略也在不断的进化,这里仅仅介绍几个非常常用且容易理解的优化策略,其他的优化策略,大家自行查阅吧。

        查询执行引擎

        在完成解析和优化阶段以后,MySQL会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为 handler API。查询过程中的每一张表由一个 handler实例表示。实际上,MySQL在查询优化阶段就为每一张表创建了一个 handler实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。

        返回结果给客户端

        查询执行的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL仍然会返回这个查询的相关信息,比如改查询影响到的行数以及执行时间等等。

        如果查询缓存被打开且这个查询可以被缓存,MySQL也会将结果存放到缓存中。

        结果集返回客户端是一个增量且逐步返回的过程。有可能MySQL在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足①中所描述的通信协议的数据包发送,再通过TCP协议进行传输,在传输过程中,可能对MySQL的数据包进行缓存然后批量发送。

        回头总结一下MySQL整个查询执行过程,总的来说分为6个步骤:

          1. 客户端向MySQL服务器发送一条查询请求
          2. 服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段
          3. 服务器进行SQL解析、预处理、再由优化器生成对应的执行计划
          4. MySQL根据执行计划,调用存储引擎的API来执行查询
          5. 将结果返回给客户端,同时缓存查询结果

          02

          优化方案

          1

          优化建议

          看了以上原理,想必你现在已经很清楚MySQL的运行原理了,接下来我将从一下这些方面对其进行优化。

          SQL优化

          我们需要注意这几点:先选择需要优化的SQL,一般都从Explain和Profile出发,永远用小结果集驱动大的结果集,在索引中完成排序,使用最小的Columns,使用最有效的过滤条件,避免复杂的JOIN和子查询。具体优化如下:

          分析SQL执行频率

          show status
          例如:分析读为主,还是写为主

          image.gif

          定位效率低的SQL

          慢查询日志定位
          -log-slow-queries = xxx(指定文件名)
          SHOW PROCESSLIST
          查看当前正在进行的线程,包括线程状态、是否锁表

          image.gif

          分析SQL执行计划

          explain "your sql"
          desc "your sql"
          - 部分参数分析
          select_type: 
          SIMPLE 简单表,不使用表连接或子查询
          PRIMARY 主查询,即外层的查询
          UNION 
          SUBQUER 子查询的第一个select
          type: 
          ALL 全表扫描
          index 索引全扫描
          range 索引范围扫描
          ref 使用非唯一索引或唯一索引的前缀扫描
          eq_ref 类似ref,使用的索引是唯一索引
          const/system 单表中最多有一个匹配行
          NULL 不用访问表或者索引,直接得到结果

          image.gif

          show profile 分析SQL

          select @@have_profiling 是否支持
          select @@profiling 是否开启
          执行 "your sql"
          show profiles 
          show profile block io for QUERY 17

          image.gif

          索引优化

          这里我从三个方面来分析,分别是类型,方法,创建:

          image.gif

          image.gif

          示例如下:

          索引的存储分类

          B-TREE索引:常见,大部分都支持
          HASH索引:只有memory引擎支持
          R-TREE索引:空间索引是MyISAM的一个特殊索引类型,主要用于地理空间数据类型
          full-text索引:全文索引,MyISAM的一个特殊索引类型,innodb从5.6开始支持

          image.gif

          索引的创建与删除

          添加索引
          ALTER Table `table_name` ADD PRIMARY KEY(`column`)
          ALTER Table `table_name` ADD UNIQUE(`column`)
          ALTER Table `table_name` ADD INDEX(`column`)
          ALTER Table `table_name` ADD FULLTEXT(`column`)
          删除
          ALTER Table `table_name` drop index index_name

          image.gif

          MySQL能使用索引的情况

          匹配全值
          匹配值范围查询
          匹配最左前缀
          仅仅对索引进行查询(覆盖查询)
          匹配列前缀 (添加前缀索引)
          部分精确+部分范围

          image.gif

          不能使用索引的情况

          以%开关的like查询
          数据类型出现隐式转换
          复合索引查询条件不包含最左部分
          使用索引仍比全表扫描慢
          用or分割开的条件

          image.gif

          语句优化

          定期优化表

          optimize table table_name 合并表空间碎片,对MyISAM、BDB、INNODB有效
          如果提示不支持,可以用 mysql --skip-new 或者 mysql --safe-mode 来重启,以便让其他引擎支持

          image.gif

          常用优化

          尽量避免全表扫描,对where及orderby的列建立索引
          尽量避免where使用 != 或 <>
          尽量避免where子句用 or 连接条件
          乱用%导致全表扫描
          尽量避免where子句对字段进行表达式操作
          尽量避免where子句对字段进行函数操作
          覆盖查询,返回需要的字段
          优化嵌套查询,关联查询优于子查询
          组合索引或复合索引,最左索引原则
          用exist代替in
          当索引列有大量重复数据时,SQL查询可能不会去利用索引

          image.gif

          JOIN的优化

          JOIN原理

          在mysql中使用Nested Loop Join来实现join;
             A JOIN B:通过A表的结果集作为循环基础,一条一条的通过结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果

          image.gif

          JOIN优化原则

          1,尽可能减少Join 语句中的Nested Loop 的循环总次数,用小结果集驱动大结果集;
          2,优先优化Nested Loop 的内层循环;
          3,保证Join 语句中被驱动表上Join 条件字段已经被索引;
          4,扩大join buffer的大小;

          image.gif

          数据库对象优化

          优化表数据类型

          PROCEDURE ANALYSE (16,256) 排除多于16个,大于256字节的ENUM建议
          "your sql" PROCEDURE ANALYSE ()

          image.gif

          表拆分

          垂直拆分
          针对某些列常用、某些列不常用
          水平拆分
          表很大
          表中的数据有独立性,能简单分类
          需要在表存放多种介质

          image.gif

          反范式

          增加冗余列、增加派生列、重新组表和分割表

          image.gif

          使用中间表

          数据查询量大
          数据统计、分析场景

          image.gif

          优化MySQL服务器

          MySQL的引擎对比:

          image.gif

          表引擎的命令:

          show engines; 查看myql所支持的存储引擎
          show variables like '%storage_engine'; 查看mysql默认的存储引擎
          show create table table_name 查看具体表使用的存储引擎

          image.gif

          InnoDB

          1. 提供事务、回滚、系统崩溃修复能力、多版本并发控制事务
          2. 支持自增列
          3. 支持外键
          4. 支持事务以及事务相关联功能
          5. 支持mvcc的行级锁

          image.gif

          MyISAM

          1. 不支持事务、不支持行级锁,只支持并发插入的表锁,主要用于高负载的select
          2. 支持三种不同的存储结构:静态、动态、压缩

          image.gif

          MySQL并发参数调整

          MySQL并发参数

          max_connections 最大连接数,默认151
          back_log 短时间内处理大量连接,可适当增大
          table_open_cache 控制所有SQL执行线程可打开表缓存的数量,受其他参数制约
          thread_cache_size 控制缓存客户服务线程数量,加快数据库连接速度,根据threads_created/connections来衡量是否合适
          innodb_lock_wait_timeout 控制事务等待行锁时间,默认50ms

          image.gif

          优化内存提高MySQL后台服务

          MyISAM内存优化

          #修改相应服务器位置的配置文件 my.cnf
          key_buffer_size
          决定myisam索引块缓存区的大小,直接影响表的存取效率,建议1/4可用内存
          read_buffer 读缓存
          write_buffer 写缓存

          image.gif

          InnoDB内存优化

          innodb_buffer_pool_size 存储引擎表数据和索引数据的最大缓存区大小
          innodb_old_blocks_pct LRU算法 决定old sublist的比例
          innodb_old_blocks_time LRU算法 数据转移间隔时间

          image.gif

          应用的优化

          使用原因

          数据的重要性
          mysql 服务及自身性能瓶颈
          保证大型系统稳定可靠运行

          image.gif

          优化方法

          使用连接池
          减少对 mysql 的真实连接
          a. 避免相同数据重复执行(查询缓存)
          b. 使用 mysql 缓存(sql 缓存)
          负载均衡
          a. LVS 分布式
          b. 读写分离(主主复制、主从复制保证数据一致性

          image.gif

          本期的MySQL优化讲解就先到这里了,当然优化方案远远不止这些,也欢迎小伙伴们在留言区积极讨论,希望本期文章能对大家在日常开发的过程中起到一定的积极作用。

          image.gif


          image.gif

          点个赞,证明你还爱我

          相关实践学习
          如何在云端创建MySQL数据库
          开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
          全面了解阿里云能为你做什么
          阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
          相关文章
          |
          18天前
          |
          存储 SQL 关系型数据库
          Mysql高可用架构方案
          本文阐述了Mysql高可用架构方案,介绍了 主从模式,MHA模式,MMM模式,MGR模式 方案的实现方式,没有哪个方案是完美的,开发人员在选择何种方案应用到项目中也没有标准答案,合适的才是最好的。
          90 3
          Mysql高可用架构方案
          |
          13天前
          |
          SQL 关系型数据库 MySQL
          MySQL慢查询优化、索引优化、以及表等优化详解
          本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
          MySQL慢查询优化、索引优化、以及表等优化详解
          |
          29天前
          |
          存储 关系型数据库 MySQL
          MySQL主从复制原理和使用
          本文介绍了MySQL主从复制的基本概念、原理及其实现方法,详细讲解了一主两从的架构设计,以及三种常见的复制模式(全同步、异步、半同步)的特点与适用场景。此外,文章还提供了Spring Boot环境下配置主从复制的具体代码示例,包括数据源配置、上下文切换、路由实现及切面编程等内容,帮助读者理解如何在实际项目中实现数据库的读写分离。
          MySQL主从复制原理和使用
          |
          18天前
          |
          缓存 监控 关系型数据库
          如何优化MySQL查询速度?
          如何优化MySQL查询速度?【10月更文挑战第31天】
          45 3
          |
          19天前
          |
          关系型数据库 MySQL
          mysql 5.7.x版本查看某张表、库的大小 思路方案说明
          mysql 5.7.x版本查看某张表、库的大小 思路方案说明
          45 5
          |
          20天前
          |
          缓存 关系型数据库 MySQL
          如何优化 MySQL 数据库的性能?
          【10月更文挑战第28天】
          44 1
          |
          28天前
          |
          NoSQL 关系型数据库 MySQL
          MySQL与Redis协同作战:百万级数据统计优化实践
          【10月更文挑战第21天】 在处理大规模数据集时,传统的单体数据库解决方案往往力不从心。MySQL和Redis的组合提供了一种高效的解决方案,通过将数据库操作与高速缓存相结合,可以显著提升数据处理的性能。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
          74 9
          |
          22天前
          |
          监控 关系型数据库 MySQL
          数据库优化:MySQL索引策略与查询性能调优实战
          【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
          108 1
          |
          28天前
          |
          NoSQL 关系型数据库 MySQL
          MySQL与Redis协同作战:优化百万数据查询的实战经验
          【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
          56 5
          |
          25天前
          |
          关系型数据库 MySQL
          mysql 5.7.x版本查看某张表、库的大小 思路方案说明
          mysql 5.7.x版本查看某张表、库的大小 思路方案说明
          28 1
          下一篇
          无影云桌面