因為這篇文章寫的比較長一些,我就將總結先列出來html
總結sql
1. 除了WHERE條件外,JOINColumn除了記得創建索引,也要注意到選擇性的高低,若是真的找不到可用的Column,能夠考慮在兩邊關聯的表上加入super eky,再作JOIN
若是單純想測試這個Column的選擇性,能夠透過這個語法
SELECT 1 / CAST(COUNT(DISTINCT {COLUMN_NAME} ) AS NUMERIC(18, 2)) FROM {TABLE_NAME}ide
2. 某些情境下(像是本文的例子),索引Column其實是因為商業邏輯沒有考慮周全,導致該Column的值重複性過高,若是在早期就發現這個問題了,能夠與開發人員溝通,想辦法讓該Column的值重複性降低。我們都知道索引越接近惟一值效能就會越好(不用付出額外的查找成本)3d
3. 在SQL Server中,索引有一欄能夠勾選是否惟一。該Column的設定值是會影響到SQL Server如何判斷查找,若是確定該欄不會有重複的資料,就勾起來吧!
htm
4. 要注意的是,不是用到了索引就會速度飛快,實際上你要先明白你資料集預期是會有多少筆,若是SQL Server實際在運做時,撈取的資料超過預期。就須要去找出緣由,否則實際上不僅是索引撈的資料量太多,後續SQL Server在作處理時,會一併用高耗能的方式去處理大量資料
e.g. 平行資料流、Hash Join 、Table Poolblog
5. 利用子查詢或CTE作資料查詢拆分並從新Join時,也要注意到選擇的Column值要從重複性較低的資料表上選取索引
正文開始 ~ ip
筆者前陣子因為有user反應SSRS的報表無法一次匯出所有的資料,當下心想應該是T-SQL語法的問題。除了先將該問題處理以外,也一併將舊的語法全面改用CTE撰寫。get
通常會用CTE寫的緣由不外乎是當一個查詢裡面的子查詢(Sub-query)過多時,語法會顯得很是複雜也很難維護。透過CTE能夠很直覺的將資料集拆成一小區一小區後,再聯合平作一次JOIN來達到我們的需求it
這次在改寫的過程中也為了效能有特別注意執行計畫這一塊,也找到了索引對於整體查詢效能影響的關鍵
先來看看這個因為選錯INNER JOIN Column導致的效能問題吧!能夠看到上圖雖然用了NCL index seek可是撈出來的資料量顯然是很是多阿!
正常來說應該只有這樣1856筆。
兩者相差了快178倍 。
在這個案例中我們首先看到的是 執行數目 232 這一個值。這裡其實就是INNER JOIN(NESTED)背後的OUTER Table表上的筆數。這裡我們把232先記下來
提到索引,接下來為了分析為何用了第一個索引會撈出33萬筆這麼多的資料。我們要來查看這個索引的統計資訊(Statistics) ,這裡為了方便我們講解,我已經截取出我們所須要的資料到Excel上並且也實際算了一下,來證明我們的猜測
能夠看到第一個索引會用到的HISTOGRAM是這幾筆鍵值。第一欄的CP_一、CP_2基本上就是我們的ability_ID,這裡也能夠看到因為EQ_ROW平均都有1500筆左右的資料,因此乘上我們OUTER Table的筆數232。最後加總取一個平均,就是SQL Server取出的資料量。
而這就也就是為什麼當我們用第一個索引時,SQL Server會取出這麼多資料,緣由其實就是在於CP_一、CP_2鍵值資料太多導致SQL Server在查找(Seek)時,每次都要掃將近1500筆。
第二個就比較簡單了,同樣的從HISTOGRAM來看,SQL Server 只須要查找(Seek) 一個區間的資料,並且這個區間資料筆數也很是的小。這裡透過CourseID取得的只有8筆再乘上我們剛剛提到的232,也就剛好為1856
透過實際瞭解SQL Server最終是如何撈取這麼多的資料列後,我們能夠簡單的看看兩邊索引的選擇性
能夠很明顯的看到第二個索引的選擇性很明顯是優於第一個的
但這裡要注意的是,雖然我們能透過索引的選擇性去比較這個索引的好壞。但實際上仍要去檢查這個Column的資料是否是與當初預期的規劃一樣,以這個案例來說,當初設計Ability_ID Column時並沒有將重複值這件事考慮進去,因此最後也導致了SQL Server要額外付出成本從重複的資料集中作搜尋(Seek)的動做。
額外補充-優化過程
其實筆者這次用CTE,改寫時!發現到,SQL Server並沒有像我預期的,先將某些資料取出成小資料集後再進行JOIN的動做,而是間接去將原始表撈取出了大量資料!
如下的語法JOIN關聯有經過調整,因此會跟上面的例子有點不太一樣
若是有遇到這種問題,能夠先透過QUERY HITS的方式來強迫SQL Server依照T-SQL語句的順序執行,透過這種方式我們能夠很容易找出問題點。(但前提是你對於資料表執行的順序有必定的瞭解)
我們先來看看本來的SQL Server認為的最佳執行語句
圖一
由圖一我們能夠發現到整體TotalSubtreeCoast大約有4.414左右,而比較執行吃重的部分我已經用黃色標記起來了。這裡我們就先稍微看一下
圖二
一樣我們看到圖二黃色標注的地方,但不一樣的是,這次我利用了OPTION (FORCE ORDER) 強迫SQL Server 依照我認為最佳的執行順序去執行,而發生問題的資料筆所撈的筆數也只有232筆。
很顯然相同語法SQL Server選了一個看起來比較差的執行計畫去執行。
不過也因為有完整的執行計畫參考,我們就依照這當中一些可能的問題,進行調整吧!
圖三
一開始看到執行計畫時,SQL Server會貼心的提醒DBA們,是否有用到的Column缺乏索引,由上圖能夠發現到在SQLServer有建議在Survey_Question表上創建一個NCL Index 。而下圖的執行計畫圖示中也顯示出Survey_Question是採用Index Scan的方式進行資料查找。這裡我們就先依照SQL Server的建議創建相關的索引
(但要注意的是,SQL Server給的建議是在語法很是的簡單的情形下,一但語法過度複雜時,SQL Server就無法判斷了,須要DBA們依照經驗進行索引的調整唷!)
圖四
調整之後能夠發現到本來的Index Scan已經因為能用到適合的索引,而變成Index Seek了。可是我們仍發現到左邊粉紅色標註的地方,有一個Index Scan的動做可是這時候SQL Server並未顯示有索引缺漏。
我們來詳看一下這段作了什麼事
圖五
透過上圖能夠更詳細的瞭解SQL Server實際上,是將兩張表Survey_question與Question_option作了Hash Join動做,而這個選擇是沒有錯的,因為我們看到最底下,Question_option這裡撈取了29萬筆之多的資料。也因為這樣的資料量太大,用Nested Join是很是沒有效率的。但在先前提到的,我們利用OPTION (FORCE ORDER)的結果,並沒有這樣大量的資料出現,因此也表明的是,這確定是還有優化的空間。下一步就先分析兩邊索引的統計資訊
我們先看Question_option這張表,首先是他利用了一個CL Index作了Scan的動做,可是本來的資料表上明明有創建了以question_id為主的NCL,怎麼會沒有使用呢?
如下是相關的統計資訊
圖六 CL_INDEX
圖七 NCL_INDEX
依照我們目前會用到的RANGE_HI_KEY (已經分別用黃色標註) ,能夠看到若是是用NCL_INDEX應該會是比較好的選擇,這裡我們直接利用TABLE HITS測試看看
圖八
能夠見到,雖然用了我們認為比較好的NCL_INDEX,但資料仍然有26萬筆之多。並沒有達到很明顯的降低
這裡我們回頭往上一層追,也就是15行顯示的R_Course_Stu_Ability資料表 。並且根據以前利用
OPTION (FORCE ORDER)執行後的執行計畫,來作一個簡單的對照。(這裡是因為要得知在最好的情況下,各個資料表的筆數應該有多少,藉此比對我們較差的語法,是否仍能夠作修正
圖九 OPTION (FORCE ORDER)的執行計畫結果
能夠從圖九得知,在最好的情形下,我們僅須要232筆資料便可關聯出我們所需的結果
此外透過圖八我們也能夠知道須要針對AbilityID這個Column作修正 (緣由在一開始有提到,因為這個Column的資料重複性過高)
因此我們想辦法讓兩張表JOIN的資料重複性下降 (也就是選用更多的Column來作JOIN,SQL Server就會認為回傳的筆數會比較少)
最後能夠看到修改後的結果以下
雖然本來R_Course_Stu_Ability的資料從5817筆上升到11281筆,可是我們一開始的Question_options資料的筆數直接從26萬筆之多,降低到只有232筆!這一來一往之間。其實效能是有提高的 (能夠看到TotalSubtreeCost 從2.76降低成1.98)
但更有趣的地方在後面
我們能夠先看到綠色註解的部分,這個部分是最一開始額外新增的JOIN Column,而另外一部分則是修改後的Column。
這裡的差別僅僅是從不一樣的資料表中,抓取我們所需的Column而已。
可是再看到執行計畫,能夠發現到這個執行計畫的R_Course_Stu_Ability資料表筆數從本來的1萬多筆,也降低為232筆。而TotoalSubtreeCost更是再度降低到1.328
為什麼只是從A表的Column選擇換到B表的Column選擇,竟然效能能夠提昇如此之多呢?
其實緣由很簡單,以這個案例來說Survey_main在執行資料查詢時,只會回傳一筆資料,可是R_Course_Ability是會回傳多筆資料的。天然而然當我們選擇Ability表時,SQL Server會認為即便資料的重複性降低了,但還是要回傳必定的資料筆數,因此在產生執行計畫時,會以較多的資料筆數為考量。而若是選擇main表時,因為僅會回傳一筆資料,當然在推估的過程中就是以一筆資料為基準去產生整個執行計畫。
這裡為了證明,我就簡單用兩張表搭配CTE來看看測試結果。
能夠看到我特地在A表上額外建了兩個Column,這裡組合起來會是一個惟一值,而B表上也是弄了一樣的Column,但這裡面的值是會有重複的
再來看我們的測試語法
第一個區塊就是我提到的JOIN Column選擇,這裡要額外注意的是WHERE條件記得要選用具備主鍵的那張表(這裡是A表),不要選錯選到B表去了喔(有時候會剛好兩張表有相同的Column名稱)!
最後一個區塊就只是一個簡單的JOIN測試,這裡就直接看TotoalsubtreeCost 大約是2.226左右
好啦!最後就來看看若是我們JOIN Column是改選擇A資料表上面呢?
資料筆數我就很少加說明瞭,直接看到TotoalsubtreeCost 大約是1.745左右
是否是很明顯比上一個語法還要好呢?
因此各位在寫Sub-Query或是拆分紅CTE後要從新作JOIN時,不要忘記了。一但Column是要作為JOIN使用的話,記得從資料重複性較低的那張資料表上選唷!
天呀~~這篇文章因為須要測試與推估過程,寫了超級久的。若是覺得不錯的話,再給我個讚吧!
我們下次見
參考文章:
http://www.sqlpassion.at/archive/2014/01/28/inside-the-statistics-histogram-density-vector/
http://www.cnblogs.com/CareySon/archive/2011/12/27/2303508.html