当前位置: 首页 > 后端技术 > PHP

1.5s~0.02s,这段时间我们可以做什么呢?

时间:2023-03-29 21:07:33 PHP

原文在我自己的博客里,小伙伴们也可以点击阅读原文跳转查看,还有好听的背景音乐哦,背景音乐取消了~2333333爷爷,就算我重做功能和重构模块,我不会做优化!!!跑得真快!前言本文的核心是【为什么不在循环中使用数据库操作?】我用了一个例子来说明为什么不这样做,以及遵循这个规则带来的好处:提高代码运行效率,心情好(乱入-_-)等等。原因最近在维护一个老项目时,发现一个页面加载非常耗时,响应速度在1.7s以上。而且,这个页面好像也没有多少东西可以加载。为什么加载这么慢?我本着一探究竟,忍无可忍这些慢响应的态度看了一下,发现它的代码写得不好,到处都是循环,SQL查询都是在循环中进行的。后来在自己的优化下,实现了从平均加载时间1.5s到0.02s的质的飞跃。这篇文章只是总结一下我是如何遇到这种代码处理和思路演变的。这篇文章我要优化的是一个受权限控制的版块。有两级菜单。并且待办事项的数量需要显示在特定的菜单位置。一个普通的权限控制菜单访问功能,其实就是【在特定菜单位置显示代理数量】的附加功能,简单想一下,只要找到对应的菜单id,在上面添加对应的就可以了它的数字会做。如果你这么认为,你是怎么做到的?确定问题在遇到加载缓慢的网页时,首先要确定到底是哪一部分加载缓慢。可以通过浏览器f12打开调试工具,在网络选项中,查看当前页面各资源的加载时间进行推断。以我博客一篇文章的加载为例:最右边有一个红框,表示每个资源的加载时间。我们可以看到,第一个是PHP服务器的处理速度。以下是各种资源。在自己要优化的部分业务中,发现是PHP服务器加载缓慢导致的巨大耗时。平均每次加载需要1.5s以上。其他资源的平均加载速度都是几十ms,可以确定是这个php的写法有问题。然后我们就可以直接上php代码了。优化查看代码,理解代码找到对应的代码块,测试这个代码块的处理时间,发现竟然用了1.5s之多,有点震撼。粗略看了一下代码,有两大段一百多行的代码块。经过一段时间的分析,发现其中有很多重复和不必要的地方。现在我整理代码逻辑(伪代码)如下:$value1){/***2.取出二级菜单,循环二级菜单*/foreach($second_menuas$key2=>$value2){/***3.取出三级菜单菜单,循环浏览三级菜单。当前菜单项包含url信息*4.验证权限判断当前主菜单是否有访问权限*5.查看顶级菜单需要显示的to-do项处理*/foreach($third_menuas$key3=>$value3){//权限验证$flag=$this->auth->check($ctrl,$action);/***处理增加顶部菜单上的待办事项计数*做某事*///.........//........./***这里奇怪的事情就是调用了另一种方法*传递一个top_id一级菜单ID*然后根据一级菜单重复2和3在对应的三级菜单上添加待办事项*/$this->handle_son_backlog($top_id,$backlog_data);}}}这个代码块做了什么?文字简述如下:取出一级菜单循环一级菜单,取出二级菜单根据一级菜单id循环二级菜单,取出三级菜单根据二级菜单id,三级菜单包含url信息循环三级菜单,验证权限,决定是否显示一级菜单:将url拆分成uri块,根据确定的first生成验证权限所需的参数ctrl(controller)和action(method)一级菜单,增加一级菜单需要展示的待办事项。以上是第一个函数的功能。然而,这还没有结束。在三级菜单循环时,调用了另一个方法handle_son_backlog()。这个方法传了两个参数,一个是一级菜单id,一个是待办事项数组,那么这个方法是做什么的呢?根据一级菜单id,取出二级菜单,循环二级菜单,取出三级菜单菜单权限验证。增加相应三级菜单的待办事项。了解原代码后,修改起来并不难。本来打算在原来的基础上修改,但是过了一段时间,发现代码太乱了,看不懂,于是决定自己写,先修改一部分,去掉多余的第二个功能。第一次尝试修改代码块的可读性;修改第一种思路后,去除了第二种方法中冗余循环和重复验证的问题,代码变得精简了一点:/***执行特定的菜单Handle添加待办事项*@paramarray&$son_data子菜单信息*@paramarray$backlog_data待办事项数据*@returnarray*/functionhandle_son_backlog(array&$son_data,array$backlog_data){if(empty($son_data['id'])){return错误的;}switch($son_data['id']){case'':$son_data['backlog_num']=(isset($backlog_data['xxx'])&&empty($backlog_data['xxx']))?$backlog_data['xxx']:'';休息;默认值:#代码...中断;}return$son_data;}/***获取菜单*@paramarray$backlog_datapendingItemdata*@returnarray*/functionget_menu(){/***1.取出一级菜单,循环一级-levelmenu*/foreach($top_menuas$key1=>$value1){/***2.取出二级菜单,循环二级菜单*/foreach($second_menuas$key2=>$value2){/***3.取出三级菜单循环三级菜单的当前菜单项包含url信息*4.验证权限判断当前主菜单是否有访问权限*5.to-做需要在顶层菜单显示的项目做处理*/foreach($third_menuas$key3=>$value3){//权限验证$flag=$this->auth->check($ctrl,$行动);/***对顶级菜单做处理增加待办事项的数量*做某事*//***对子菜单的待办事项进行处理*/$this->handle_son_backlog($value3,$backlog_data);}}}}修改后,运行0.6s,快了一倍,但这肯定不够或者慢!!!能再快点吗?使用递归结构;第一次修改后粗略看一下代码,还有提速的空间。三层循环确实很吸引眼球,因为循环中还有数据库操作,请注意:任何在循环中参与数据库处理都是不明智的选择。我在脑子里设想了一下,其实这些都可以通过递归来实现。只需要一下子把菜单全部取出来,用递归的方式形成树状结构。先说一下我这个过程的大概思路:把菜单表中的菜单数据全部取出来,调用递归的方法,形成一个树形结构。在递归方法中,做一些特殊处理,判断三级菜单是否为三级菜单。权限处理对三级菜单做待办事项处理和上面的步骤差不多,完成的伪代码如下:/***递归处理菜单并验证权限,增加数量待办事项*@paramarray&$menu菜单*@paramarray$backlog_data待办事项数据*@paramarray$menu_list原始菜单*@paramint$pidpid*@paramint|integer$last_pid父菜单id*@paramint|integer$i递归标识(用于特定操作)*/functionget_handle(array&$menu,array$backlog_data,array$menu_list,int$pid,int$last_pid=0,int$i=0){foreach($menu_listas$key=>$value){if($value['pid']==$pid){if($i==1){//要验证的url$check_url=explode('?',$值['url']);//拆分成uri数据段$check_url_arr=explode('/',$check_url[0]);//控制器名称$ctrl=$check_url_arr[0].'_'。$check_url_arr[1];//方法名$action=isset($check_url_arr[2])?$check_url_arr[2]:'索引';如果($this->auth->check($ctrl,$action)){$menu[$last_pid]['zi'][$value['type_id']]=$this->handle_son_backlog($value,$积压数据);}}else{$this->get_handle($menu,$rule_list,$backlog_data,$menu_list,$value['type_id'],$pid,1);}}}}/***获取菜单*@paramarray$backlog_data积压数据*@returnarray*/functionget_menu(array$backlog_data){//获取菜单列表$menuList=$menuModel->get_list(['id','名称','pid','url'],['版本'=>1]);//获取一级菜单foreach($menuListas$key=>$info){if($info['pid']==0){$menu[$info['id']]=$info;}}foreach($menuas$id=>$info){//递归处理菜单$this->get_handle($menu,$backlog_data,$menuList,$info['id']);/***判断当前主菜单下是否有子菜单,如果没有则释放当前一级菜单*如果当前一级菜单有待办事项处理*///////}return$menu;}调试的差不多了,运行0.3s,感觉和第一个一样修改一次就跑的差不多了!(此时已经比原来的运行速度快了将近4倍)但是我觉得这还不够……能再快点吗?减少数据库查询次数;重新梳理代码逻辑,尝试寻找可以优化的点。整理的时候注意到一个地方,就是检查权限的$this->auth->check()方法。我去跳转查看,发现这个方法也是查一次数据库。这样的话,结合起来,这还是涉及到循环查询数据库的操作。这一块必须优化。如果把当前登录已经拥有的所有权限都拿出来,换成check()这块,效率会不会更快一些?感觉答案应该是肯定的!经过一番调整,发现程序的执行速度有了很大的提升,增加了一个去掉所有权限的操作:/***获取所有用户权限列表*@paramint$user_id用户id*@返回数组/布尔值*/functionget_user_operation_list(int$user_id){$group_ids=$this->get_value_by_pk($user_id,'groupid');如果($group_ids){$group_ids_arr=explode(',',$group_ids);//提取用户拥有的权限控制器和方法名$result=$this->db->select('o.module,o.action')->from('admin_group_operationsago')->join('operationso','ago.operations_id=o.operation_id','left')->where_in('ago.group_id',$group_ids_arr)->where('o.operation_id>',0)->get()->result_array();如果(!empty($result)){$new_data=[];//生成指定的键值对foreach($resultas$key=>$value){$new_data[]=$value['module'].'/'。$值['行动'];}返回$new_data;}}returnfalse;}把$this->auth->check()这一行换成in_array($ctrl.'/'.$action,$operation_list就可以了,运行看看,速度还是挺喜人的。甚至达到了0.014,比原来快了一百多倍。然后我去看网页运行,发现我优化的这个明显比网页上的其他模块快很多(因为我用的项目一个iframe),其他模块的内容出来之前,但是headermenu还没有出来。现在情况刚好相反,先加载headermenu,然后等待其他iframe加载。完成这个之后工作,时间长了松一口气,这次编码没有白费。总结从这个例子中,我们可以得到一些代码优化技巧:看来只有这样才能减少数据库操作....2333333可以继续优化吗?缓存里会发生什么?如果放在缓存里,也不是不可能e,但是这里有一点,这里的待办事项是可变的。并且项目中没有使用socket技术。如果只是保存在缓存中,那么在缓存中更新这条数据会变得比较冗长。干脆暂时这样吧,等性能指标提高了再优化。结尾。