mysql性能优化分析 --- 下篇

时间:2022-05-29 17:14:49

概要回顾

之前看过《高性能mysql》对mysql数据库有了系统化的理解,虽然没能达到精通,但有了概念,遇到问题时会有逻辑条理的分析;

这回继上次sql分析结果的一个继续延伸分析,我拿了;

备注:分析(除sql基本优化过程外)这个过程并定位到具体问题,给出针对性的解决方案,是一个非常漫长的过程,需要一个个的去排除和论证的过程;当对来说十分枯燥和窘境,但成功了就是一非常大的收获和经验;

-- ③ 优化前
select p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.oss_volume as userO*acity,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string FROM poehistoryinfo p LEFT JOIN recordregistryinfo r ON p.record_registry_info_id=r.id LEFT JOIN identityinfo i ON i.account_id=r.mgmt_account_id where r.contract_address=? GROUP BY r.contract_address,p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string,p.oss_volume ORDER BY p.block_time DESC limit ?,? -- ③ 优化后
SELECT p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.oss_volume as userO*acity,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string FROM poehistoryinfo p INNER JOIN
(SELECT p1.id FROM poehistoryinfo p1 LEFT JOIN recordregistryinfo r ON p1.record_registry_info_id=r.id LEFT JOIN identityinfo i ON i.account_id=r.mgmt_account_id where r.contract_address=? ORDER BY p1.block_time DESC limit ?,?) a ON a.id = p.id ;
--- 环境RDS 1C 1G mysql数据库,导入数据库,这时没有其他多余的IO读写操作
这回我申请了一个RDS 1C 1G mysql数据库,导入数据库,这时没有其他多余的IO读写操作;
效果:2条sql语句,
①优化前sql, 5.77ms
②优化后sql, 0.01ms 执行计划正常 --- 环境ECS 1C 1G自建数据库这是没有多余操作,这时没有其他多余的IO读写操作
ECS 自建数据库 1C 1G 这是没有多余操作,
效果:2条sql语句,
①优化前sql, 1.77ms
②优化后sql, 0.01ms 执行计划正常 --- 环境RDS 1C 1G mysql数据库,线上库,这时IOPS平均操作为300~400之间
我司生产上RDS 1C 1G 这时平均读写操作为300-400;
效果:2条sql语句,
①优化前sql, 5.89ms
②优化后sql, 6.35ms,不正常

问题

按mysql语句优化原则优化之后发现未达到预想的效果,问题如下:

  1. 在不优化的情况下自建数据库执行③slq语句比RDS数据库要快5倍以上;
  2. 优化之后的sql语句放到RDS上,比之前未优化的sql语句还慢;

分析思路

为了发现问题所以,我获取RDS 上的sql执行计划:

mysql> explain select p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.oss_volume as userO*acity,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string FROM poehistoryinfo p LEFT JOIN recordregistryinfo r ON p.record_registry_info_id=r.id LEFT JOIN identityinfo i ON i.account_id=r.mgmt_account_id  where r.contract_address='85b692c81d16bfa49ce0b8a166458c71f522fd5b' GROUP BY r.contract_address,p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string,p.oss_volume ORDER BY p.block_time DESC limit 1,25;
id FROM poehistoryinfo p1 LEFT JOIN recordregistryinfo r ON p1.record_registry_info_id=r.id LEFT JOIN identityinfo i ON i.account_id=r.mgmt_account_id where r.contract_address='85b692c81d16bfa49ce0b8a166458c71f522fd5b' ORDER BY p1.block_time DESC limit 1,25;
+----+-------------+-------+------------+------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
| 1 | SIMPLE | r | NULL | ALL | PRIMARY | NULL | NULL | NULL | 39 | 10.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | i | NULL | ref | identityinfo_account_id_ind | identityinfo_account_id_ind | 153 | sync.r.mgmt_account_id | 1 | 100.00 | Using where; Using index |
| 1 | SIMPLE | p | NULL | ALL | NULL | NULL | NULL | NULL | 172999 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.01 sec)
mysql> explain SELECT p.record_data,p.cooperation_name,p.cooperation_id,p.block_time,p.oss_volume as userO*acity,p.txhash,p.seq_id,p.pre_seq_id,p.custom_string FROM poehistoryinfo p  INNER JOIN (SELECT p1.id FROM poehistoryinfo p1 LEFT JOIN recordregistryinfo r ON p1.record_registry_info_id=r.id LEFT JOIN identityinfo i ON i.account_id=r.mgmt_account_id  where r.contract_address='85b692c81d16bfa49ce0b8a166458c71f522fd5b' ORDER BY p1.block_time DESC limit 1,25) a ON a.id = p.id ;

+----+-------------+------------+------------+--------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 26 | 100.00 | NULL |
| 1 | PRIMARY | p | NULL | eq_ref | PRIMARY | PRIMARY | 4 | a.id | 1 | 100.00 | NULL |
| 2 | DERIVED | r | NULL | ALL | PRIMARY | NULL | NULL | NULL | 39 | 10.00 | Using where; Using temporary; Using filesort |
| 2 | DERIVED | i | NULL | ref | identityinfo_account_id_ind | identityinfo_account_id_ind | 153 | sync.r.mgmt_account_id | 1 | 100.00 | Using where; Using index |
| 2 | DERIVED | p1 | NULL | ALL | NULL | NULL | NULL | NULL | 172999 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+------------+------------+--------+-----------------------------+-----------------------------+---------+------------------------+--------+----------+----------------------------------------------------+
5 rows in set, 1 warning (0.00 sec)

根据上面的分析和上篇ECS sql执行计划结果比较差别如下:

  1. ECS ③优化前 sql 与 RDS ③优化前 执行计划结果无很大差异,但执行结果返回的时间分别是0.47 sec 和 5.77 sec(unbelievable);
  2. ECS ③优化后 sql 与 RDS ③优化后 执行计划结果相差非常大;
    1. ECS ③优化后 sql Extra : Using where(索引);Using index, rows 最大259行等
    2. RDS ③优化后 sql Extra : 比较多Using where;Using temporary;Using filesort;Using where;Using index;Using buffer(Block Nested Loop); rows 中有172999(相当于全表查询) 

分析术语名词说明

Using index:表示MySQL将使用覆盖索引
Using where: 表示使用存储引擎检索索引在进行过滤
Using temporary: 表示MySQL对查询结果排序时会使用一个临时表
Using filesort: 表示Mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取
Using buffer(Block Nested Loop): 关联嵌套查询

分析以上结果,很明显RDS 优化后的效果非常不佳,甚至RDS优化前还差,使用时间还长; 而优化语句是按照Mysql 优化器和索引的基本原则来的,除非是唯一能想到的是两个mysql环境与配置存在差异导致的。 按照这个思路,结合RDS工单处理工程师, 去查询语句如下

  • show profiles;
  • show global variables like '%query_cache%';
  • show variables like '%optimizer_switch%' \G;
  • show create table 表明 \G;
  • show variables;

step 1:分析show profiles,发现主要耗时时间在sending data上,但无法定位,猜测可能是缓存或者是乐观锁之类的问题;

step 2:分析%query_cache%发现query_cache是没有开启的;

step 3:分析table表是否存在差异,貌似没有...排除

step 4:分析show variables;猜测rds的"tmpdir","/home/mysql/data3001/tmp",ecs自建的是| tmpdir | /tmp 会对Using temporary; Using filesort 操作有影响;怀疑/tmp 是属于内存盘,所以ECS会比RDS性能更优,但当我用df -h 看一下,发现并没有这种情况,再由RDS工程师查看ubuntu文档对其验证,发现假设不成立;

step 5: 之前是试过使用强制索引来改变优化后的sql语句的执行计划,但并没有多大的用处。RDS工程师分析认为问题出在IOPS(Input/Output Operations Per Second)上,因为RSD平均IOPS有300-400,而ECS则是0;为了排除这个原因RDS工程师很大方的建了一个1C1G RDS MySQL给我测试使用。优化后的问题不在出现,而优化前的SQL语句还是有问题:ECS的执行时间比RDS的块10多倍;

不过有一点可以确定,但重复执行多次sql语句时,执行返回结果时间会缩短,一直到一个基本固定不变的值,这说明查询存在缓存, 目前给出的解决方案是修改RDS配置如下:

set tmp_table_size=50*1024*1024*2;
set max_heap_table_size=50*1024*1024*2;
set join_buffer_size=50*1024*1024;
set sort_buffer_size=50*1024*1024;

其实,我认为这个语句无论是否有效,对RDS的用户而言都是非常差的一个表现,还需要认为的去深度考虑mysql的配置参数变量,从某种意义上说就是DB owner,那我们又何必要使用RDS呢。

后来发现 到了公司生产环境上修改配置时反而干慢了,后来一同事针对IOPS 数值不正常情况做了深度排查,发现xxxx系统上有一个定时任务,该定时任务每隔3秒执行一次,而且是针对600多万数据的非索引全表扫描,耗时已经大于3秒,(导致上一次还没执行完,变又开始下一轮的执行) ,不过定时任务用的是SpringBoot下Quartz的集成框架,其中有一共能是要等上一次任务执行完毕才可以继续下一次任务执行;这也就意味着每时每刻Mysql都有全盘扫描操作,这也是导致Mysql IOPS 居高不下的原因;

后来给出的解决方案是将该查询语句添加索引,直接让IOPS 从300-800 降到了30左右;整体性能提高;之前的条件语句优化查询也回到了正常范围;

从这点我们可以看出在Mysql中 IOPS的高低直接影响SQL 语句的性能查询与优化;