大家常常会遇到一种任务,名唤作:优化。
往往都是已经性能上差到让人发指的功能点,才足以引起优化的地步。
我记忆中遇到好几次这样的情况,其中一次是在jm做动态库存的统计页。当时连了大概7、8张表,其中有千万级的大表,查询功能逻辑也是复杂的组合计算,最终逻辑是跑通的,但功能完全无法用,页面直接白板罢工。
当时的优化就是减少不必要的表联查,并走统一 继承数据的接口,简化逻辑循环嵌套深度。
最近我又遇到了一个下载页面比较慢的优化问题,这次比较有调理地做以下分析,也留作一种总结和对其他人的参考。
首先,我分析了一下整个现状。下载某报表能正常下载成功,只是耗时很长,那么说明功能逻辑是能走的,还不至于5xx的报错。然后去查代码发现,基础数据来自一个写死的sql,后面是组装原始数据的循环,循环中不断调用一个计算终端数量的方法。
<?php
$sql = 'xxxxxxx';
$baseInfos = $this->db->query($sql);
..................
foreach ($agentIds as $id) {
// some codes
$TotalNums = $this->agent->getRealDeviceNum($id, $date);
}
我先把写死的sql打印出来,去直接跑数据库,发现耗时并不长,大概0.2xxs,是命中索引的一个sql。
这个时候其实已经有两种路可以走,一种是纯看代码,靠逻辑分析出性能瓶颈。一种是打断点,计算每段消耗时间,最终用数据判断耗时最多的代码部分。
我使用了后者,在循环外部和每次内部调用计算终端数量的方法的地方分别计算了消耗时间。microtime函数派上用处。
从而发现,每次内调用耗时大概在0.1s左右,一共循环调用了2270多次。故而性能就差在反复调用上。深入去跟踪这个函数方法,其中每次调用都查了2次数据库,一共就查询了2*2270次。
很多性能问题,大部分不是在php的循环计算组合数据上,而是查询数据库上。less is more。问题找到后,我们要做的就是减少查询。首先我们要的结果是实时的,所以 排除了 使用缓存的方案。所以只能从减少查询上做功夫。
先把循环中的调用移出去,然后把单个查询改为批量,把数据处理全交给php。最后变为只查询一次。
其实后面我们发现,之前之所以这么单个循环调用是为了图方便,直接用之前单个id查询的接口。所以历史不断告诉我们:任何功能要立足需求,而不是偷懒复用。
最终一个下载耗时超过3分钟的excel,在不到3秒下载完毕。问题虽小,但是debug思路比什么都重要。