当前位置: 首页 > 编程日记 > 正文

1.5s~0.02s,期间我们可以做些什么?

原文是在我自己博客中,小伙伴也可以点阅读原文进行跳转查看,还有好听的背景音乐噢背景音乐已取消~ 2333333

大爷我就算功能重做,模块重构,我也不做优化!!!

运行真快!

不装了!


前言

本文主要探讨的核心是【为什么不要在循环中使用数据库操作?】
用了一个例子来说明为什么不要这样做的原因以及当遵循了这条规则后,所带来的好处:代码运行效率的提升、心情好(乱入-_-)之类的。

起因

最近在对一个老项目进行维护的时候,发现有一个页面加载很耗时,响应速度在1.7s以上,而且这个页面粗略看起来需要加载的东西也不是很多,为什么加载会这么慢呢?本着一探究竟和对这些慢响应无法忍受的态度去看了一下,发现它的代码写的很糟糕,到处都是循环,而且还在循环中进行了sql查询。后来在自己的优化下,从均加载1.5s到均0.02s,实现了一个质的飞跃。
本文,就是总结一下,自己在遇到这种代码的处理方式,以及思想的演化

介绍

本文所要优化的是一段,由权限控制的菜单,共有两级。而且需要在特定的菜单位置上显示待办事项的数量。普普通通的一段权限控制菜单访问的功能,其实处理起来也就是多了一个【特定菜单位置上显示代办数量】的功能,简单思考一下,只要找到对应的菜单id,在其上面增加一个对应的数字就可以了。想是这么想,做起来呢?

确定问题所在

遇到网页加载很慢的时候,首先要确定到底是哪一部分加载很慢。可以通过浏览器f12打开调试工具,在network选项里,查看当前页面上每条资源的加载耗时情况来推断。以我的博客某篇文章加载为例:

network.jpg

最右边有个红框标识的就是每条资源的加载耗时,我们可以看到第一条是php服务端的处理速度。下面的便是各种资源了。我要优化的那段业务中,发现正是由php服务端处理加载过慢带来的巨大耗时,平均每次这里加载需要1.5s以上。其他资源的加载速度平均都是在几十ms,那么就可以确定是这段php写的有问题了。

接下来我们就可以直接去看php代码了。

优化

检查代码,理解代码

找到对应的代码块,测试了一下这段代码块的处理时间,发现用时1.5s之多,有点震惊。简单看了一下代码,两大段过百行的代码块,经过一段时间的分析,发现有很多重复的、不必要的地方,现整理代码逻辑(伪代码)如下:

<?php /*** 1、取出一级菜单 并循环一级菜单 */
foreach ($top_menu as $top_id=> $value1) {/*** 2、取出二级菜单 并循环二级菜单*/foreach ($second_menu as $key2 => $value2) {/*** 3、取出三级菜单 循环三级菜单 当前菜单项含有url信息* 4、对权限进行验证 判断当前主菜单下是否拥有可以访问的权限* 5、对顶级菜单需要显示的待办事项做处理*/foreach ($third_menu as $key3 => $value3) {// 权限验证$flag = $this->auth->check($ctrl, $action);/*** 做处理 在顶级菜单上增加待办事项数* to do something*/// ............// ............/*** 这里奇葩的是又调用了另外一个方法* 传递了一个top_id 一级菜单ID* 然后根据一级菜单重复2、3在对应的三级菜单上再增加待办事项*/$this->handle_son_backlog($top_id, $backlog_data);}}
}

这段代码块都做了什么呢?文字简述如下:

  1. 取出一级菜单
  2. 循环一级菜单,根据一级菜单id,取出二级菜单
  3. 循环二级菜单,根据二级菜单id,取出三级菜单,三级菜单包含url信息
  4. 循环三级菜单,验证权限,并决定一级菜单是否显示:将url拆分成uri块,生成验证权限所需要的参数ctrl(控制器)和action(方法)
  5. 根据确定好的一级菜单,增加一级菜单需要显示的待办事项数

好了,以上就是第一个函数的作用,然而,这还没完,在循环三级菜单的时候,又调用了另外一个方法handle_son_backlog(),这个方法传了两个参数,一个是一级菜单id,另外一个是待办事项数组,那么这个方法又做了什么呢?

  1. 根据一级菜单id,取出二级菜单
  2. 循环二级菜单,取出三级菜单
  3. 菜单权限验证
  4. 在对应的三级菜单上增加待办事项数

理解完原来代码的用意后,再修改起来就不难。本来打算再原本的基础上修改,但是用了一段时间发现,代码写得太乱,根本没办法在看,于是我决定,自己写,先改造一部分,去掉多余的第二个函数

第一次尝试修改

改变代码块的可读性

经过第一次想法的修改之后,去掉了第二个方法多余的循环、重复验证的问题,代码变得稍微精简一些了:

/*** 对特定的菜单进行处理 增加待办事项* @param  array  &$son_data    子菜单信息* @param  array  $backlog_data 待办事项数据* @return array */
function handle_son_backlog(array &$son_data, array $backlog_data)
{if (empty($son_data['id'])) {return false;}switch ($son_data['id']) {case '':$son_data['backlog_num'] = (isset($backlog_data['xxx']) && empty($backlog_data['xxx'])) ? $backlog_data['xxx']: '';break;default:# code...break;}return $son_data;
}/*** 获取菜单* @param  array  $backlog_data 待办事项数据* @return array*/
function get_menu()
{/*** 1、取出一级菜单 并循环一级菜单 */foreach ($top_menu as $key1 => $value1) {/*** 2、取出二级菜单 并循环二级菜单*/foreach ($second_menu as $key2 => $value2) {/*** 3、取出三级菜单 循环三级菜单 当前菜单项含有url信息* 4、对权限进行验证 判断当前主菜单下是否拥有可以访问的权限* 5、对顶级菜单需要显示的待办事项做处理*/foreach ($third_menu as $key3 => $value3) {// 权限验证$flag = $this->auth->check($ctrl, $action);/*** 做处理 在顶级菜单上增加待办事项数* to do something*//*** 对子菜单的待办事项做处理*/$this->handle_son_backlog($value3, $backlog_data);}}}
}

修改好之后,运行0.6s,快了一倍,但是这肯定是不够的。还是慢!!!

还能不能再快?

使用递归结构

略看第一次修改后的代码还是有可以提速的地方。三层循环写的着实让人辣眼睛啊,因为在循环中还有数据库操作,请注意:任何在循环中参与数据库的处理都是不明智的选择。在大脑中构思了一下,其实这些完全可以通过递归来实现嘛。只需要把菜单一股脑取出来,在用递归形成树形结构就可以了。说干就干

先说说我这段处理大致思路:

  1. 取出菜单表里所有的菜单数据
  2. 调用递归方法,形成树形结构
  3. 递归的方法中,做一些特殊处理

    1. 确定是第三层菜单
    2. 对第三层菜单做权限处理
    3. 对第三层菜单做待办事项处理

差不多就是如上几步思路,完成版伪代码如下:

/*** 对菜单进行递归处理 并验证权限 增加待办事项数量* @param  array       &$menu        菜单* @param  array       $backlog_data 待办事项数据* @param  array       $menu_list    原来的菜单* @param  int         $pid          pid* @param  int|integer $last_pid     父菜单id* @param  int|integer $i            递归标识(用于执行特定操作)*/
function get_handle(array &$menu, array $backlog_data, array $menu_list, int $pid, int $last_pid = 0, int $i = 0)
{foreach ($menu_list as $key => $value) {if ($value['pid'] == $pid) {if ($i == 1) {// 要验证的url$check_url     = explode('?', $value['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] : 'index';if ($this->auth->check($ctrl, $action)) {$menu[$last_pid]['zi'][$value['type_id']] = $this->handle_son_backlog($value, $backlog_data);}} else {$this->get_handle($menu, $rule_list, $backlog_data, $menu_list, $value['type_id'], $pid, 1);}}}
}/*** 获取菜单* @param  array  $backlog_data 待办事项数据* @return array*/
function get_menu(array $backlog_data)
{// 获取菜单列表$menuList = $menuModel->get_list(['id', 'name', 'pid', 'url'], ['version' => 1]);// 取得一级菜单foreach ($menuList as $key => $info) {if ($info['pid'] == 0) {$menu[$info['id']] = $info;}}foreach ($menu as $id => $info) {// 对菜单作递归处理$this->get_handle($menu, $backlog_data, $menuList, $info['id']);/*** 判断当前主菜单下是否有子菜单 如果没有则释放掉当前一级菜单* 如果有则对当前一级菜单进行待办事项处理*/////    //            }return $menu;
}

差不多了就来进行调试一下吧,运行一看0.3s,感觉跟第一次修改的时候运行的也差不多嘛!(这时候已经比最初的运行速度提升了差不多4倍。)但隐隐觉得这还不够...

还能不能更快?

减少数据库查询次数;

重新梳理一下代码逻辑,试图找到可以优化的点。在梳理的时候注意到一个地方,就是$this->auth->check()这个检查权限的方法了。去跳转查看了一下,发现这方法也是查一次查一下数据库,这样的话,综合起来,这里还是牵涉到在循环中查询数据库的操作了。这块必须优化。

如果把当前登陆者已拥有的全部权限都取出来,替换掉check()这一块,是不是效率就会更快些?感觉答案应该是肯定的!

在经过一些调整之后,发现程序执行的速度有了极大的提升,增加了一段取出所有权限的操作:

/*** 获取用户所有权限列表* @param  int $user_id 用户id* @return array/boolean*/
function get_user_operation_list(int $user_id)
{$group_ids = $this->get_value_by_pk($user_id, 'groupid');if ($group_ids) {$group_ids_arr = explode(',', $group_ids);// 取出用户所拥有的权限 控制器和方法名$result = $this->db->select('o.module, o.action')->from('admin_group_operations ago')->join('operations o', 'ago.operations_id = o.operation_id', 'left')->where_in('ago.group_id', $group_ids_arr)->where('o.operation_id >', 0)->get()->result_array();if (!empty($result)) {$new_data = [];// 生成指定的键值对foreach ($result as $key => $value) {$new_data[] = $value['module'] . '/' . $value['action'];}return $new_data;}}return false;
}

并且在$this->auth->check()这行替换成了in_array($ctrl . '/' . $action, $operation_list。这样就差不多了。

运行一看,速度也挺喜人。竟然达到了0.014,比最原始的快了百倍不止。
然后再去看网页运行,发现我优化的这块,明显比网页上的其他模块加载速度要快了许多(因为项目用了iframe),之前是其他模块的内容出来了,头部的菜单还没出来。现在的情况恰恰相反,头部菜单最先加载出来,然后等待其他iframe的加载。

做完这番工作,长舒一口气,这一番coding没有白费。

总结

从这个例子中,我们可以得到一些,代码优化的技巧:

  1. 减少数据库的操作

好像就只有这个吧....2333333

思考

能不能够继续优化呢?放在缓存中会如何?
如果放在缓存中的话,也不是不行,但是这里有一个点就是这里的待办事项是可变的。而且项目中也没有使用socket的技术。如果单单存储在缓存中的话,那么更新缓存里的这块数据就会变得更加啰嗦。索性就暂时这样放着,能以后性能指标提高了,再来优化。

结。

相关文章:

Python 自动化办公之 Excel 拆分并自动发邮件

作者 | 周萝卜来源 | 萝卜大杂烩今天我们来分享一个真实的自动化办公案例&#xff0c;希望各位 Python 爱好者能够从中得到些许启发&#xff0c;在自己的工作生活中更多的应用 Python&#xff0c;使得工作事半功倍&#xff01;需求需要向大约 500 名用户发送带有 Excel 附件的电…

In Gradle projects, always use http://schemas.andr

2019独角兽企业重金招聘Python工程师标准>>> 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 在做项目自定义时候遇到这个错误 In Gradle projects, always use http://schemas.android.com/apk/res-auto for custom attributes 解决办…

HTTP POST慢速DOS攻击初探

1. 关于HTTP POST慢速DOS攻击 HTTP Post慢速DOS攻击第一次在技术社区被正式披露是今年的OWASP大会上&#xff0c;由Wong Onn Chee 和 Tom Brennan共同演示了使用这一技术攻击的威力。他们的slides在这里&#xff1a; http://www.darkreading.com/galleries/security/applicatio…

Java 学习(20)--异常 /  IO 流

异常&#xff08;Exception&#xff09; (1)程序出现的不正常的情况。 (2)异常的体系 Throwable&#xff08;接口&#xff0c;将异常类对象交给 JVM 来处理&#xff09; |--Error 严重问题&#xff0c;我们不处理。(jvm 错误&#xff0c;程序无法处理) |--Exception 异常 …

runtime自动归档/解档

原文出自&#xff1a;标哥的技术博客 前言 善用runtime&#xff0c;可以解决自动归档解档。想想以前归档是手动写的&#xff0c;确实太麻烦了。现在有了runtime&#xff0c;我们可以做到自动化了。本篇文章旨在学习如何通过runtime实现自动归档和解档&#xff0c;因此不会对所有…

Ivanti 洞察职场新趋势:71% 的员工宁愿放弃升职也要选择随处工作

近日&#xff0c;为从云端到边缘的 IT 资产提供检测、管理、保护和服务的自动化平台供应商 Ivanti 公布了其年度无处不在的办公空间&#xff08; Everywhere Workplace&#xff09; 调查结果。这项调查是Ivanti与全球“未来工作”专家共同完成的&#xff0c;调查范围涵盖 6100 …

Shippable和Packet合作提供原生ARM CI/CD

DevOps自动化平台Shippable和裸金属云服务提供商Packet联合发布了一种新的持续集成和交付&#xff08;CI/CD&#xff09;托管服务&#xff0c;适用于在Armv8-A架构上开发软件应用的开发人员。该解决方案支持开源和商业软件项目&#xff0c;用于在Packet提供的基于ARM的云服务上…

阿里云ECS架设***过程总结

原文地址:最近开发移动项目,数据库服务是架设在电信服务器上,可怜我的联通网络本地调试直接x碎了一地!!度娘相关资料后,最终决定在阿里云ECS上架设作为跳板来访问电信服务器!一.原理1.阿里云ECS上架设.2.本地连接使用拨号到阿里云ECS.3.使用阿里云ECS网络访问电信服务器.使用前…

MYSQL的MERGE存储引擎

MYSQL的引擎不是一般的多&#xff0c;这次说到的是MERGE&#xff0c;这个引擎有很多特殊的地方&#xff1a; MERGE引擎类型允许你把许多结构相同的表合并为一个表。然后&#xff0c;你可以执行查询&#xff0c;从多个表返回的结果就像从一个表返回的结果一样。每一个合并的表必…

Pandas SQL 语法归纳总结,真的太全了

作者 | 俊欣来源 | 关于数据分析与可视化对于数据分析师而言&#xff0c;Pandas与SQL可能是大家用的比较多的两个工具&#xff0c;两者都可以对数据集进行深度的分析&#xff0c;挖掘出有价值的信息&#xff0c;但是二者的语法有着诸多的不同&#xff0c;今天小编就来总结归纳一…

分布式RPC实践--Dubbo基础篇

2019独角兽企业重金招聘Python工程师标准>>> 简介 Dubbo是阿里巴巴开源的一个高性能的分布式RPC框架&#xff0c;整个框架的核心原理来源于生产者与消费者的运作模型&#xff1b;框架的核心分4大部分&#xff1a; 1. 服务注册中心 注册中心主要用于保存生产者消费者…

又居家办公了,要签合同怎么办?

本篇文章暨 CSDN《中国 101 计划》系列数字化转型场景之一。 《中国 101 计划——探索企业数字化发展新生态》为 CSDN 联合《新程序员》、GitCode.net 开源代码仓共同策划推出的系列活动&#xff0c;寻访一百零一个数字化转型场景&#xff0c;聚合呈现并开通评选通道&#xff0…

lombox的用法(省去了set/get/NoArgsConstructor/AllArgsConstructor)

1、环境的搭建&#xff0c;在eclipse下的eclipse.ini中添加以下参数&#xff0c;-Xbootclasspath/a:C:\repository\org\projectlombok\lombok\1.16.6\lombok-1.16.6.jar-javaagent:C:\repository\org\projectlombok\lombok\1.16.6\lombok-1.16.6.jar重启你的eclipse.2、将lombo…

mysql 压力测试脚本

#创建表DEPTCREATE TABLE dept( /*部门表*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,dname VARCHAR(20) NOT NULL DEFAULT "",loc VARCHAR(13) NOT NULL DEFAULT "") ENGINEMyISAM DEFAULT CHARSETutf8 ;#创建表EMP雇员CREATE TABLE emp(empno…

C++语言学习(十二)——C++语言常见函数调用约定

C语言学习&#xff08;十二&#xff09;——C语言常见函数调用约定 一、C语言函数调用约定简介 C /C开发中&#xff0c;程序编译没有问题&#xff0c;但链接的时候报告函数不存在&#xff0c;或程序编译和链接都没有错误&#xff0c;但只要调用库中的函数就会出现堆栈异常等现象…

PHP代码保护——Zend Guard

Zend Guard的作用&#xff0c;就是用编译处理的方式来保护PHP源代码免于被反编译查看、未经授权的定制修改、未经许可的使用和重新发布等。而且&#xff0c;它是PHP的东家Zend公司开发的&#xff0c;是完全为PHP量身定做的保护神。 下面&#xff0c;请大家就和我一起来学习使用…

Python 2.4 递归函数

递归函数在函数内部&#xff0c;可以调用其他函数。如果一个函数在内部调用本身&#xff0c;这个函数就是递归函数。举个例子&#xff1a;计算阶乘n!1*2*3*4*5*...*n&#xff0c;用函数fact(n)表示&#xff0c;可以看出fact(n)n!f(n-1)*n所以&#xff0c;fact(n)可以表示为n*fa…

生于俄罗斯的 Web 服务器王者 Nginx,现宣布俄罗斯禁止贡献!

作者 | 苏宓出品 | CSDN不久之前&#xff0c;一些底层工具、软件、开源项目相继宣布在俄罗斯停服&#xff0c;彼时也有不少开发者呼吁 Nginx 是时候进行反限制了。万万没想到&#xff0c;就在国际局势发生改变的一个月后&#xff0c;Nginx 动了手&#xff0c;但是有些「意料之外…

OCP换考题了,052新考题及答案整理-第17题

17、Which two statements are true about tablespaces? A) A database can contain multiple undo tablespaces. B) A database can contain only a single temporary tablespace. C) A database instance stores undo data In the SYSTEM tablespace If no undo tablespace …

linux的mount(挂载)命令详解

linux下挂载&#xff08;mount&#xff09;光盘镜像文件、移动硬盘、U盘、Windows和NFS网络共享 linux是一个优秀的开放源码的操作系统&#xff0c;可以运行在大到巨型小到掌上型各类计算机系统上&#xff0c;随着 linux系统的日渐成熟和稳定以及它开放源代码特有的优越性&…

GPT-3 再更新,新增编辑和插入文本功能,简直不要太好用!

编译 | 禾木木出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;GPT-3 是 OpenAL 提出的基于上下文的超大规模自然处理深度学习模型。这意味着如果你给 GPT-3 某些上下文内容时&#xff0c;它会试图去填充其余内容。例如给出句子的前部分&#xff0c;它会推测出下半部分…

scala akka 修炼之路5(scala特质应用场景分析)

scala中特质定义&#xff1a;包括一些字段&#xff0c;行为(方法/函数/动作)和一些未实现的功能接口的集合&#xff0c;能够方便的实现扩展或混入到已有类或抽象类中。 scala中特质(trait)是一个非常实用的特性&#xff0c;在程序设计中能够 更好的抽象现实。使程序更关注各自功…

6.2 sql安全性

最后的例子将显示如何通过现有证书创建-个新的用户。本章稍后会介绍证书&#xff0c;但在 这个例子中&#xff0c;首先创建证书&#xff0c;然后创建用户&#xff1a; USE AdventureWorks2008; CREATE CERTIFICATE SalesCert ENCRYPTION BY PASSWORD Pssw0rd WITH SUBJECT fSa…

2022,人工智能开启未来新密码

作者 | 剑客阿良_ALiang&#xff08;胡逸&#xff09; 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 购买大型电器、汽车&#xff0c;你是否会询问有没有智能语音功能&#xff1f;是的&#xff0c;潜移默化中人们已经不再将人工智能当作魔术&#xff0c;而是习以为…

PHP Socket配置以及实例

2个php测试文件 server.php <?php//phpinfo();//确保在连接客户端时不会超时set_time_limit(0);$ip 127.0.0.1;$port 1935;/*-------------------------------* socket通信整个过程-------------------------------* socket_create* socket_bind* socket_lis…

Windows下msysGit使用及相关配置

Windows下msysGit使用及相关配置

使用可信证书为windows RDP服务提供加密

2019独角兽企业重金招聘Python工程师标准>>> 0x01 前言 在windows server下可以通过配置远程桌面服务为RDP连接提供有效的数字证书以便提高安全性。 可是个人用户或并没有部署域控制器的用户是无法通过这种途径修改RDP所使用的数字证书&#xff0c;在不安全的环境中…

JavaScript跨域总结与解决办法

JavaScript跨域总结与解决办法 什么是跨域1、document.domainiframe的设置2、动态创建script3、利用iframe和location.hash4、window.name实现的跨域数据传输5、使用HTML5 postMessage6、利用flash本文来自网络&#xff08;http://f2e.me/200904/cross-scripting/&#xff0c;该…

这几个 Python 的小技巧,你会么?

来源丨Python小二作者 Peter Gleeson 是一名数据科学家&#xff0c;日常工作几乎离不 python。一路走来&#xff0c;他积累了不少有用的技巧和 tips&#xff0c;现在就将这些技巧分享给大家。这些技巧将根据其首字母按 A-Z 的顺序进行展示。ALL OR ANYPython 之所以成为这么一门…

如何创建可扩展表视图中的iOS 学习和拓展优化(有待更新)

首先介绍老外的文章&#xff1a;《How To Create an Expandable Table View in iOS》这是老外用Swift实现的&#xff0c;对应的老外github项目源码&#xff1a;https://github.com/appcoda/expandable-table-view小编经过学习了老外的Expandable Table View然后用Objective-C实…