摘要:而且需要在特定的菜單位置上顯示待辦事項的數(shù)量。以我的博客某篇文章加載為例最右邊有個紅框標識的就是每條資源的加載耗時,我們可以看到第一條是服務端的處理速度。接下來我們就可以直接去看代碼了。在大腦中構(gòu)思了一下,其實這些完全可以通過遞歸來實現(xiàn)嘛。
原文是在我自己博客中,小伙伴也可以點閱讀原文進行跳轉(zhuǎn)查看,還有好聽的背景音樂噢背景音樂已取消~ 2333333
大爺我就算功能重做,模塊重構(gòu),我也不做優(yōu)化?。?!運行真快!
本文主要探討的核心是【為什么不要在循環(huán)中使用數(shù)據(jù)庫操作?】
用了一個例子來說明為什么不要這樣做的原因以及當遵循了這條規(guī)則后,所帶來的好處:代碼運行效率的提升、心情好(亂入-_-)之類的。
最近在對一個老項目進行維護的時候,發(fā)現(xiàn)有一個頁面加載很耗時,響應速度在1.7s以上,而且這個頁面粗略看起來需要加載的東西也不是很多,為什么加載會這么慢呢?本著一探究竟和對這些慢響應無法忍受的態(tài)度去看了一下,發(fā)現(xiàn)它的代碼寫的很糟糕,到處都是循環(huán),而且還在循環(huán)中進行了sql查詢。后來在自己的優(yōu)化下,從均加載1.5s到均0.02s,實現(xiàn)了一個質(zhì)的飛躍。
本文,就是總結(jié)一下,自己在遇到這種代碼的處理方式,以及思想的演化
本文所要優(yōu)化的是一段,由權(quán)限控制的菜單,共有兩級。而且需要在特定的菜單位置上顯示待辦事項的數(shù)量。普普通通的一段權(quán)限控制菜單訪問的功能,其實處理起來也就是多了一個【特定菜單位置上顯示代辦數(shù)量】的功能,簡單思考一下,只要找到對應的菜單id,在其上面增加一個對應的數(shù)字就可以了。想是這么想,做起來呢?
確定問題所在遇到網(wǎng)頁加載很慢的時候,首先要確定到底是哪一部分加載很慢??梢酝ㄟ^瀏覽器f12打開調(diào)試工具,在network選項里,查看當前頁面上每條資源的加載耗時情況來推斷。以我的博客某篇文章加載為例:
最右邊有個紅框標識的就是每條資源的加載耗時,我們可以看到第一條是php服務端的處理速度。下面的便是各種資源了。我要優(yōu)化的那段業(yè)務中,發(fā)現(xiàn)正是由php服務端處理加載過慢帶來的巨大耗時,平均每次這里加載需要1.5s以上。其他資源的加載速度平均都是在幾十ms,那么就可以確定是這段php寫的有問題了。
接下來我們就可以直接去看php代碼了。
優(yōu)化 檢查代碼,理解代碼找到對應的代碼塊,測試了一下這段代碼塊的處理時間,發(fā)現(xiàn)用時1.5s之多,有點震驚。簡單看了一下代碼,兩大段過百行的代碼塊,經(jīng)過一段時間的分析,發(fā)現(xiàn)有很多重復的、不必要的地方,現(xiàn)整理代碼邏輯(偽代碼)如下:
$value1) { /** * 2、取出二級菜單 并循環(huán)二級菜單 */ foreach ($second_menu as $key2 => $value2) { /** * 3、取出三級菜單 循環(huán)三級菜單 當前菜單項含有url信息 * 4、對權(quán)限進行驗證 判斷當前主菜單下是否擁有可以訪問的權(quán)限 * 5、對頂級菜單需要顯示的待辦事項做處理 */ foreach ($third_menu as $key3 => $value3) { // 權(quán)限驗證 $flag = $this->auth->check($ctrl, $action); /** * 做處理 在頂級菜單上增加待辦事項數(shù) * to do something */ // ............ // ............ /** * 這里奇葩的是又調(diào)用了另外一個方法 * 傳遞了一個top_id 一級菜單ID * 然后根據(jù)一級菜單重復2、3在對應的三級菜單上再增加待辦事項 */ $this->handle_son_backlog($top_id, $backlog_data); } } }
這段代碼塊都做了什么呢?文字簡述如下:
取出一級菜單
循環(huán)一級菜單,根據(jù)一級菜單id,取出二級菜單
循環(huán)二級菜單,根據(jù)二級菜單id,取出三級菜單,三級菜單包含url信息
循環(huán)三級菜單,驗證權(quán)限,并決定一級菜單是否顯示:將url拆分成uri塊,生成驗證權(quán)限所需要的參數(shù)ctrl(控制器)和action(方法)
根據(jù)確定好的一級菜單,增加一級菜單需要顯示的待辦事項數(shù)
好了,以上就是第一個函數(shù)的作用,然而,這還沒完,在循環(huán)三級菜單的時候,又調(diào)用了另外一個方法handle_son_backlog(),這個方法傳了兩個參數(shù),一個是一級菜單id,另外一個是待辦事項數(shù)組,那么這個方法又做了什么呢?
根據(jù)一級菜單id,取出二級菜單
循環(huán)二級菜單,取出三級菜單
菜單權(quán)限驗證
在對應的三級菜單上增加待辦事項數(shù)
理解完原來代碼的用意后,再修改起來就不難。本來打算再原本的基礎(chǔ)上修改,但是用了一段時間發(fā)現(xiàn),代碼寫得太亂,根本沒辦法在看,于是我決定,自己寫,先改造一部分,去掉多余的第二個函數(shù)
第一次嘗試修改改變代碼塊的可讀性;
經(jīng)過第一次想法的修改之后,去掉了第二個方法多余的循環(huán)、重復驗證的問題,代碼變得稍微精簡一些了:
/** * 對特定的菜單進行處理 增加待辦事項 * @param array &$son_data 子菜單信息 * @param array $backlog_data 待辦事項數(shù)據(jù) * @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 待辦事項數(shù)據(jù) * @return array */ function get_menu() { /** * 1、取出一級菜單 并循環(huán)一級菜單 */ foreach ($top_menu as $key1 => $value1) { /** * 2、取出二級菜單 并循環(huán)二級菜單 */ foreach ($second_menu as $key2 => $value2) { /** * 3、取出三級菜單 循環(huán)三級菜單 當前菜單項含有url信息 * 4、對權(quán)限進行驗證 判斷當前主菜單下是否擁有可以訪問的權(quán)限 * 5、對頂級菜單需要顯示的待辦事項做處理 */ foreach ($third_menu as $key3 => $value3) { // 權(quán)限驗證 $flag = $this->auth->check($ctrl, $action); /** * 做處理 在頂級菜單上增加待辦事項數(shù) * to do something */ /** * 對子菜單的待辦事項做處理 */ $this->handle_son_backlog($value3, $backlog_data); } } } }
修改好之后,運行0.6s,快了一倍,但是這肯定是不夠的。還是慢?。。?/p> 還能不能再快?
使用遞歸結(jié)構(gòu);
略看第一次修改后的代碼還是有可以提速的地方。三層循環(huán)寫的著實讓人辣眼睛啊,因為在循環(huán)中還有數(shù)據(jù)庫操作,請注意:任何在循環(huán)中參與數(shù)據(jù)庫的處理都是不明智的選擇。在大腦中構(gòu)思了一下,其實這些完全可以通過遞歸來實現(xiàn)嘛。只需要把菜單一股腦取出來,在用遞歸形成樹形結(jié)構(gòu)就可以了。說干就干
先說說我這段處理大致思路:
取出菜單表里所有的菜單數(shù)據(jù)
調(diào)用遞歸方法,形成樹形結(jié)構(gòu)
遞歸的方法中,做一些特殊處理
確定是第三層菜單
對第三層菜單做權(quán)限處理
對第三層菜單做待辦事項處理
差不多就是如上幾步思路,完成版?zhèn)未a如下:
/** * 對菜單進行遞歸處理 并驗證權(quán)限 增加待辦事項數(shù)量 * @param array &$menu 菜單 * @param array $backlog_data 待辦事項數(shù)據(jù) * @param array $menu_list 原來的菜單 * @param int $pid pid * @param int|integer $last_pid 父菜單id * @param int|integer $i 遞歸標識(用于執(zhí)行特定操作) */ 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數(shù)據(jù)段 $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 待辦事項數(shù)據(jù) * @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; }
差不多了就來進行調(diào)試一下吧,運行一看0.3s,感覺跟第一次修改的時候運行的也差不多嘛!(這時候已經(jīng)比最初的運行速度提升了差不多4倍。)但隱隱覺得這還不夠...
還能不能更快?減少數(shù)據(jù)庫查詢次數(shù);
重新梳理一下代碼邏輯,試圖找到可以優(yōu)化的點。在梳理的時候注意到一個地方,就是$this->auth->check()這個檢查權(quán)限的方法了。去跳轉(zhuǎn)查看了一下,發(fā)現(xiàn)這方法也是查一次查一下數(shù)據(jù)庫,這樣的話,綜合起來,這里還是牽涉到在循環(huán)中查詢數(shù)據(jù)庫的操作了。這塊必須優(yōu)化。
如果把當前登陸者已擁有的全部權(quán)限都取出來,替換掉check()這一塊,是不是效率就會更快些?感覺答案應該是肯定的!
在經(jīng)過一些調(diào)整之后,發(fā)現(xiàn)程序執(zhí)行的速度有了極大的提升,增加了一段取出所有權(quán)限的操作:
/** * 獲取用戶所有權(quán)限列表 * @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); // 取出用戶所擁有的權(quán)限 控制器和方法名 $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,比最原始的快了百倍不止。
然后再去看網(wǎng)頁運行,發(fā)現(xiàn)我優(yōu)化的這塊,明顯比網(wǎng)頁上的其他模塊加載速度要快了許多(因為項目用了iframe),之前是其他模塊的內(nèi)容出來了,頭部的菜單還沒出來?,F(xiàn)在的情況恰恰相反,頭部菜單最先加載出來,然后等待其他iframe的加載。
做完這番工作,長舒一口氣,這一番coding沒有白費。
總結(jié)從這個例子中,我們可以得到一些,代碼優(yōu)化的技巧:
減少數(shù)據(jù)庫的操作
好像就只有這個吧....2333333
思考能不能夠繼續(xù)優(yōu)化呢?放在緩存中會如何?
如果放在緩存中的話,也不是不行,但是這里有一個點就是這里的待辦事項是可變的。而且項目中也沒有使用socket的技術(shù)。如果單單存儲在緩存中的話,那么更新緩存里的這塊數(shù)據(jù)就會變得更加啰嗦。索性就暫時這樣放著,能以后性能指標提高了,再來優(yōu)化。
結(jié)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/29082.html
摘要:原文出處這種垃圾收集器的官方名稱是。使用收集器的名稱。事件時長記錄不同的類型回收期間垃圾收集器線程消耗事件調(diào)用操作系統(tǒng)活著等待系統(tǒng)事件消耗時間應用停頓的時鐘時間?,F(xiàn)在我們看一些一些任務的時間,垃圾收集器線程等待很長時間。 原文出處:Concurrent Mark and Sweep 這種垃圾收集器的官方名稱是Mostly Concurrent Mark and Sweep Garbag...
摘要:效果預覽按下右側(cè)的點擊預覽按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽。可交互視頻此視頻是可以交互的,你可以隨時暫停視頻,編輯視頻中的代碼。最后,把擺線的數(shù)量調(diào)整為個。 showImg(https://segmentfault.com/img/bVbe6re?w=400&h=301); 效果預覽 按下右側(cè)的點擊預覽按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽。 https://code...
摘要:效果預覽按下右側(cè)的點擊預覽按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽??山换ヒ曨l此視頻是可以交互的,你可以隨時暫停視頻,編輯視頻中的代碼。最后,把擺線的數(shù)量調(diào)整為個。 showImg(https://segmentfault.com/img/bVbe6re?w=400&h=301); 效果預覽 按下右側(cè)的點擊預覽按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽。 https://code...
閱讀 3499·2021-10-08 10:15
閱讀 6265·2021-09-23 11:56
閱讀 1530·2019-08-30 15:55
閱讀 529·2019-08-29 16:05
閱讀 2790·2019-08-29 12:34
閱讀 2097·2019-08-29 12:18
閱讀 973·2019-08-26 12:02
閱讀 1718·2019-08-26 12:00