searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

MySQL慢查询优化

2024-05-31 05:34:40
12
0

在上一篇文章中学习了如何使用pt-query-digest分析RDS慢查询日志,而在本文中我们将进一步学习如何来优化慢查询


一、什么是慢查询?

慢查询是指在数据库中执行的查询操作,其执行时间超过了一定的阈值(这个阈值通常是根据实际情况设定的在MySQL中受long_query_time控制)。


二、慢查询会有什么现象?

监控层面:

     慢查询现象出现的时候,一般伴随着多个监控指标同时飙升,例如:CPU 使用率、慢查询数量、innodb行读。

业务层面:

  1. 系统响应迟缓:在执行相关查询时,整个系统的响应变得很慢,其他操作也受到影响。
  2. 资源占用异常:可能会导致 CPU、内存等资源被过度占用,影响其他进程的正常运行。
  3. 页面加载缓慢:如果涉及到数据库查询的网页或应用界面,会出现加载缓慢甚至长时间无响应的情况。
  4. 查询执行时间过长:明显超出正常预期的时间来完成查询操作,导致用户等待时间增加。
  5. 大量并发时性能急剧下降:在高并发场景下,慢查询会更加突出,导致整体性能大幅下滑。

三、慢查询相关的参数

slow_query_log

用于开启或关闭慢查询日志。

slow_query_log_file

指定慢查询日志的存储文件路径。

long_query_time

慢查询阈值,超过这个时间的查询将被记录为慢查询,默认值通常是 10 秒。

log_queries_not_using_indexes

如果设置为 ON,则会将没有使用索引的查询记录到慢查询日志中,有助于提前规避一些隐患。

innodb_buffer_pool_size

缓冲池大小,该部分缓存是 InnoDB 引擎最重要的缓存区域,是通过内存来弥补物理数据文件的重要手段,在云数据库 MySQL 上会采用实例规格配置的25%作为该部分大小。其中主要包含数据页、索引页、undo 页、insert buffer、自适应哈希索引、锁信息以及数据字典等信息。在进行 SQL 读和写的操作时,首先并不是对物理数据文件操作,而是先对 buffer_pool 进行操作,再通过 checkpoint 等机制写回数据文件。该空间的优点是可以提升数据库的性能、加快 SQL 运行速度,缺点是故障恢复速度较慢。对查询性能有较大影响,具体说明可以查下MySQL官方文档,根据业务情况一般建议设置在65%左右。

query_cache_size 

主要用于控制查询缓存的大小,当相同的查询再次执行时,如果结果在查询缓存中,就可以直接从缓存中获取结果,避免了重复执行查询的开销,从而可以提高查询性能。这个参数限制也是有的,比如对于查询SQL的匹配度要完全相同才会命中,其次对于经常需要更新的表也不适用。当查询缓存变得过大时,管理和维护它也会消耗一定资源。不是所有类型的查询都适合缓存,一些复杂的、动态性强的查询可能不太适合放入查询缓存。它的工作流程如下:-

  1. 当一个SELECT语句执行时,MySQL会先检查查询缓存,是否有该查询的结果。
  2. 如果缓存中有该查询的结果,则直接返回结果给客户端。
  3. 如果缓存中没有该查询的结果,则执行查询语句,将结果存储到缓存中,并返回结果给客户端。
thread_cache_size

该参数用于指定线程缓存的大小。优点就是可以减少频繁创建和销毁连接线程的开销,提高连接处理的效率。当有新的连接请求时,首先会尝试从线程缓存中获取可用的线程,而不是立即创建新的线程。需要注意的是需要根据系统的实际负载和连接情况来合理设置。如果设置得太小,可能无法充分利用线程缓存的优势;如果设置得太大,可能会浪费系统资源。要结合服务器的并发连接数、连接的活跃程度等因素来综合考虑。通常需要通过实际测试和观察来确定一个较为合适的值,以在性能和资源利用之间达到较好的平衡。结合物理内存设置:根据系统的物理内存大小来调整 thread_cache_size。可以参考以下则:
1G 内存:thread_cache_size=8
2G 内存:thread_cache_size=16
3G 内存:thread_cache_size=32
大于 3G 内存:thread_cache_size=60+4


四、慢查询通常情况下是 SQL 语句的执行效率不够高,导致大量的请求堆积在云数据库 MySQL 中,常见原因有两个

 
  • 原因1:SQL 设计不合理,语句没有利用索引或者没有用较佳的索引,简单举例一下走索引和不走索引的区别。

1.准备一张t1的测试表

CREATE TABLE "t1" (
  "id" int(11) NOT NULL,
  "name" varchar(100) DEFAULT NULL,
  "insert_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY ("id")
) ;

2.通过explain分析慢SQL,explain字段详细说明

mysql> explain select * from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10161 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
1 row in set, 1 warning (0.00 sec)
id 表示查询中每个子查询或操作的编号
select_type 描述查询的类型,如简单查询、子查询等
table 涉及的表名
partitions 相关的分区信息
type 表示索引的访问类型,如全表扫描、索引范围扫描等,它反映了查询的效率
possible_keys 可能用到的索引
key 实际使用的索引
key_len 索引键的长度
ref 表示与索引进行比较的对象
rows 估计需要扫描的行数
filtered 表示过滤后剩余结果的比例
Extra 包含一些额外的执行信息,如是否使用了索引等

2.用id字段和name字段作为条件,分别查看他们的执行计划有什么区别。

mysql> explain select * from t1 where id =2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t1 where name ='dengmeng';
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10161 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

3.通过对比我们发现以id为条件,走了主键索引,扫描行数是1行执行效率很不错的,而通过name为条件时进行了全表扫描,没有走到索引。那么业务就需要通过name条件查询时,该如何优化呢?我们可以给name列也加上一个索引。

alter table t1 add index index_name (name);

4.在查看执行计划发现效果很明显,从全表扫描降低到只扫描1行数据,大大提升了SQL执行效率。对于大表查询没有走索引是很容易造成慢查询,从而导致业务响应慢。还有一种情况是走了索引缺还是查询慢,这种就需要根据业务情况具体分析,是索引失效或是索引区分度不高,再或者是表的数据量非常之大本身就很慢。

mysql> explain select * from t1 where name='dengmeng';
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | index_name    | index_name | 303     | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

5.索引优化参考官方文档:

优化建议:

  1. 单表的索引数量不应超过5个,单个索引中的字段数不应超过5个,以避免索引过多的过滤作用和资源消耗。
  2. 确保索引字段长度固定且不宜过长。过长的索引字段会占用更多磁盘空间,并影响索引的性能。
  3. 避免冗余索引,即存在两个索引 (a,b) 和 (a) 的情况,若查询条件为a列,只需建立 (a,b) 索引即可,不需要额外建立 (a) 索引。
  4. 对于高过滤性的字段,考虑在其上加索引。高过滤性字段的索引可以提高查询效率。
  5. 注意选择性和数据类型。选择性高的字段和合适的数据类型可以提高索引效果和查询性能。
  6. 合理利用覆盖索引来减少IO开销,通过创建包含所需列的复合索引,避免回表操作。

注意事项:

  1. 禁止在更新频繁且区分度低的列上建立索引,这会降低数据库性能。
  2. 在使用LIKE关键字时,避免在搜索模式字符串之前使用前置%符号。这样做会导致索引失效,影响查询性能。
  3. 避免在具有空值的列上创建索引。一般情况下,索引不会建立在含有空值的列上。
  4. 在使用OR关键字时,确保OR操作符的左右字段都有索引。如果一个字段有索引而另一个字段没有索引,有索引的字段也会失效。
  5. 避免使用!=操作符。范围的不确定性导致使用索引效率低下,数据库引擎会自动将其改为全表扫描。
  6. 不要在索引字段上进行运算。这会导致索引失效,降低查询性能。
  7. 使用复合索引时,应该遵循最左前缀原则,并确保查询中使用索引的第一个字段,否则索引将失效。为了最大化索引的效率,应该尽量保持查询中字段的顺序与索引的顺序一致。
  • 原因2:QPS 压力超过当前实例的承载上限

处理措施:提升云数据库 MySQL 配置。

1.登录管理控制台。
2.单击管理控制台左上角的资源池,选择区域和项目。
3.选择“数据库 > 关系数据库MySQL版”,进入关系数据库MySQL版控制台。
4.在“实例管理”页面, 选择指定的实例,单击“更多”选择规格扩容操作,进入规格扩容页面,对实例进行扩容。

0条评论
0 / 1000
DBM
8文章数
0粉丝数
DBM
8 文章 | 0 粉丝
原创

MySQL慢查询优化

2024-05-31 05:34:40
12
0

在上一篇文章中学习了如何使用pt-query-digest分析RDS慢查询日志,而在本文中我们将进一步学习如何来优化慢查询


一、什么是慢查询?

慢查询是指在数据库中执行的查询操作,其执行时间超过了一定的阈值(这个阈值通常是根据实际情况设定的在MySQL中受long_query_time控制)。


二、慢查询会有什么现象?

监控层面:

     慢查询现象出现的时候,一般伴随着多个监控指标同时飙升,例如:CPU 使用率、慢查询数量、innodb行读。

业务层面:

  1. 系统响应迟缓:在执行相关查询时,整个系统的响应变得很慢,其他操作也受到影响。
  2. 资源占用异常:可能会导致 CPU、内存等资源被过度占用,影响其他进程的正常运行。
  3. 页面加载缓慢:如果涉及到数据库查询的网页或应用界面,会出现加载缓慢甚至长时间无响应的情况。
  4. 查询执行时间过长:明显超出正常预期的时间来完成查询操作,导致用户等待时间增加。
  5. 大量并发时性能急剧下降:在高并发场景下,慢查询会更加突出,导致整体性能大幅下滑。

三、慢查询相关的参数

slow_query_log

用于开启或关闭慢查询日志。

slow_query_log_file

指定慢查询日志的存储文件路径。

long_query_time

慢查询阈值,超过这个时间的查询将被记录为慢查询,默认值通常是 10 秒。

log_queries_not_using_indexes

如果设置为 ON,则会将没有使用索引的查询记录到慢查询日志中,有助于提前规避一些隐患。

innodb_buffer_pool_size

缓冲池大小,该部分缓存是 InnoDB 引擎最重要的缓存区域,是通过内存来弥补物理数据文件的重要手段,在云数据库 MySQL 上会采用实例规格配置的25%作为该部分大小。其中主要包含数据页、索引页、undo 页、insert buffer、自适应哈希索引、锁信息以及数据字典等信息。在进行 SQL 读和写的操作时,首先并不是对物理数据文件操作,而是先对 buffer_pool 进行操作,再通过 checkpoint 等机制写回数据文件。该空间的优点是可以提升数据库的性能、加快 SQL 运行速度,缺点是故障恢复速度较慢。对查询性能有较大影响,具体说明可以查下MySQL官方文档,根据业务情况一般建议设置在65%左右。

query_cache_size 

主要用于控制查询缓存的大小,当相同的查询再次执行时,如果结果在查询缓存中,就可以直接从缓存中获取结果,避免了重复执行查询的开销,从而可以提高查询性能。这个参数限制也是有的,比如对于查询SQL的匹配度要完全相同才会命中,其次对于经常需要更新的表也不适用。当查询缓存变得过大时,管理和维护它也会消耗一定资源。不是所有类型的查询都适合缓存,一些复杂的、动态性强的查询可能不太适合放入查询缓存。它的工作流程如下:-

  1. 当一个SELECT语句执行时,MySQL会先检查查询缓存,是否有该查询的结果。
  2. 如果缓存中有该查询的结果,则直接返回结果给客户端。
  3. 如果缓存中没有该查询的结果,则执行查询语句,将结果存储到缓存中,并返回结果给客户端。
thread_cache_size

该参数用于指定线程缓存的大小。优点就是可以减少频繁创建和销毁连接线程的开销,提高连接处理的效率。当有新的连接请求时,首先会尝试从线程缓存中获取可用的线程,而不是立即创建新的线程。需要注意的是需要根据系统的实际负载和连接情况来合理设置。如果设置得太小,可能无法充分利用线程缓存的优势;如果设置得太大,可能会浪费系统资源。要结合服务器的并发连接数、连接的活跃程度等因素来综合考虑。通常需要通过实际测试和观察来确定一个较为合适的值,以在性能和资源利用之间达到较好的平衡。结合物理内存设置:根据系统的物理内存大小来调整 thread_cache_size。可以参考以下则:
1G 内存:thread_cache_size=8
2G 内存:thread_cache_size=16
3G 内存:thread_cache_size=32
大于 3G 内存:thread_cache_size=60+4


四、慢查询通常情况下是 SQL 语句的执行效率不够高,导致大量的请求堆积在云数据库 MySQL 中,常见原因有两个

 
  • 原因1:SQL 设计不合理,语句没有利用索引或者没有用较佳的索引,简单举例一下走索引和不走索引的区别。

1.准备一张t1的测试表

CREATE TABLE "t1" (
  "id" int(11) NOT NULL,
  "name" varchar(100) DEFAULT NULL,
  "insert_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY ("id")
) ;

2.通过explain分析慢SQL,explain字段详细说明

mysql> explain select * from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10161 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------+
1 row in set, 1 warning (0.00 sec)
id 表示查询中每个子查询或操作的编号
select_type 描述查询的类型,如简单查询、子查询等
table 涉及的表名
partitions 相关的分区信息
type 表示索引的访问类型,如全表扫描、索引范围扫描等,它反映了查询的效率
possible_keys 可能用到的索引
key 实际使用的索引
key_len 索引键的长度
ref 表示与索引进行比较的对象
rows 估计需要扫描的行数
filtered 表示过滤后剩余结果的比例
Extra 包含一些额外的执行信息,如是否使用了索引等

2.用id字段和name字段作为条件,分别查看他们的执行计划有什么区别。

mysql> explain select * from t1 where id =2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t1 where name ='dengmeng';
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10161 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

3.通过对比我们发现以id为条件,走了主键索引,扫描行数是1行执行效率很不错的,而通过name为条件时进行了全表扫描,没有走到索引。那么业务就需要通过name条件查询时,该如何优化呢?我们可以给name列也加上一个索引。

alter table t1 add index index_name (name);

4.在查看执行计划发现效果很明显,从全表扫描降低到只扫描1行数据,大大提升了SQL执行效率。对于大表查询没有走索引是很容易造成慢查询,从而导致业务响应慢。还有一种情况是走了索引缺还是查询慢,这种就需要根据业务情况具体分析,是索引失效或是索引区分度不高,再或者是表的数据量非常之大本身就很慢。

mysql> explain select * from t1 where name='dengmeng';
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | index_name    | index_name | 303     | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

5.索引优化参考官方文档:

优化建议:

  1. 单表的索引数量不应超过5个,单个索引中的字段数不应超过5个,以避免索引过多的过滤作用和资源消耗。
  2. 确保索引字段长度固定且不宜过长。过长的索引字段会占用更多磁盘空间,并影响索引的性能。
  3. 避免冗余索引,即存在两个索引 (a,b) 和 (a) 的情况,若查询条件为a列,只需建立 (a,b) 索引即可,不需要额外建立 (a) 索引。
  4. 对于高过滤性的字段,考虑在其上加索引。高过滤性字段的索引可以提高查询效率。
  5. 注意选择性和数据类型。选择性高的字段和合适的数据类型可以提高索引效果和查询性能。
  6. 合理利用覆盖索引来减少IO开销,通过创建包含所需列的复合索引,避免回表操作。

注意事项:

  1. 禁止在更新频繁且区分度低的列上建立索引,这会降低数据库性能。
  2. 在使用LIKE关键字时,避免在搜索模式字符串之前使用前置%符号。这样做会导致索引失效,影响查询性能。
  3. 避免在具有空值的列上创建索引。一般情况下,索引不会建立在含有空值的列上。
  4. 在使用OR关键字时,确保OR操作符的左右字段都有索引。如果一个字段有索引而另一个字段没有索引,有索引的字段也会失效。
  5. 避免使用!=操作符。范围的不确定性导致使用索引效率低下,数据库引擎会自动将其改为全表扫描。
  6. 不要在索引字段上进行运算。这会导致索引失效,降低查询性能。
  7. 使用复合索引时,应该遵循最左前缀原则,并确保查询中使用索引的第一个字段,否则索引将失效。为了最大化索引的效率,应该尽量保持查询中字段的顺序与索引的顺序一致。
  • 原因2:QPS 压力超过当前实例的承载上限

处理措施:提升云数据库 MySQL 配置。

1.登录管理控制台。
2.单击管理控制台左上角的资源池,选择区域和项目。
3.选择“数据库 > 关系数据库MySQL版”,进入关系数据库MySQL版控制台。
4.在“实例管理”页面, 选择指定的实例,单击“更多”选择规格扩容操作,进入规格扩容页面,对实例进行扩容。

文章来自个人专栏
RDS
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0