MySql教程

深入MySQL(四):MySQL的SQL查询语句性能优化概述

本文主要是介绍深入MySQL(四):MySQL的SQL查询语句性能优化概述,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

关于SQL查询语句的优化,有一些一般的优化步骤,本节就介绍一下通用的优化步骤。

一条查询语句是如何执行的

首先,我们如果要明白一条查询语句所运行的过程,这样我们才能针对过程去进行优化。
参考我之前画的一张MySQL基础架构图:
在这里插入图片描述
MySQL中一条查询语句的生命周期大概可以分为

  1. 客户端通过连接器连接服务器
  2. 在解析器中解析SQL语句
  3. 优化器进行优化
  4. 执行器执行生成的执行计划
  5. 返回结果给客户端

一般而言,执行器执行阶段是最为重要的阶段,也就是说,最耗费性能的阶段。所以我们的优化,其实就是在优化执行器的执行计划。

如何发现与解决慢查询

查询性能低下的原因是因为访问的数据量太大,使得一个查询很慢,那么我们如何发现并且解决慢查询呢。

慢查询日志

MySQL中有一个日志叫做慢查询日志SLOW_QUERY_LOG,它会记录该MySQL中响应时间超过阈值(long_query_time)的语句,慢查询日志默认不开启,并且默认阈值为10,如果是在线上环境的话不建议开启慢查询日志,有调优需要的时候在开启比较合适。

mysql> show variables like '%slow_query_log%';
+---------------------+-------------------------------------------------+
| Variable_name       | Value                                           |
+---------------------+-------------------------------------------------+
| slow_query_log      | OFF                                             |
| slow_query_log_file | /var/lib/mysql/iZwz9iiwojnmkotgajxjlqZ-slow.log |
+---------------------+-------------------------------------------------+
2 rows in set (0.01 sec)

mysql> set global slow_query_log=1;
Query OK, 0 rows affected (0.00 sec)

使用set global slow_query_log=1开启慢查询在重启MySQL之后会失效,如果要一直生效的话,那么就需要修改配置文件my.cnf。

slow_query_log_file就可以看到你所存储的慢查询日志的位置,我们首先执行语句:

mysql> select sleep(12);
+-----------+
| sleep(12) |
+-----------+
|         0 |
+-----------+
1 row in set (12.00 sec)

然后进入/var/lib/mysql/iZwz9iiwojnmkotgajxjlqZ-slow.log

/usr/sbin/mysqld, Version: 8.0.27-0ubuntu0.20.04.1 ((Ubuntu)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
# Time: 2022-02-04T08:27:51.234016Z
# User@Host: root[root] @ localhost []  Id:  1653
# Query_time: 12.001395  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 1
SET timestamp=1643963259;
select sleep(12);

我们就可以看到,一条慢查询已经记录到慢查询日志中,然后我们可以根据后面会提到的正常的优化步骤进行逐步优化。

注:因为我本身专注后端,MySQL的慢查询日志这里只是简单使用,其实慢查询日志还有很多其他的参数,同时还有多种MySQL日志分析工具可以协助,等到以后如果有需要的时候,我会专门出一章来讲述MySQL日志分析工具的使用。

通用的优化思路

SQL语句的优化有一些通用的思路,这里一一介绍一下。

尽量不获取不需要的数据

有些时候,查询会请求超过实际需要的数据,然后在应用程序中丢弃多余数据,这样会给MySQL服务器带来额外的负担,并且增加网络开销,损耗应用服务器的CPU和内存资源。
典型的场景:

  • 查询不需要的数据
  • 多表关联时返回全部列
  • 总是取出全部列
  • 重复查询相同的数据

总结而言,我认为其实就是分为两点:

  1. 慎用SELECT * FROM
  2. 只取出需要的字段

减少MySQL扫描的额外记录行数

对于MySQL的查询语句而言,其返回的数据行和扫描的数据行是不同的,我们可以用一个很简单的例子来说明:
这是一张利用存储过程生成了100w条数据的数据表
在这里插入图片描述
其中索引有这些:
在这里插入图片描述
我们执行一条查询SQL语句:

mysql> EXPLAIN SELECT * from user where account = 12345;
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | test          | test | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

EXPLAIN待会再解释,大家只要知道该关键字可以看到大致扫描行数即可。
可以看到扫描行数为1行,返回的行数也是一行。
我们删除掉‘test’索引,再来执行同样的语句:

mysql> EXPLAIN SELECT * from user where account = 12345;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 991536 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

可以看到,我们的扫描行数非常大,是一个全表扫描,但是我们返回的行数其实是一行,也就是说我们做了大量的无用扫描。
一般MySQL能够使用如下三种方式应用WHERE条件,从好到坏依次为:

  • 在索引中使用WHERE条件过滤不匹配的记录。这是在存储引擎完成的。
  • 使用覆盖索引扫描返回记录,直接从索引中过滤不需要的记录并返回命中的结果。MySQL的服务层完成的,不需要回表。
  • 从数据表中返回数据,然后过滤不满足条件的记录。在MySQL服务器层完成。

重构查询的方式

常见的重构查询的方式有以下几种:

  1. 将一个复杂查询拆分为多个简单查询
  2. 切分查询,对大查询分而治之,一次查询一小部分结果
  3. 分解关联查询

一个复杂查询还是多个简单查询

在传统的实现中,强调在数据库层完成尽可能多的工作,在过去是认为网络通信、查询解析和优化是一件代价很高的事情。
但是MySQL的设计让连接和断开连接都十分轻量级,返回一个小的查询结果方面很高效。MySQL内部每秒能够扫描内存中上百万行数据,相较而言,MySQL响应数据给客户端速度很慢。
所以有的时候可以权衡一下是否需要将一个大查询分解为多个小查询。

切分查询

有时候对于一个大查询分而治之,将一个大查询分解为小查询。每个查询功能完全一样,每次只返回一小部分。
最典型的场景便是删除旧数据,比如要删除100w旧数据,如果一次SQL执行全部删除,那么该SQL语句会执行相当长的过程,同时可能导致锁住大量数据、占满整个事务日志、耗尽系统资源、阻塞其他查询。那么我们可以一次删除1w的旧数据,这样子就可以将删除全部旧数据的性能损耗平摊到一个很长的时间,大大降低对于服务器的影响。

分解关联查询

关于JOIN关键字,很多DBA会直接禁用JOIN语句,原因是JOIN语句可能会扫描大量的行数,在后期的博客中,我会专门总结。简单而言,如果被驱动表上面不能使用索引,那么会直接每次查询被驱动表的时候都进行全表扫描,A JOIN B的扫描行数会直接是A的行数 * B的行数
所以我们可以将一个JOIN语句拆分成多个简单的查询,然后在应用程序中进行组装。

分解关联查询可以有以下优点:

  1. 执行单个查询可以减少锁的竞争
  2. 应用层做关联,更容易对数据库进行拆分,更容易做到高性能和可扩展
  3. 可以减少冗余记录的查询
  4. 这种形式相当于在应用层做了哈希关联,很多情况下哈希关联效率高很多

MySQL优化器

MySQL的优化器是一个基于成本的优化器,有些原因会导致MySQL优化器选择错误的执行计划:

  1. 统计信息不准确。
  2. 执行计划的成本估计并不等于实际成本。
  3. MySQL的最优并不只是考虑响应时间。
  4. MySQL不考虑并发执行的情况。
  5. MySQL有时候会基于一些固定规则优化。

MySQL能够处理的优化类型有以下:

  1. 重新定义关联表的顺序
  2. 将外连接转为内连接
  3. 使用等价变化规则:比如合并和减少一些比较、移除一些恒等或者不恒等的判断,例如(5=5 AND a>5)会被改写为a>5。
  4. 优化COUNT()、MIN()、MAX()
  5. 预估并转化为常数表达式
  6. 覆盖索引扫描
  7. 子查询优化
  8. 提前终止查询:LIMIT、发现一个不成立的条件。
  9. 等值传播:使用USING的时候,不用重复声明两个表中相同的字段。
    10.列表IN()的比较:MySQL中的IN()是将列表中的数据先排序,然后二分查询。

EXPLAIN关键字的应用

EXPLAIN关键字对于经常进行SQL语句优化的同学肯定很熟悉,我在这里简单介绍一下,EXPLAIN是显示该SQL语句的预计执行计划,是否使用索引、中间表、排序等,以及预计扫描行数等数据。

尾言

以上便是MySQL的SQL查询语句性能优化的概述,后期将会针对以下问题出针对性的博客进行具体场景的优化:

  1. 优化COUNT()查询
  2. 优化LIMIT分页
  3. 关联语句的优化
  4. 排序的优化

以及EXPLAIN的详解、常见的优化失效的情况,以及如何解决。

这篇关于深入MySQL(四):MySQL的SQL查询语句性能优化概述的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!