最近又遇到了一次慢查把db(mariadb10)几乎打挂的案例,做为一个核心支付系统的技术负责人,真是每日如履薄冰。由于以前支付系统常常出问题,如今各个BG对支付系统都盯得很紧。此次要不是我及时让DB给暴力清理数据,没准又提一个P2故障;mysql
抱怨归抱怨,过后复盘,一丝都不能马虎。首先,描述一下故障的全过程。原由是咱们支付系统有一个异步队列,这个队列使用的一张mysql表存储,异步回调业务线的任务(姑且表名称叫task),都会首先放这里。同时这个task表还有其余的异步任务,不一样的任务使用task_type字段来进行区分。而后应用系统有一个定时任务,扫描这张表是否有待消费的任务,若是有,则会取出来进行消费;典型的生产者消费者模型;sql
这里的task说的再具体一点:数据库
一、全部的异步任务都在这张表,有支付成功通知业务线消息,有给结算系统推送支付信息的任务;架构
二、消费者在任务处理成功后,则会把任务从task表删除。因此这张表常常是空的;并发
消费者根据不一样的任务,调用不一样的上游订单系统和结算系统。出故障时,是由于推送支付信息的结算系统接口超时,出了问题,致使任务被积压到了task表。异步
任务积压以后,消费者线程池很快就被积压的任务占满,致使应该通知BG订单支付成功的任务也被block住,进而影响到订单支付成功率。线程
当时我已经意识到,咱们的消费者线程池隔离没有作到位,马上找DBA将推送给结算系统的任务进行了备份并清理。而且嘱咐DBA定时清理推送结算任务的数据。这样才化解支付成功率继续下滑的趋势。3d
危机解除后,咱们和DBA配合进一步排查问题,找出了事情的根本缘由。日志
原来是推送结算信息的逻辑中,有一个对task表的查询,而这个查询的sql,没有建索引。这样当这类任务数量积压的比较多时,查询会愈来愈慢,慢查致使mysql堵塞。堵塞致使消费者没法拉取任务,进而影响到其余通知BG的任务的消费;咱们分析了一下日志,其实咱们的程序查询数量当时3分钟大概查询了1万屡次,能够说qps很少。可是问题出如今sql没法命中索引,把mysql的worker thread都用完了。给咱们研发的感受,mysql是如此的脆弱,2w多条数据,查询没有索引,几千个select,就能把它打挂。blog
几乎相似的案例,一年前,咱们也碰到过一次。当时支付系统有一个bug,用户每支付一次,都会把支付客史中一个月以前的数据都清理一下(1月1日,清12月1日以前的数据,2月1清理1月1日以前的数据)。这个bug藏的很深,这块代码也不多有业务需求,一直没有被发现。可是,是雷就会有爆炸的一天。3月1日凌晨,支付系统忽然全部接口都挂了。DBA最终定位是删除支付客史的sql。这个问题,咱们研发一开始是不认可的,毕竟这个sql,在线上跑了2年多,一直没有出过问题。DBA说这个delete语句删3000w数据,并且在不断的请求,数据库固然扛不住,咱们反驳说,这个客史表一共才3000w数据。过后咱们发现,DBA的描述有误,不是说删除3000w,而是这个delete语句没有走索引,每次要扫描3000w数据。这样才能解释通,也就是说,这个delete进行了全表扫描。而实际上,这个delete是按照时间删除的,而且时间字段是有单列索引的,可是为何这个delete没有走索引呢?咱们最后猜想,多是由于2月份天数太少致使。之前,可能数据比较少,每次删一天,或者2天的数据,mysql可能会走索引。可是3月1日比较特殊,因2月28日删的是1月28日一天的数据,3月1日却要删除1月29,30,31三天的数据,mysql可能认为删除这么多数据,没有必要走索引了。
遇到相似问题如何解决?
一、读写分离。
二、提早消灭慢查询;
三、对异步任务作好线程隔离;
关于mysql的线程池,我最近也了解了一下,收获也不小,给你们推荐一篇好文章;
https://www.jianshu.com/p/88e606eca2a5
关注个人公众号“猿界汪汪队”,关注大并发架构实战。