【影响范围】
所有用户不可登录,21:00左右恢复登录,但某业务服务A不可用,且恢复的业务功能也不能稳定服务
【故障表现】
18:30开始,收到大量服务报警,随后服务压力井喷式上升,CPU使用率接近800%,最后全网服务不可用
【处理过程】
2020-08-28 18:30 左右发现大量服务报警,业务服务处于不可用状态。业务服务数据库压力陡增,CPU 使用率近 800%。
通过 show processlist 和 show engine innodb status 查看数据库线程信息和 InnoDB 引擎状态,发现大量的查询被阻塞。但是并没有明显的证据表明是哪个 SQL 引起的。
由于执行过大表的 RENAME 操作,初步怀疑问题跟 RENAME 有关,为了快速恢复业务服务,期间重启了数据库服务,然而并没有什么效果。
随后执行了数据库的主从切换,将从库作为主库提供服务,发现从库压力陡增,所以初步排除是数据库服务器本身的问题。
实际上 RENAME 操作是速度很快的,秒级完成,不会引起锁表,可以放心执行。
至此,尝试过了重启数据库,切换从库,问题均没有任何缓解,排除数据库自身问题,那么问题很有可能就是由于慢查询引起的。
但是此时数据库中存在大量慢查询等待,涉及到的服务众多,最终只能通过排除法逐个排除,有一些公共服务被阻塞的请求比较多,初步怀疑是这两个服务引起的,关闭服务后发现问题依旧没有解决。
后来了解到当晚是对 某个数据库A的表结构做了变更,而且上线的了几个服务都与这个数据库有关,因此开始怀疑是这个数据库A账号引起的。但是由于是服务重构过度期间,使用这个账号的服务有很多,
为了节省处理问题的时间,将数据库A账号的所有权限回收,以便快速验证
REVOKE ALL PRIVILEGES ON database_name
.* FROM ‘database_name’@‘%’;
强制 flush 权限,以断开所有的数据库连接,使权限变更立即生效
FLUSH ALL;
权限回收后,发现数据库负载下降,确定问题是由于 数据库A 这个账号的某些查询引起的,此时,由于服务器压力恢复正常,部分用户的访问已经恢复。
接下来就是排查出是哪个服务的问题了,将涉及到的几个服务全部关闭,重新赋予数据库A账号权限
GRANT ALL PRIVILEGES ON database_name
.* TO ‘database_name’@‘%’
逐个重启服务,最终在重启服务a之后,数据库压力陡增,确定问题出在这个服务上,此时恢复其他服务。除服务a其他业务功能均已恢复正常。
定位到了服务,但是问题的根本原因还是没有找到,经过大家的讨论,发现对该服务用到的两个表做了字段类型的变更(表字段类型变更,但是 SQL 没有做相应的调整,查询字段类型与数据库中的类型不一致会造成索引失效,无法利用索引来做查询优化),尝试恢复原来的表结构以验证问题是否能够解决
再次重启服务a,一切恢复正常。
可以看到,在问题排查过程中走了很多弯路,原因在于在最开始没有对故障发生前的所有上下文进行梳理,缺乏一些关键的问题排查信息,导致病急乱投医。所以在遇到线上故障没有思路,无法快速解决的时候,一定要沉下心来,纵观全局,收集事故发生前的上下文信息,做出更加明智,正确的决策,减少故障造成的影响。
【原因分析】
数据库 A中两个表中有个字段的类型由 int 调整为 varchar 导致 SQL 中字段类型与数据库不匹配,无法命中索引,产生大量慢查询,数据库压力过大。
【改进措施】
重大上线必须进行全面的方案评审
数据库大表结构变更一定慎之又慎,先在开发、测试环境验证(数据量规模与正式一致)
优化监控机制,对慢查询 SQL 做严重级别分类,严重影响性能的慢查询作为错误报警发出
建立数据库自身保护机制
对服务分级 ,重要服务的运行环境、数据库等做隔离,重点保护
如果故障发生时有过上线行为,则5分钟内找不到故障原因,立刻开始回滚上线
对于大的上线,在上线方案准备时,必须同步规划回滚方案
在排查问题的时候,发现 某个表上建立了很多的索引,其中 idx_is_confirm 这个索引时可以删掉的,删除该索引可以提高数据库的 insert/update 性能。
在建立索引的时候,除了考虑 SQL 中会用到的字段基于 最左前缀匹配 规则之外,还要考虑该字段的基数是否足够大(Cardinality),如果基数很小,建立索引带来的收益就会很小,甚至影响 insert/update 性能,得不偿失,因此建议对于可枚举的字段,不要建立索引。