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