容易引起雪崩的两个处理
背景
先介绍标题《容易引起雪崩的两个处理》的第一个处理:慢查询。上周在测试环境遇到一个慢查询问题,虽然是测试环境,但是现象还是很让人担忧的:“在大量执行update操作”“已经执行1个多小时”“负载200多,基本上库都卡死了”。把库都要搞挂了,细思极恐啊。于是,这个事件处理的优先级即刻被升级为S级别,我们进行了点线面的梳理和分析。
点
现象分析
首先针对此问题做一个分析。慢查询的原因简单可以这么理解:公司创立之初写的代码,当时上线工期紧,做的比较糙(声明:不代表公司水平)。后台有个人工查询操作,这个操作要查询下游,下游是异步返回结果。所以是通过异步转同步实现的,使用了一张表,每次操作将历史记录标识位全部改成“已过期”,再将新结果插入到数据表中。“在大量执行update操作”指的就是批量更新标识位的操作。大概一次操作要更新几十万条,然后插入几条。一次更新要执行几十秒。
问题解决
当时同事提出要加索引解决,我第一个反应:最主要的字段只有两个值:“已过期”、“未过期”。对这种字段加索引是不是没什么用?结果实际测试结果是:不加这条索引十几秒执行完的查询和更新操作,加了索引只要几毫秒。
原理分析
这里主要分析三个问题。
1>为什么这条索引如此管用?
先说说为什么我第一反应觉得不管用,看看某乎上的神回复:
先说为什么能提高查询速度。举个例子,假设表中有一千万条记录,状态字段有0和1两个值。某个状态为0的记录总数大概会有100条,那么你想查询状态为0的记录时,有没有索引影响非常大,而查询状态为1的记录,则索引基本无用。如果两种状态的记录数相差无几的话,索引也基本无用。所有的关于索引的文章,建议你不要为这种字段建索引的依据,都是以值分布是均匀为前提的。但如果值分布不均匀的时候,这个建议就不一定是正确的了。当我们需要查询的记录恰好是分布较少的记录的时候,值分布越是不均匀,索引就越有价值!那为什么能提高更新速度呢?
对于update/insert/delete的每次执行,字段的索引都必须重新计算更新。听起来很慢,但是更新操作实际上是先select再update的过程,这里因为“未过期”数据条数很少,所以select效率高,然后更新是按照id进行更新,所以很快。
2>为什么慢查询会导致库卡死?
一般慢查询,特别是这样将历史记录标识位全部改成“已过期”的,必然会引起锁表。这个表的相关操作会受到影响是可以理解的,但是为什么会影响到整个数据库呢?这就涉及一个最基本的问题:资源竞争。慢查询和慢请求一个道理,长时间占用连接不释放、连接数是有限的,其他后到的请求要排队。
这个问题在生产环境相对好些,因为生产环境一般都会用物理机,而且数据盘至少是SSD的。测试环境资源差很多,所以问题会更明显。
3>负载200多是什么概念?
系统平均负载(load averages)是对当前CPU工作量的度量,被定义为特定时间间隔内运行队列中的平均线程数。可以通过top, htop, uptime这些命令找到它们.关于负载的含义,网上最广泛的示例,是通过桥梁的通过率来解释的。讲的真心好,所以直接「借鉴」过来,需要看原文的直接从参考引用处自行穿越。注意这里的比喻是基于单核CPU的。
系统负荷为0,意味着大桥上一辆车也没有
系统负荷为0.5,意味着大桥一半的路段有车
系统负荷为1.0,意味着大桥的所有路段都有车,但任然可以顺次通行
系统负荷为1.7,除了桥满之外,在桥的入口处还有70%的车辆在等待
系统负荷为200,除了桥满之外,在桥的入口处还有19900%的车辆在等待!不卡死才怪!
线
sql问题影响巨大,所以我们针对所有的数据表进行了梳理,排查隐患。
面
还需要梳理所有可能引起稳定性隐患的问题。这里就要引出标题《容易引起雪崩的两个处理》的第二个处理了:递归。
递归如果深度控制不好,会产生栈溢出,也就是StackOverflowError。溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。怎么改呢?要解决这个问题之前刷的leetcode技能就排上用场了。把递归算法转化为非递归算法有如下三种基本方法
1、对于尾递归和单向递归的算法,可以用循环结构的算法替代
2、自己用堆栈模拟运行时栈,分析只保存必须保存的信息(因而可小幅提高时间效率),从而用非递归算法替代递归算法。
3、利用堆栈保存参数,由于堆栈的后进先出特性吻合递归算法的执行过程,因而可以用非递归算法替代递