关于SQL查询语句的优化,有一些一般的优化步骤,本节就介绍一下通用的优化步骤。
首先,我们如果要明白一条查询语句所运行的过程,这样我们才能针对过程去进行优化。
参考我之前画的一张MySQL基础架构图:
MySQL中一条查询语句的生命周期大概可以分为
一般而言,执行器执行阶段是最为重要的阶段,也就是说,最耗费性能的阶段。所以我们的优化,其实就是在优化执行器的执行计划。
查询性能低下的原因是因为访问的数据量太大,使得一个查询很慢,那么我们如何发现并且解决慢查询呢。
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和内存资源。
典型的场景:
总结而言,我认为其实就是分为两点:
对于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条件,从好到坏依次为:
常见的重构查询的方式有以下几种:
在传统的实现中,强调在数据库层完成尽可能多的工作,在过去是认为网络通信、查询解析和优化是一件代价很高的事情。
但是MySQL的设计让连接和断开连接都十分轻量级,返回一个小的查询结果方面很高效。MySQL内部每秒能够扫描内存中上百万行数据,相较而言,MySQL响应数据给客户端速度很慢。
所以有的时候可以权衡一下是否需要将一个大查询分解为多个小查询。
有时候对于一个大查询分而治之,将一个大查询分解为小查询。每个查询功能完全一样,每次只返回一小部分。
最典型的场景便是删除旧数据,比如要删除100w旧数据,如果一次SQL执行全部删除,那么该SQL语句会执行相当长的过程,同时可能导致锁住大量数据、占满整个事务日志、耗尽系统资源、阻塞其他查询。那么我们可以一次删除1w的旧数据,这样子就可以将删除全部旧数据的性能损耗平摊到一个很长的时间,大大降低对于服务器的影响。
关于JOIN关键字,很多DBA会直接禁用JOIN语句,原因是JOIN语句可能会扫描大量的行数,在后期的博客中,我会专门总结。简单而言,如果被驱动表上面不能使用索引,那么会直接每次查询被驱动表的时候都进行全表扫描,A JOIN B的扫描行数会直接是A的行数 * B的行数。
所以我们可以将一个JOIN语句拆分成多个简单的查询,然后在应用程序中进行组装。
分解关联查询可以有以下优点:
MySQL的优化器是一个基于成本的优化器,有些原因会导致MySQL优化器选择错误的执行计划:
MySQL能够处理的优化类型有以下:
EXPLAIN关键字对于经常进行SQL语句优化的同学肯定很熟悉,我在这里简单介绍一下,EXPLAIN是显示该SQL语句的预计执行计划,是否使用索引、中间表、排序等,以及预计扫描行数等数据。
以上便是MySQL的SQL查询语句性能优化的概述,后期将会针对以下问题出针对性的博客进行具体场景的优化:
以及EXPLAIN的详解、常见的优化失效的情况,以及如何解决。