SQL語句優化 | 如何提高SQL運行速度
引言
今天早上,我的某位電腦科學專業、高學歷的上司傳了一段SQL碼,是關於一個每日報告的數據擷取程序。然而,該段SQL碼的運行速度可以說是十分感人,簡單的一萬多行數據跑三分鐘,而且還想在Production App正使用的SQL Server上運行,大概是怕公司發展太順利會忘記危機感,要給公司點挑戰。
因此,熱愛公司的我重新寫了一遍該段SQL碼,把三分鐘硬是壓低到一秒。
此文會紀錄一下我改了些什麼,以及可以從什麼角度嘗試對SQL碼優化。
由於安全問題,不會用到真實的例子(跑三分鐘的那段)。
最後,本文會涉及部分大O符號或數據結構的知識,沒學過的可以去我的舊文看一下,
如果覺得我寫的太亂,也可以Google一下。
首先,所有測試以Microsoft SQL Server為準,
以下是一段示範SQL碼
:
SELECT ShopCode ,sum([Money])/1000FROM [MainTable] a (nolock)INNER JOIN [WhatShopWeHave] bon a.[ShopId] = b.[ShopId]WHERE cast(DATEADD(hour,8,b.WhatDateIsIt) as date) between '2000-01-01' and '2000-01-31'AND ( upper(WhatTypeIsThisMember) like '%A_TPYE%' OR upper(WhatTypeIsThisMember) like '%B_TPYE%' OR upper(WhatTypeIsThisMember) like '%C_TPYE%' )AND ShopCode <>'TESTING' group by ShopCode
以SQL碼而言,可以說是雜亂且毫無美感可言,而且這還是我幫它整理過的結果。
其目的是要從[MainTable]中抽出特定類型會員的消費總額,並且該數據要確保以下條件 :
1. 該行數據的[Shopid]存在於[WhatShopWeHave]表中。
2. 該行數據的[ShopCode]不能是'TESTING'
3. 該行數據的日期於 2000-01-01 與 2000-01-31 之間。
4. 該行數據需是A_TYPE或B_TYPE或C_TYPE其中一種會員。
問題講解
首先,Inner Join與Full Outer Join是能避免則避免的做法,如果要進行不同表的結合,
最好是使用Left / Right Join,原因在於Inner / Outer Join都需要在結合的兩個表進行彼此的檢查,因此其時間複雜度會是O(2n1n2)。相反的,Left / Right Join只會在主表掃描一次副表便完成,其時間複雜度只有O(n1n2),換句話說Inner / Outer Join比Left / Right Join慢2倍。
如果要處理問題 1,最好是改用Exists加TOP 1 1 的SubQuery來確認該行數據的[Shopid]存在於[WhatShopWeHave]表中。原因在於加入TOP 1的話,SubQuery並不會把副表中的所有數據都跑一遍,當條件吻合便會立馬停止Query,那進行一行數據的檢查只需要O(log(n2))而不是O(n2)。
但就算必須要使用Inner Join來完成語句的話,也必須在使用Join語句前先用SELECT INTO創建臨時表並提前完成 問題3 和 問題2的數據篩檢。用途是使後面Join語句的時間複雜度的n1減少,那最後運算時間便會大幅下降。例如,現在[MainTable]有1000000(一百萬)行數據,[WhatShopWeHave]有100(一百)行數據,完成Inner Join便需要200000000(二億)個步驟,
但如果提前進行數據篩檢的話,[MainTable]會只有1000(一千)行數據,[WhatShopWeHave]有100(一百)行數據,完成Inner Join便需要200000(二十萬)個步驟。最終運行時間最少有一千倍。
而且先插入臨時表也可以減少表的鎖定時間。
至於最後問題4,我個人習慣會在代碼頂部,創建一個變數表,並一行一行把選項插入至變數表中。最後再使用Exists或In語句進行數據篩檢(由於這裏需要用LIKE運算式,因此會使用Exists), 這種做可以令修改或增加篩選的選項簡單化,方便後續使用, 並且使用TOP 1 1 Exists也可以減少步驟數量。
結論而言,優化SQL代碼的方法大多都離不開時間複雜度這個概念,減少進行數據檢查的次數, 自然代碼的運行速度便會提高。而能只做一次的東西就只做一次, 而且要盡量避免一個Query有過多的Join,如果只需要數個其他表的數據,便用TOP 1 1 的SubQuery調用便可。 需要多次Join就用臨時表。 當然Nosql的數據庫能玩的優化更多,例如Key-Value類型的雜湊表可以減少到O(1)的單一調取時間複雜度。 但SQL單就代碼層面,能做就這些了,但不太清楚電腦科學碩士是不是都不會教數據結構就是了。