慢查询在 MySQL 只要我们使用它,数据库管理就已经很熟悉了。 MySQL,慢查询将永远存在,因为无论是业务还是业务 APP,还是 MySQL,他们的状态是动态变化的。在这项动态服务中,可能经常遇到的问题是,某些指标的变化形成了共振效应,导致缓慢查询句变成缓慢查询,本可以通过二次索引和快速返回的句子变成了全表面扫描,这可能会继续扩大影响范围,导致整个例子或集群被杀死,一些影响缓慢的查询句子,从而产生我们通常意义上的雪崩效应,最终导致数据库故障。
但是这样的故障真的是慢查询造成的吗?我不这么认为。具体原因很难耗尽,但应该确定的是,在一个动态变化的环境中,多种因素导致共振效应,进一步导致雪崩。面对这样的问题,应该如何解决,或者提前避免?很多人可能会认为是共振引起的,是否要找出具体的共振因素,这样问题才能解决?我想说的是,这种解决方案有时可以解决问题,但通常在问题发生后,共振场景消失了,我们只看到慢查询,除非有非常全面的日志,否则这种方法是不可操作的。
对于上述问题,我们不能从原因上解决慢查询,所以我们能考虑从结果上解决吗?结果是慢查询,也就是说,我们只需要消除慢查询,就可以避免相应的雪崩?
答案是肯定的。我们可以考虑如果雪崩出现,也就是雪崩引起的故障,我们如何处理故障?一般来说,通过不断杀死缓慢查询来解决问题,让拥堵消失,拥堵消失,数据库本身平静,业务可以继续有序访问,所以同样的原则,如果我们能在共振后,第一批缓慢查询,杀死它,可以有效避免随后的雪崩效应,这样的推论没有问题。
很多人想到了上面的解决方案,就是将慢查询杀掉,但在慢查询一开始出现的时候,压力还不大,数据库可能可以扛过去,DBA 可能不会注意到数据库未来会出现问题,所以不会选择杀死,只有恶化,或已经有一个小雪崩,会选择杀死慢查询,但杀死逻辑非常简单——只要是查询,时间超过多少,可能会被杀死,增加更多的过滤条件,如状态是 statistics,方法是一样的,但是有很多这样的方法,我列举如下:
正如上面所说,在雪崩之前,无法预测当前数据库即将出现故障。由于数据库是一种动态服务,需要综合各种指标来判断当前的服务水平和服务能力,包括 CPU、内存、多实例相互影响,IO、并发个数、Buffer Pool有效性等,即使是手动判断,也很难做到,所以正常程序决定是否杀死慢查询,基本上是胡说八道。这种杀慢查询的方法,只能用来在处理故障的时候使用,而不是常态化。
事实上,这种杀慢查询的方法是根据经验值制定杀慢查询策略,综合各种指标,如机器 Load,或者 CPU 当利用率达到多少时,它开始被杀死,但在多实例模式下,你如何判断它? 3306 导致的 Load 而不是 3307 呢?比如再考虑 IO 如果在某个时间发现问题, IO 特别高,然后开始杀慢查询,IO 如何判断哪个高? SQL 语句导致的 IO 高?如果你不能判断,你想把它全部杀掉吗?IO要杀多少次?30?50?还是 100?谁来定义这个假设? 30,那 29 要不要杀?如何确定30有问题, 29 没问题?同时,业务出现故障,还有一种容忍度,一些业务 30 可能有问题,但有些业务可能有问题,但有些业务 load 到达 100 没问题,用什么逻辑来区分不同的数据库?很明显,不同数据库使用相同的判断指标是非常不明智的。事实上,这里有两个指标的例子。我想解释的问题是,最终的方法可能是随机杀死它。它本身没有问题,但它造成了很多问题,但问题本身并没有得到解决。谁负责杀死问题?
自动化有两个问题,一个是自动决定是否杀死,杀死什么,另一个是需要杀死的时候 DBA 启动自动脚本杀戮,这里最关键的问题是决策,什么时候启动。显然,人的判断比机器判断可靠得多。 AIDBA 没有这样的能力,如果没有,就不能谈论自动化,只能手动触发,显然这是处理故障的问题,而不是提前避免或预防故障的问题。
正如上面提到的,不同的业务线,不同的机器性能,不同的 SQL 句子,不同的数据量,不同的索引,不同的表大小,一个句子需要执行多久才能被杀死?扫描需要杀死多少行?Load 需要杀死多少?并发量需要杀死多少?但核心问题是,这些指标是否有标准?达到这个标准肯定有问题吗?谁能设定这样的逻辑?谁又敢定呢?如果达到一定标准,肯定会有问题,或者业务在误杀后自行承担责任,那么不同的业务肯定会有不同的指标,那么如何管理这些指标呢?这些都是问题。
DBA 大维过程中的角色大多是做的 DB 迁移、风险控制、故障处理、拆分、优化等一些处理、拆分、优化等。 SQL 句子本身,只有业务自己理解其内部逻辑和句子之间的相关逻辑,句子执行多少时间是合理的,多少时间是不合理的,这个信息 DBA 我不明白。这方面没有专业性。而现在正是杀戮 SQL 语句决策交给 DBA,荒谬,不可信,DBA 除非数据库此时出现故障,否则不能做出这样的决定。因为 DBA 没有能力去做这件事,那就不要去做,因为运维逻辑很简单,DBA 不做没有把握的事情。
正如上面提到的,判断指标包括很多。如果没有标准确定一个指标达到一定值,肯定会有问题,或者如果小于一定值,肯定没问题。 DBA 没有办法定义,也没有经验值可用,但如果这件事要做,DBA 没有办法做到,也没有权利决定是否杀慢查询,是否可以把这些参数的决策权交给业务开发自己定义,比如他关心的数据库,如果有慢查询,给出一系列指标。当这些指标达到时,杀死它们。指标之间的关系是否可以精细的定制,系统非常精细,或者可以随意定制,但问题是,业务看到 Load 这个框应该填多少?有多少没有问题,或者给出一个数据库指标,比如并发量,会有多少问题?有多少没有问题,或者给一个数据库指标,比如并发量,会有多少问题?此时,我相信业务是无知的,这是跨领域的。虽然你已经交出了决策权,但他不能在专业上做出这个决定。他如何将并发度与故障联系起来?或者 Load 与故障有关怎么样?这更是无稽之谈。
这种杀慢查询的方式,上面已经说得很清楚了,没有优点只有缺点,花了很大的努力也惹一个风骚,吃力不讨好,做了一个非常强大的系统,最后不知道怎么开始,不知道怎么执行,那不是白费力气吗这样有一个很大的问题,就是有很多错杀问题,杀错了,出了问题,谁负责?这意味着情况是各种争吵,各种逃避责任,然后,可能进入无尽的参与运动,所以 DBA /开发成功地做了一个华丽的转身,请让我们参考工程师。
最后一个问题其实是 DB 作为核心作用 DBA 或者了解 DBA 大家都知道,操作系统,手机系统,APP 以系统著称的运维三板斧之一是:重启,重启很有效。然而,数据库并非如此。数据库管理数据,重启可能会导致更大的问题。因此,数据库的操作和维护理念是让他尽可能地生活,让他尽可能地生活。这样的目的只有一个,那就是更好的服务业务。因此,在任何时候,我们的第一个想法是提供服务,而不是错误地杀死它,在某些情况下,我们也应该进入拒绝服务的状态。这个想法有问题,不符合数据库运维思路的。
以上介绍了杀慢查询综合方法的各种缺点。显然,这是不可靠的、不负责任的、无尽的麻烦和不解决问题。我们应该始终坚决保持这样的底线,不要做这样的事情,否则一定是徒劳的,吃力不讨好。
有没有办法在有效避免上述所有问题的同时解决慢查询带来的各种问题?答案是肯定的。
当我们这样做时,我们需要改变我们的想法来考虑问题。句子是开发写的,句子是从应用程序访问的。只有应用程序端或开发才能理解 SQL 句子的情况,包括需要执行多长时间(SQL 语句的超时设置),或者执行多久,肯定是有问题的,其他与语句相关的参数可能无法知道。当他们只知道这些信息时,如何杀慢查询?有三种方法:
DBA 为注册开发强大系统 SQL 有了这个系统,语句、指标包括数据库地址、最大执行时间等。DBA 可以在每个数据库上做一个 Agent,不断访问配置库,同时查看 Processlist 如果句子和时间能匹配,中间的信息就会被杀死,当然,这种方法比上述方法要好得多。至少执行杀戮行动是有决策依据的。这一决策依据是基于业务对自己句子的理解和对健康风险的控制。这样,数据库雪崩问题就可以有效避免意外拥堵造成的数据库雪崩问题,至少在雪崩之前就可以解决这种拥堵状态。然而,这种方法也有问题。随着时间的推移,这个配置库将非常大,因为在极限条件下,每一个在线出现 SQL 句子会出现在这里,杀慢查询。 Agent 没有办法好好运行,匹配整个 SQL 语句涉及模式处理和字符串比较,效率无法保证。然而,这种方法也有问题。随着时间的推移,这个配置库将非常大,因为在极限条件下,每一个在线出现 SQL 句子会出现在这里,杀慢查询。 Agent 就没办法良好运行了,并且匹配的是整个 SQL 语句涉及模式处理和字符串比较,效率无法保证。因此,这种方法也是不可行的。
还有一更好的方法是 SQL 在句子中加注释,类似于这种形式:
/*!99999 21B2438F55 kill me when query_time > 10 app comments*/ select sleep(10);
1. 99999 表示的是 MySQL 版本,99999 大于现在所有的 MySQL 版本,所以注释里面的内容就会被 MySQL 忽略,所以这用的是 MySQL 所支持的方式。
2. 后面的 MD5 值,是为了做签名的,主要是为了防止错杀的,一个 MD5 填在这里,如果能碰巧雷同了,那是不是可以买彩票了。所以这个 MD5 值,可以很好地用来给 DBA 做语句识别的功能,而不用去比较整个字符串了,识别到这个值之后,再去解析其它信息,匹配到了,则执行杀的动作。
3. 后面的 "kill me when query_time > 10",类似是一个协议内容,明确表示这个语句要启用杀慢查询的服务,这里的 10 是可以由业务自己定义,想定义多少都可以,以秒为单位,定义值的选择,需要慎重考虑清楚,可以参考业务正常执行的历史时间,也可以参考业务流程最大容忍的正常时间,大致设置一个值就行,因为当出现异常情况的时候,这个语句需要执行的时间肯定都会比这个大不少,肯定就被杀掉了。当然如果设置太大,导致没有杀掉,也是有问题的,以秒为单位设置的话,杀慢查询是不是及时还要决定于数据库后台杀慢查询程序的执行频率,如果 5 秒一次的话,那就精度是 5 秒,如果是1秒一次的话,精度就是 1 秒,可以自由控制。
4. 后面 “app comments” 部分,业务程序就可以随便写了,Agent 也不会做解析,也可以不写,主要用来做一些注释功能。
很明显,这种办法是具体到了语句级别,谁想要使用这样的服务,就在语句前面做签名,写上时间,不写的不会被杀掉。因为有签名,遵守了相关协议,业务程序和 DB 之间不存在责任界定不清楚的问题,合作可以很愉快。
这种办法就可以在数据库本机部署一个 Agent,专门每隔几秒去检查一次数据库执行情况,如果能匹配到慢查询就杀,匹配不到就白跑一次,动作轻量,影响不大,可以有效避免问题的出现。
当匹配到了需要杀的语句之后,也可以放心地杀掉,因为这是业务根据自己的逻辑及预期设置好的时间,即使被杀了,也是不会有问题的,风险可控,关键是可以避免异常语句引起的问题,因为我们杀慢查询的目标,就是要处理异常情况的慢查询。
业务做了自己擅长的事情,DBA 在这个过程中没有任何决策的工作,是一个双赢的局面。
相比上面的方式,这种方式的配置都在 SQL 语句中,并且只有一个执行时间值,非常容易解析出来,并且大部分情况下,数据库状态都是正常的,并没有什么语句需要杀掉的,所以效率是非常高的。Agent 本身并不需要依赖其它模块,简单易推广。
这种办法存在的唯一风险是,杀一个语句使用的是 connection id,当匹配到一个需要杀掉的语句之后,在执行 kill 动作时,这个语句正好执行完了,而此时正好这个 connection 执行了一个新的语句,杀掉的时候并不知道是新的语句,此时被杀掉的是新语句,从而导致了误杀,但实际上应该想想,这种概率是非常小的,在匹配到与杀之时,时间差应该是几毫秒,在这几毫秒的窗口内,误杀的概率可以忽略不计,但这是一种潜在风险,需要提前考虑到。
这种方法是有接入门槛的,但实际上门槛高度有限,如果所有业务都能接入的话,数据库整体运行就会很流畅,异常问题都会提前发现与避免,不会再出现大的雪崩效应,当然这个结论,还需要时间验证,并且还需要业务填的时间相对合理才行。
还有一种办法可以实现这种功能,就是通过修改 MySQL 源代码来实现,其实业内已经有一些这样的团队做了这样的事情,但最基本的逻辑还是没变的,需要业务开发自己在 SQL 语句中设置超时时间值,Mysql 服务执行的时候通过解析语句发现设置了这个值,就会在执行的过程中不断检查已经执行的时间,如果超过所设置的时间了,就会将其杀掉,从而实现了这样的 SQL 语句执行超时的机制。
但很明显,这样的实现方式,门槛非常高,需要修改源码,不断维护源码,很少有人能做到这样,并且我认为,运维和使用 MySQL 的过程中,如果有啥需求,能通过 MySQL 的原生方法就能解决掉的(外围办法),就不要去改源码来解决,因为通过外围办法解决的话,风险度会小很多,并且自由可控,不需要对 MySQL 服务本身做过多干预,解决过程很轻量,易用。
综上所述,杀慢查询还是需要非常谨慎的,提供服务是第一原因,所以既需要保证杀的准确,也需要保证杀的及时,还需要保证不能杀出来问题,所以这事情本身是一个很复杂的问题。
上面推荐的这种解决办法,实际上就是在给一个 SQL 语句设置一个相对合理的超时时间,这是非常容易理解的,大家可以想想,写代码的时候,超时时间不都是随处可见的吗?如果能给 SQL 语句也设置一个超时时间,这样可以更好的保护数据库的稳健运行,那何乐而不为呢?
DBA 是一个服务性质的工种,也非常想替业务解决一些头疼的问题,但解决问题的时候,不能只见树木不见森林,需要站在一定高度去看待问题,需要找到合适的方法才能很好的解决问题,不然有可能就是在创造问题,找到了好的方法,通常就可以达到事半功倍的效果。
把复杂问题简单化,业务去做自己擅长的工作,DBA 也去做自己力所能及的工作,分工明确,合作共赢,长久下去,一定可以建立一个稳定、健康和良好的服务环境。