《開源框架那些事兒27》一段SQL引起的性能危機及其背後隱藏的設計缺陷

有個同窗,說是系統中出現性能問題了,說是讓我幫助診斷一下。原本是不想花這時間的,結果耐不住對方的死纏亂打,只要答應幫看看。  故事發生的背景是,在文件上傳的時候,有時間會有人上傳了文件,可是最後沒有使用上傳的文件,這樣就會產生一些垃圾文件。 原來軟件做者就想寫一個後臺定時任務程序,來清除這些垃圾文件? 因爲做者堅決的不讓我發她的SQL語句(這個我也理解,這麼醜陋的SQL),因此這裏就不發源代碼了,發僞代碼。php

void deleteMissLinkFile{
   List fileList=getFileList();
   List deleteFileList=new ArrayList();
   for(file:fileList){
       int count1=execute(select count(*) from ...);
       int count2=execute(select count(*) from ...);
       int count3=execute(select count(*) from ...);
       int count4=execute(select count(*) from ...);
       int count5=execute(select count(*) from ...);
       if(count1==0&&count2==0&&count3==0&&count4==0&&count5==0){
           deleteFileList.add(file);
       }
   }
   delete(deleteFileList);
 }

  

固然,這裏我已經給進行了必定的加工,使得看起一漂亮了許多,實際上,嗯嗯,實在是醜。  這個時候的性能狀況是怎麼樣的呢?說是表裏的數據只有500多條,可是執行時間要100多秒,可是實際上實際的應用場景都遠不止這個數量級,並且隨着數據的增長,性能會呈指數級降低。 我說你去加10萬條記錄測試一下,保證你一夜算不出來。 好吧,廢話少說,接下來看看怎麼優化這段程序。 在開始以前,咱們能夠假設有N個文件,有M個文件引用表,並且假設全部的文件引用表中的記錄條數都同樣。 很顯然,原來的實現方法中執行了:1次文件數查詢+N*M次統計操做 最笨的優化方法 先用成本最低的方式來優化一把:java

void deleteMissLinkFile{
   List fileList=getFileList();
   List deleteFileList=new ArrayList();
   for(file:fileList){
       int count1=execute(select count(*) from ...);
       int count2=execute(select count(*) from ...);
       int count3=execute(select count(*) from ...);
       int count4=execute(select count(*) from ...);
       int count5=execute(select count(*) from ...);
       if(count1==0&&count2==0&&count3==0&&count4==0&&count5==0){
           deleteFileList.add(file);
       }
   }
   delete(deleteFileList);
 }

  

嗯嗯,經過上面的重構,性能立刻就能夠提高一倍。難看是難看了一點,可是1倍也是不小的提高哦。  緣由,原來是要把全部的統計值都算出來,再進行判斷,經過上面的重構,平均只要查一半就能夠退出了,因此性能會有1倍的提高。 1次文件數查詢+N*M/2次統計操做 通常的優化方法 偶當時提醒她說,你能夠把內外換換,性能就會提高許多,結果死活聽不懂,<ignore_js_op>git

異步

實際上邏輯是這樣的,因爲統計操做的執行效率是很是低的,而帶主鍵的查詢速度是很是快的,也就是把邏輯從:遍歷全部的文件看看引用次數是多少,改變成從全部文件列表中刪除全部已經引用的文件,其他就是要刪除的垃圾文件。性能

void deleteMissLinkFile{
   List fileList=getFileList();
   List refList1=execute(select file from tb1…)
   for(ref:refList1){
       fileList.remove(ref)
   }
   List refList2=execute(select file from tb2…)
   for(ref:refList2){
       fileList.remove(ref)
   }
   ……
   delete(deleteFileList);
 }
 

  

經過上面的優化,須要執行的SQL語句是:  1+m 條SQL語句,其它都是大量的內存數據比對,相對來講,性能會高太多,經過必定的技巧進行一些優化,會有更大的提高。 這種方式,我毛估估比原始的方式,能夠提升兩個數量級以上。 爲何提升了兩個左右數量級仍是說比較笨的方法呢? 由於這種方法雖然比原始的方法有了顯著的提高,可是仍是存在嚴重的設計問題的。 首先,當數據量比較小的時候(這裏的小是指與互聯網應用中的數據相比),作徹底遍歷是沒有問題的,可是當數據量比較大的時候,用一條SQL來遍歷全部的數據,就是有很是大的問題的。這個時候就要引入一系列的複雜問題來解決,好比:把單機計算變成集羣計算,把整個計算變成分段時間,無論怎麼樣,都是很是複雜的處理過程。 無爲而治的方法 下面就要推出最快的、最省事的、效率最高的方法。 其實通常來講,只要是算法都是有優化空間和餘地的,所以通常來講本人不多把話說滿的。此次本人使用了「最」字,那就是用來代表將來已經沒有優化的空間了,那什麼樣的算法才能沒有優化的空間呢?答案就是:啥也不作。 固然了,實際上也不可能啥也不作,問題就在哪裏,你不作怎麼可能好呢? 實際上就是把任務進行必定的分解。經過把架構進行合理的分析與設計,把全部的文件上傳、刪除都作成公共的方法(或服務),在須要與文件打交道的地方,凡是與文件打交道的時候,作以下處理:測試

  • 文件上傳:在文件上傳數據中加一條數據,好比:文件相關信息,惟一標識,引用次數爲0
  • 文件關聯:當數據與文件關聯的時候,修改引用次數爲+1
  • 文件取消關聯:當數據與文件取消關聯的時候(通常來講是刪除或編輯的時候置爲空或者換成另一個的時候),修改引用次數爲-1

自次,當要清理垃圾的時候,就很是簡單的了,只要:    select ... from ... where ref_times=0 而後進行相應的清理工做就好。 這個時候就優化了處理模式,而且把文件引用數據的維護分解到業務工做的過程中,能夠極大幅度的提高清理垃圾的處理效率。固然有的人說了:若是這麼作,會使得個人業務處理過程變慢,那怎麼辦?其實也沒有關係了,你能夠把這個變成異步消息的方式,通知文件引用處理去作這件事情就好了,這樣就不會影響到你的業務處理效率了。 總結 經過上面的分析,咱們對文件上傳過程當中的垃圾清理過程進行優化,並分析了原來的問題之所在,及後面3種優化方式及其優缺點對比。 固然,實際上許多朋友也會有更好的辦法來解決,歡迎你們參與討論,並批評指正。 若是,你喜歡個人博文,請關注我,以便收到個人最新動態。 若是對個人開源框架感興趣,能夠從這裏獲取到最新的代碼,也能夠訪問Tiny官網獲取更多的消息,或到Tiny社區進行即時交流。優化

相關文章
相關標籤/搜索