前言
最近遇见一个很奇怪的线上问题,就是生产环境MySQL(使用的是阿里云的RDS,版本号是5.7)的CPU莫名其妙地会被打满,导致整个服务不可用,最终耗费了很大的精力才定位到出现这个问题的原因。出现这类现象是因为当MySQL的time_zone=SYSTEM时,查询timestamp时会调用系统时区做时区转换,而系统时区存在全局锁,在并发大数据量访问会导致线程上下文频繁切换CPU使用率飙升,系统响应变慢。在排查问题时也在网上找了很多资料,但大都不是同一种问题,所以为这次排查做一次分享。本文为技术分享,可能有误望谅解。
问题现象
出现这个问题的业务逻辑是查询一张存储用户搜索词的表,统计出最近七天搜索最多的几个关键词。在正常情况下查询正常,如果并发量增加RDS的CPU使用率飙升并且出现慢查询,这条SQL耗时能达到10s以上。表中的数据在20w左右,存在一个联合索引,正常查询会走索引,数据表的表结构如下:
1 | ***只列出了两个需要查询的字段*** |
使用的查询语句:
1 | SELECT |
问题复现
为了复现问题,首先将线上的20w数据导入测试环境(测试环境的MySQL没有使用阿里云的RDS机器,而是搭建在应用服务器上),然后对查询接口进行压测,开启20个线程请求测试环境,mysqld的CPU使用率马上就飙升,出现了和生产环境一样的问题。
排查步骤
首先连接上数据库,使用show processlist命令查看当前正在运行的线程,发现大量的查询处于sending data状态,sending data状态是正在读取和处理一个select语句的行,并发送数据到客户端,因为可能会发生物理读,该状态可能是给定生命周期时间最长的。
show processlist查看正在运行的线程,如果你有process权限,能够看到所有的线程,如果没有权限只能看到自己的线程
1 | show processlist; |
接下来使用show profile查看详细的查询耗时,使用show profile需要使用set profiling=1打开配置
1 | set profiling=1; |
将异常的sql执行一次,再使用show profiles;得到query_id
1 | SELECT a.keywords FROM(SELECT k.keywords,count(1) c FROM tb_search_keywords k WHERE DATE_SUB(CURDATE(), INTERVAL 7 DAY) <= k.t_crt_tm AND LENGTH(k.keywords) > 3 GROUP BY k.keywords) a ORDER BY a.c DESC LIMIT 8; |
得到query_id后使用show profile all for query Query_ID查看语句工作情况和耗时,很明显sending data状态的Context_voluntary的值十分大,达到了1223336,Context_voluntary指的时上下文主动切换次数,一般是越小越好。
1 |
|
到这里已经差不多快接近定位到是由于什么原因了,只要知道是什么会导致Context_voluntary偏大,经过不断地查找资料,终于找到一篇细说MySQL的timestamp的文章。
文中指出:当MySQL参数time_zone=system时,查询timestamp字段会调用系统时区做时区转换,而由于系统时区存在全局锁问题,在多并发大数据量访问时会导致线程上下文频繁切换,CPU使用率暴涨,系统响应变慢设置假死。
查到可能的原因马上尝试一番,使用show variables like ‘%time_zone’命令查看time_zone的值,果不其然time_zone的值是SYSTEM。
1 | show variables like '%time_zone'; |
因为mysqld进程是在应用服务器上,可以使用linux的pstack命令来查看各个线程的状态,也很容易看到是因为时区转换存在全局锁,pstack信息如下:
1 | Thread 2 (Thread 0x7fc5a702c700 (LWP 11259)): |
搜索__tz_convert发现19个线程都是处于请求锁的状态,前文提到使用了20个线程对接口压测,则证明了是存在锁。
修复方案
一:将time_zone参数设置为system外的值,如中国地区服务器设置为’+8:00’;
- 修改time_zone是最直接的方式,这样就不会访问linux的系统时区,不会存在锁
- 修改time_zone在业务场景对时区有要求时不适用