1.背景慢查询在MySQL数据库管理中已经是耳熟能详的事情了。只要我们在使用MySQL,慢查询就会一直存在,因为无论是业务APP还是MySQL,它们的状态都是动态变化的。在这种动态服务中,可能经常遇到的问题是某些指标的变化形成共振效应,导致慢查询语句变成了慢查询,本来可以经过二级索引返回的语句很快就变成了全表扫描,而且这个是没有限制的。这种影响的范围可能会继续进一步扩大,导致整个实例或集群死亡。有一些“受影响”的非常慢的查询语句,产生了我们通常意义上的“雪崩效应”,最终导致慢查询导致数据库故障。但是这样的故障真的是查询慢造成的吗?我不这么认为,具体原因很难穷尽,但在动态变化的环境中,多种因素导致共振效应,进而导致雪崩现象,这个应该是一定的。面对这样的问题,我们应该如何解决,或者提前避免呢?很多人可能会认为是共振引起的,是不是要找出具体的共振因素,问题才会迎刃而解呢?我想说的是,这个方案有时可以解决问题,但是通常在问题出现之后,共振场景就没有了,此时我们只能看到慢查询,除非有非常全面的日志,否则这个方法不行可操作的。2、解决方案对于以上问题,我们无法从原因上解决查询慢,那么是否可以考虑从结果上解决呢?结果是慢查询,也就是说我们是否只需要淘汰慢查询就可以避免相应的雪崩?答案是肯定的,我们可以想一想,如果发生了雪崩,也就是发生了雪崩导致的故障,我们该如何处理故障呢?一般情况下,通过不断的killslowquery,让拥塞消失也能解决问题。拥塞消失后,数据库本身状态平静,业务可以继续有序访问。因此,利用同样的原理,如果我们在共振出现后,当第一批慢查询出现时,将其杀死,这样可能会有效避免后续的雪崩效应。这样的推断是没有问题的。3.killslowqueries的办法很多人想到了上面的解决方案,就是killslowqueries,但是当慢查询刚出现的时候,压力不是很大,数据库可能扛得住,DBA可能不注意这个数据库“以后”会出问题,所以不会选择杀掉它。只有当它恶化,或者发生小面积雪崩时,我才会选择killslowqueries,但是这个时候kill它的逻辑很简单————只要是query,时间就是大于一定数量,可能会被kill掉,最差的就是多加几个过滤条件,比如状态是统计,方法类似,但是这种方法有很多缺点,我列举如下:1.它难以实现常态化。上面说了,在雪崩之前,不可能预测到现在的数据库很快就会失效。自动启动慢查询的动作,因为数据库是一个动态服务,当前的服务水平,以及服务能力需要通过综合指标来判断,包括CPU,内存,多实例相互影响,IO,并发数,Buffer等池的有效性等,即使是人工也很难判断,所以决定是否用规范化程序杀死慢查询基本上是无稽之谈。这种killslowqueries的方法只能在处理失败时使用,不能归一化。2.很难实现准确的查杀慢查询的方法。其实就是根据经验和综合指标来制定击杀慢查询的策略。杀了,但是请问,在多实例模式下,如何判断3306导致的Load增加不是3307?比如再考虑一下IO问题。如果发现某个时候IO特别高,那就开始查杀慢查询的操作。如果IO高,如何判断是哪条SQL语句导致了IO高?如果你不能判断,你是不是要把他们都杀了?当IO达到多少应该kill掉?30?50?还是100?这个谁来定义,假设定义是30,那我们要不要杀掉29?我如何确定30有问题而29没有问题?同时,对商业失败也有容忍度。有的业务可能到了30就出问题了,但是有的业务负载到了100,那么通过什么逻辑来区分不同的数据库呢?因为很明显,对不同的数据库使用同一个判断指标是非常不明智的。事实上,这里有两个指标的例子。我要说明的问题是,这个方法最终可能是乱杀。本身没有问题,却产生了很多问题。问题本身并没有解决。Excuseme,killit问题,谁负责?3、难以实现自动化。自动化有两种问题。一是自动决定要不要杀,杀什么。另一种是在DBA需要kill的时候启动自动化脚本kill。这里最重要的是问题是决策问题,什么时候开始的问题。显然,人的判断比机器判断可靠得多。目前,AIDBA不具备这样的能力。如果没有,就无从谈起自动化。它只能手动触发。显然,这是处理失败的问题。这不是提前避免或预防故障的问题。4、难以统一。上面说了,不同的业务线,不同的机器性能,不同的SQL语句,不同的数据量,不同的索引,不同的表大小,执行一条语句需要多长时间才能杀掉?扫描多少行需要杀死?需要杀死多少负载?需要杀死多少并发?相关指标很多,但核心问题是,这些指标有没有标准?达到这个标准会不会有问题?谁能做出这样的逻辑?谁敢定?达到某个标准就一定有问题,或者出事是商家的责任,那么不同的商家肯定有不同的指标。那么多的数据库实例,如何管理这些指标呢?这些都是问题。5、DBA不了解SQL语句DBA在运维过程中的作用。大多是对DB本身做一些运维工作,比如迁移、风控、故障处理、拆分、优化等,但是对于SQL语句本身,只有业务自己了解它的内部逻辑和相关逻辑语句之间等。执行一个语句很长时间是合理的,超过时间是不合理的。DBA看不懂这类资料,没有这方面的专业素养,而现在恰恰是决定把??SQL语句杀给DBA,这是荒谬的,也是不可信的。DBA无法做出这样的决定,除非此时数据库发生故障,这是故障排除的范围。因为DBA没有能力做这个,那就不要做,因为运维逻辑很简单,没有把握的事情DBA不做。6.企业不能做决定。如上所述,判断指标有很多。没有标准来判断一个指标达到某个值就出问题,低于某个值就没有问题。这个DBA没有办法定义。没有可用的经验值,但是如果要做这个,DBA也没办法,也无权决定是否杀掉慢查询,是否把这些参数的决定权交给业务开发去定义,比如他关心对于某个数据库,如果有慢查询,给出一系列的指标,当这些指标达到一定数量的时候,就会被kill掉。指标之间的关系是or或and,或者可以自定义的更细。系统很好,或者可以随意定制,但是问题来了,业务看到Load框,应该填多少?多少没有问题,或者给定一个数据库索引,比如并发量,多少就会有问题?这个时候,我认为业务是懵懂的,是跨域的。虽然你把决策权交给了他,但他是专业的,不能做这个决定。他怎么能把并发和失败联系起来呢?还是Load与失败有关?这更是无稽之谈。7.职责定义这种查杀慢查询的方法上面已经说的很清楚了。如果你不知道如何去执行,那不是白费力气吗?这种方法有一个很大的问题,就是误杀、错杀、失败的情况很多,谁来负责?这意味着出现的情况是各种扯皮,各种推卸责任,之后可能会进入没完没了的调整运动,让DBA/开发成功华丽转身,请叫我们调整工程师。8、数据库的核心是服务的最后一个问题。其实就是DB的核心作用。作为DBA或者了解DBA的人都知道,在操作系统、手机系统、APP等系统中广为人知的运维三招之一就是:重启,Rebootingdoesthetrick。但是数据库不是这样的。数据库管理数据,重启带来的问题可能更大。所以数据库的运维思路就是尽可能让他活着,尽可能让他活着。这样做的目的只有一个,就是更好的服务业务,所以任何时候,我们首先想到的就是提供服务,而不是宁愿误杀,也要在一定情况下进入拒绝服务状态。这种思路是有问题的,不符合数据库运维的思路。的。上面提到了综合查杀慢查询的各种弊端。显然,这是不靠谱的,不负责任的,后患无穷,解决不了问题。我们要时刻牢牢守住这个底线,不能再这样了,否则一定是徒劳无功,吃力不讨好。那么有没有一种方法可以有效避免上述所有问题,同时解决慢查询带来的各种问题呢?答案是肯定的。4.系铃解铃的人我们需要换个角度思考问题。语句由开发者编写,语句由应用程序访问。SQL语句的情况只有应用端或者开发才能了解,包括执行需要多长时间(SQL语句的超时设置),或者执行需要多长时间,肯定是有问题的,而且他们可能不知道语句相关的其他参数,他们只知道这些信息如何杀死缓慢的查询?方法有以下三种:1、注册DBA开发一个“强大”的SQL语句注册系统。指标包括数据库地址,最大执行时间,其他可选。一个Agent不断访问配置库,同时读取Processlist中的信息。如果语句和时间可以匹配,则将其杀死。这种方法当然比上面的方法要好很多,至少执行杀戮动作是有决策依据的。这个决策依据是基于业务对自身报表的理解和健康的风险控制,这样可以有效避免在突发情况下相互拥塞导致的数据库雪崩问题,至少在雪崩这种拥塞状态下是可以的解决之前。但是这种方法也是有问题的。久而久之,配置库就会非常庞大??,因为在极端情况下,线上出现的每条SQL语句都会出现在这里,杀慢查询的Agent将无法很好的运行和匹配,更重要的是整个SQL语句,涉及模式处理问题和繁重的字符串比较问题,效率无法保证。因此,这种做法是不可行的。2、系统签名还有一个更好的方法,就是在SQL语句中添加注释,类似这样的形式:/*!9999921B2438F55killmewhenquery_time>10appcomments*/selectsleep(10);设计细节如下:1)99999代表MySQL版本,99999大于当前所有MySQL版本,所以注释中的内容会被MySQL忽略,所以这是MySQL支持的方式。2)后面的MD5值是签名用的,主要是防止误杀。这里填了一个MD5。如果碰巧是一样的,是不是可以买彩票呢?因此,这个MD5值可以很好的用于DBA进行句子识别,而不是比较整个字符串。该值被识别后,将解析其他信息。如果找到匹配项,将执行查杀操作。3)下面的“killmewhenquery_time>10”类似于一个协议内容,明确表示这条语句应该开启killslowqueries的服务。这里,10可以由业务自己定义。可以任意定义,以秒为单位,定义值的选择需要慎重考虑。可以参考业务正常执行的历史时间,或者业务流程的最大容忍正常时间,大致定一个值,因为当出现异常情况时,需要执行这条语句。时间肯定会比这长很多,他们肯定会被干掉。当然,如果设置过大,杀不死也是有问题的。如果设置为秒,慢查询是否及时取决于数据库后台慢查询程序的执行频率。如果是每5秒一次,那么精度就是5秒。如果是每秒一次,则精度为1秒,可以自由控制。4)后面的“应用评论”部分,业务程序可以随便写,Agent不会分析,或者可以不写。主要用于一些评论功能。先说说这种方法的优缺点:1)精确:很明显,这种方法是针对语句级别的。谁想要使用这样的服务,就应该在声明前签名并写上时间。会被杀死。因为有签名,遵守相关协议,所以不存在业务程序和DB之间职责不清的问题,合作可以很愉快。2)规范化:通过这种方式,可以在数据库本地部署一个代理,每隔几秒检查一次数据库执行状态。如果它可以匹配一个慢查询,它就会杀死它。如果不匹配,它将白运行。动作轻,影响小,可以有效避免问题。3)有效风控:匹配到需要杀掉的语句后,就可以放心杀掉了,因为这是业务根据自己的逻辑和预期设定的时间。就算杀掉也不会有什么问题,风险可控,关键是要避免异常语句带来的问题,因为我们杀掉慢查询的目的就是为了应对异常情况下的慢查询。4)业务决策:业务做自己擅长的事情,DBA在这个过程中没有任何决策工作,双赢。5)效率高:与上述方法相比,该方法的配置在SQL语句中,执行时间值只有一个,非常容易解析出来,而且大多数情况下数据库状态正常,而且没有什么语句需要杀掉,所以效率很高。Agent本身不需要依赖其他模块,简单易推广。6)Manslaughter:这种方法唯一的风险是connectionid被用来kill一个statement。匹配到一条需要kill的statement后,kill动作执行的时候,statement刚好执行完,这时候恰好这个Connection执行了一条新的statement。当它被杀死时,它并不知道这是一个新的语句。这时,被杀的是一个新的陈述,从而导致误杀。但实际上,你应该考虑一下。这种可能性很小。匹配和查杀的时候,时间差应该是几毫秒。在这几毫秒的时间窗口内,误杀的概率可以忽略不计,但这是一个潜在的风险,需要提前考虑。7)推广度:该方法有准入门槛,但实际上门槛高度是有限的。如果所有的服务都可以访问,那么数据库的整体运行会非常顺畅,异常问题会提前发现和避免,不会重蹈覆辙。有很大的雪崩效应。当然这个结论需要时间去验证,业务需要填写一个相对合理的时间。3、源码系统还有另一种实现该功能的方法,即修改MySQL源码。其实业界已经有一些这样的团队做了这样的事情,但是最基本的逻辑没有变,需要业务开发。自己在SQL语句中设置超时值。Mysql服务在执行的时候发现这个值是通过解析语句设置的,在执行过程中会不断的查看执行时间。如果超过设定的时间,它将被删除。kill,从而实现这种SQL语句执行超时的机制。但显然,这样的实现门槛很高,需要不断修改源码和维护源码。很少有人能做到这一点,而且我觉得在运维和使用MySQL的过程中,如果有什么需要,可以通过MySQL的native方法(外设方法)来解决,所以不要改源码解决,因为通过外围的方式解决风险会低很多,而且自由可控,不需要对MySQL服务本身做太多的干预。解决方案过程非常轻巧且易于使用。五、小结综上所述,我们对于杀死慢查询还是需要非常谨慎的。第一个原因是提供服务。所以,我们要保证查杀准确,及时,查杀不出问题,所以这件事本身就是一个复杂的问题。上面推荐的解决方案其实就是给一条SQL语句设置一个比较合理的超时时间,这个很容易理解。你可以考虑一下。写代码的时候,超时时间不是随处可见吗??如果也可以为SQL语句设置一个超时时间,这样可以更好的保护数据库的稳定运行,何乐而不为呢?DBA是服务型的工种,很想为业务解决一些头疼的问题,但是解决问题的时候不能只见树木不见森林,需要站在一定的高度看问题,而你需要找到一个合适的方法才能很好的解决问题,否则可能就是在制造问题。如果找到好的方法,通常可以达到事半功倍的效果。把复杂的问题简单化,做自己擅长的工作,DBA也做力所能及的工作。分工明确,合作共赢。从长远看,一定会形成稳定、健康、良好的服务环境。作者介绍王竹峰,去哪儿网数据库负责人。擅长数据库开发、数据库管理和维护,一直致力于MySQL数据库源码的研究和探索,对数据库原理和实现有深刻理解。曾就职于大梦数据库,从事数据库内核开发多年,后转入人人网任高级数据库工程师。目前在去哪儿网负责MySQL源码研究与运维、数据库管理与自动化运维平台设计开发与实战工作,Inception开源项目作者,《MySQL运维内参》,OracleACE在MySQL的方向。
