一次慢SQL问题导致CPU100事故
一次数据库问题导致CPU100事故
背景
因为某些不可抗因素,公司的一个项目需要提供一个接口完成对另一个数据库的全表同步。
由于是增量同步,因此在没有时间戳和记录轴的比较关系情况下对另一张表进行全表,只有数据与数据逐条比较的可能。
然后重点来了,因为是公司内部使用的系统,类似报表统计。
其同步脚本全全由一个实习生开发,然,这个功能在开发之初到半年内都可以正常使用。
数据随着时间的增加,终于在最近发生了项目所使用的数据库服务CPU100的P0级别事故。
起因
先说一下这个事故产生的原因;
很简单,表A同步表B的流程中。
有一个从表B拿到一条数据,然后在表A中组装成条件查询的SQL;因为表的特殊性,这里必须在表B的for循环中一次一次查询。
即表B假如有10W的数据,需要做如上10W的查询。
还有一个最重要的原因,大伙对这个功能并不知晓,只知道有这个功能。
因此在做10W次查询,然后每次查询都是3S的条件中,服务器也终于崩溃了。
解决方式
从阿里巴巴的SQL监控日志中可以看到,一条相同的SQL语句执行了8000多次,每次执行3S。
先解决当前服务器问题:
因为可以很明确的看出来是数据库问题导致服务器CPU100,而这出现的原因无非是两种:
- 一直在进行执行动作
- 数据库连接池占满,连接一直被请求,需处理连接请求
不管是哪个原因,解决的方案都很简单,两个:
- 找到一直在执行SQL的语句,直接KILL掉
- 找到占据过多连接的应用,释放数据库连接,将其重启的同时,把问题应用停止直到解决。
从起因中可以很简单的分析出,首先是循环查询10W次这个问题。
在复盘分析中得出,可以接收这个数据量的循环内查询;但是问题则是出在很简单的问题:SQL语句未走索引,导致50W数据量的表,特定的3个条件查询原本只需要10ms,最终用了3S。
最终将表加索引,优化SQL,重启应用后,问题解决。
复盘
一个非常低级的,由于一个临时工未加索引导致的P0级事故。
复盘中查阅代码,发现了一个分析时被绕进复杂思路的问题:
连接的数据库连接数是300,服务有10个左右的应用,每个设置的最大连接池为20。
在这次循环查询中,因为不存在并发的问题,慢SQL虽然慢但也是一条一条执行的。
那么,为何会出现CPU100,并且数据库连接池被占满的问题。
首先是一个原理性的问题:
当MySQL服务器上的某些SQL查询执行得非常慢时,它们可能会消耗大量的CPU资源,甚至导致CPU使用率达到100%
而当CPU100后,数据库没有性能支持任何的连接动作,因此返回给所有的连接请求响应都是超时。
这也是为什么数据库 "连接池占满"的问题。
复杂的来了:
- 为什么数据库执行慢SQL,就会消耗大量的CPU资源:例如我有100次查询每次查询都是3S,不是应该每次查询一次后就会释放资源吗,那么最终消耗的时候是一种3S的SQL资源。
- 后续复测中,明明只需要8000条左右的循环数据就会导致CPU100,为何过去调用同步功能时未出现崩溃问题。
解答:
1、虽然每次查询执行完毕后,相关的资源(如内存和CPU时间片)会被释放回系统,以供其他进程或查询使用,但慢查询在执行期间会持续占用资源,如果这样的查询频繁发生或并发执行,累积的资源消耗就会非常高。并且在连续的查询中,指for循环嵌套查询中,出去SQL执行本身的问题,虽然有线程池复用的优势,但是频繁请求数据库的网络带框资源的消耗是不可忽略的。并且慢SQL时,是需要建立一次长时间的IO连接的。
2、涉及Mybatis-一级缓存的特性:
在一次事务中,对相同的表进行相同的查询的数据会被缓存
即第一次条件A,查询出B,当第n次条件未A时,会检查是否存在该条件以及表的缓存,如有则不进行数据库DB操作,直接使用本地缓存。
过去没有出现的问题,则是因为很多数据的条件非常巧合的一模一样,过滤掉了非常多的数据