转自:mysql性能瓶颈-高CPU定位
在性能压测过程中,导致数据库CPU很高的原因有很多种,一般和慢SQL也有关(因为每条SQL要么占CPU高,要么占IO高,大体是这样),那么如何分析到是某些SQL引起的呢?
分析时机:运行性能测试,监测到CPU比较高时,实时分析;

1、SQL引起的高CPU

先抛出结论:如果数据分布不合理,会导致sql语句执行时间长,尤其在基础数据量大,该sql语句执行次数多时更为明显;这样会导致SQL语句执行占用CPU高;
分析过程:
1)首先定位占用CPU高的线程(目的:明确占用CPU高的均为mysql线程?)
通过top命令发现mysql占用CPU高
再看mysql进程下有多少线程占用CPU高:top -p [pid] H
image.png
2)在mysql中使用 SHOW FULL PROCESSLIST; 查询,找到对CPU影响较大的语句(最后附上show full processlist详解)
当CPU较高时,执行show full processlist,查看到实时执行的SQL语句以及各种信息(最重要的为state列,查看sql语句当前状态);
image.png 可以看到有不少是Sending data状态的,我们挑选其中最复杂的一条语句来分析:(为啥挑Sending data状态的最复杂语句,不理解)
image.png
此条语句使用了联合查询,可以根据情况对此语句进行分析或对联合查询的单条语句进行分析;
3)语句分析
分析的方式同 慢SQL分析(手动查询、Explain分析、show profile分析),细节可参见另一篇文章:https://blog.csdn.net/xiaona0523/article/details/107981984
以下为大体分析过程:
a)show profile分析:
image.png
由于语句为联合查询,因此可看到有2条sending data,占用时间和花费时间都很高;
可以拆分继续分析,如下述语句:

  1. SELECT r.id,c.check_action_name,check_date,check_end_date,c.id AS check_content_id,c.check_object_name AS checkObjectName,c.update_time,c.verify
  2. FROM administrative_check_content c
  3. LEFT JOIN administrative_check_report r ON c.report_id=r.id
  4. LEFT JOIN administrative_check_report_enforcers e ON r.id=e.report_id
  5. WHERE e.enforcer_id= 'ec66d95c8c6d437b9e3a460f93f1a592'

b)手动查询
执行上述拆分出来的单条语句,查询时间约为0.8s多,耗时较长
c)Explain分析
执行 EXPLAIN 分析,看到通过索引查询到的内容多达30084行,如下所示:
image.png
一般像这样的系统进行压测,多半是数据分布不合理,测试数据没有反应真实的业务场景,明显数据分布不均衡,一个ID号就关联三万条数据,使用索引的效率都没能体现出来。
总结:通过SHOW PROCESSLIST我们可以知道Mysql当前的线程状态,以及主要资源消耗在哪方面;再结合show profile分析具体占用CPU高的SQL,可以进一步定位出SQL引起高CPU的原因,到这一步无疑就能指导开发人员的优化方向了。

2、其他原因引起的高CPU

1)show processlist
通过SHOW PROCESSLIST查询mysql线程状态,我们需要重点了解State列不同状态所代表的含义(state含义见文章最后附注)
注:show processlist只列出前100条,而show full processlist会列出全部;
下图为show processlist查询结果示例:
image.png
大部分状态(state列)对应操作很快,只要有一个线程保持同一个状态好几秒钟,那么可能有问题发生,需要检查一下;
2)show status
通过show status查询当前mysql的运行状态(附注记录了详细解释)
image.png
如果在日常运维过程做过记录,那么当系统出现性能异常时,即可做状态值对比,偏离过大的就是需要关注的点;可将这些参数值加入到运维监控系统,作为关注指标;
3)kill id
可以尝试kill id(id在SHOW PROCESSLIST中显示 ),关掉疑似占CPU高的线程,以确认是否能让CPU降下来。
对于mysql来说,慢SQL及死锁以外的CPU问题确实不好定位,要求对数据库系统及性能非常了解,而对于我们做性能测试的,能做的就是逐层分析,缩小问题范围,实在不行,只能用kill id的方式来试错排查。

附注:

1)show full processlist详解,转载自https://www.cnblogs.com/tongcharge/p/11495393.html
当cpu百分比居高不下时,可使用show processlist分析;
show processlist只列出前100条,show full processlist 可全部列出;
image.png
各列的含义和用途:

  1. id列,不用说了吧,一个标识,你要kill一个语句的时候很有用。
  2. user列,显示单前用户,如果不是root,这 个命令就只显示你权限范围内的sql语句。
  3. host列,显示这个语句是从哪个ip的哪个端口上发出的。呵呵,可以用来追踪出问题语句的用户。
  4. db列,显示 这个进程目前连接的是哪个数据库。command列,显示当前连接的执行的命令,一般就是休眠(sleep),查询(query),连接 connect)。
  5. time列,此这个状态持续的时间,单位是秒。
  6. state列,显示使用当前连接的sql语句的状态,很重要的列,后续会有所有的状态 的描述,请注意,state只是语句执行中的某一个状态,一个sql语句,已查询为例,可能需要经过copying to tmp tableSorting resultSending data等状态才可以完成,
  7. info列,显示这个sql语句,因为长度有限,所以长的sql语句就显示不全,但是一个判断问题语句的重要依据。

其中state列最关键,mysql列出的state主要有以下几种:
注:下图中未列出全部状态,如有些状态为查看服务器是否存在错误,未列出

  1. Checking table
  2.  正在检查数据表(这是自动的)。
  3. Closing tables
  4.  正在将表中修改的数据刷新到磁盘中,同时正在关闭已经用完的表。这是一个很快的操作,如果不是这样的话,就应该确认磁盘空间是否已经满了或者磁盘是否正处于重负中。
  5. Connect Out
  6.  复制从服务器正在连接主服务器。
  7. Copying to tmp table on disk
  8.  由于临时结果集大于tmp_table_size,正在将临时表从内存存储转为磁盘存储以此节省内存(如果临时表过大会导致mysql将临时表写入硬盘的时间过长,会影响整体性能)。
  9. Creating tmp table
  10.  正在创建临时表以存放部分查询结果。
  11. deleting from main table
  12.  服务器正在执行多表删除中的第一部分,刚删除第一个表。
  13. deleting from reference tables
  14.  服务器正在执行多表删除中的第二部分,正在删除其他表的记录。
  15. Flushing tables
  16.  正在执行FLUSH TABLES,等待其他线程关闭数据表。
  17. Killed
  18.  发送了一个kill请求给某线程,那么这个线程将会检查kill标志位,同时会放弃下一个kill请求。MySQL会在每次的主循环中检查 kill标志位,不过有些情况下该线程可能会过一小段才能死掉。如果该线程程被其他线程锁住了,那么kill请求会在锁释放时马上生效。
  19. Locked
  20.  被其他查询锁住了。
  21. Sending data
  22.  正在处理SELECT查询的记录,同时正在把结果发送给客户端。
  23. Sorting for group
  24.  正在为GROUP BY做排序。
  25.  Sorting for order
  26.  正在为ORDER BY做排序。
  27. Opening tables
  28.  这个过程应该会很快,除非受到其他因素的干扰。例如,在执ALTER TABLELOCK TABLE语句行完以前,数据表无法被其他线程打开。正尝试打开一个表。
  29. Removing duplicates
  30.  正在执行一个SELECT DISTINCT方式的查询,但是MySQL无法在前一个阶段优化掉那些重复的记录。因此,MySQL需要再次去掉重复的记录,然后再把结果发送给客户端。
  31. Reopen table
  32.  获得了对一个表的锁,但是必须在表结构修改之后才能获得这个锁。已经释放锁,关闭数据表,正尝试重新打开数据表。
  33. Repair by sorting
  34.  修复指令正在排序以创建索引。
  35. Repair with keycache
  36.  修复指令正在利用索引缓存一个一个地创建新索引。它会比Repair by sorting慢些。
  37. Searching rows for update
  38.  正在讲符合条件的记录找出来以备更新。它必须在UPDATE要修改相关的记录之前就完成了。
  39. Sleeping
  40.  正在等待客户端发送新请求.(Sleeping过多也是问题,比如wait_timeout设置过大,导致MySQL里大量的SLEEP进程无法及时释放,拖累系统性能,不过也不能把它设置的过小,否则你可能会遭遇到“MySQL has gone away”之类的问题)。
  41. System lock
  42.  正在等待取得一个外部的系统锁。如果当前没有运行多个mysqld服务器同时请求同一个表,那么可以通过增加--skip-external-locking参数来禁止外部系统锁。
  43. Upgrading lock
  44.  INSERT DELAYED正在尝试取得一个锁表以插入新记录。
  45. Updating
  46.  正在搜索匹配的记录,并且修改它们。
  47. User Lock
  48.  正在等待GET_LOCK()。
  49. Waiting for tables
  50.  该线程得到通知,数据表结构已经被修改了,需要重新打开数据表以取得新的结构。然后,为了能的重新打开数据表,必须等到所有其他线程关闭这个 表。以下几种情况下会产生这个通知:FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE,或OPTIMIZE TABLE
  51. waiting for handler insert
  52.  INSERT DELAYED已经处理完了所有待处理的插入操作,正在等待新的请求。

2)show status查询结果,状态值及含义

  1. Aborted_clients 由于客户没有正确关闭连接已经死掉,已经放弃的连接数量。
  2. Aborted_connects 尝试已经失败的MySQL服务器的连接的次数。
  3. Connections 试图连接MySQL服务器的次数。
  4. Created_tmp_tables 当执行语句时,已经被创造了的隐含临时表的数量。
  5. Delayed_insert_threads 正在使用的延迟插入处理器线程的数量。
  6. Delayed_writes INSERT DELAYED写入的行数。
  7. Delayed_errors INSERT DELAYED写入的发生某些错误(可能重复键值)的行数。
  8. Flush_commands 执行FLUSH命令的次数。
  9. Handler_delete 请求从一张表中删除行的次数。
  10. Handler_read_first 请求读入表中第一行的次数。
  11. Handler_read_key 请求数字基于键读行。
  12. Handler_read_next 请求读入基于一个键的一行的次数。
  13. Handler_read_rnd 请求读入基于一个固定位置的一行的次数。
  14. Handler_update 请求更新表中一行的次数。
  15. Handler_write 请求向表中插入一行的次数。
  16. Key_blocks_used 用于关键字缓存的块的数量。
  17. Key_read_requests 请求从缓存读入一个键值的次数。
  18. Key_reads 从磁盘物理读入一个键值的次数。
  19. Key_write_requests 请求将一个关键字块写入缓存次数。
  20. Key_writes 将一个键值块物理写入磁盘的次数。
  21. Max_used_connections 服务器启动后同时使用的连接的最大数目。
  22. Not_flushed_key_blocks 在键缓存中已经改变但是还没被清空到磁盘上的键块。
  23. Not_flushed_delayed_rows INSERT DELAY队列中等待写入的行的数量。
  24. Open_tables 当前打开表的数量。
  25. Open_files 打开文件的数量。
  26. Open_streams 打开流的数量(主要用于日志记载)
  27. Opened_tables 已经打开的表的数量。
  28. Questions 发往服务器的查询的数量。
  29. Slow_queries 要花超过long_query_time时间的查询数量。
  30. Threads_connected 当前打开的连接的数量。
  31. Threads_running 不在睡眠(激活)的线程数量。
  32. Uptime 服务器工作了多少秒。
  33. Uptime_since_flush_status 最近一次使用FLUSH STATUS 的时间(以秒为单位)