二十一、執行計劃

一、什麼是執行計劃

用戶提交的 sql 語句,數據庫查詢優化器,經過分析生成多個數據庫可以識別的高效執行查詢方式。然
後優化器會在衆多執行計劃中找出一個資源使用最少,而不是最快的執行方案,給你展示出來,可以是
文本格式,也可以是圖形化的執行方案。

二、爲什麼要讀懂執行計劃?

首先執行計劃讓你知道你複雜的 sql 到底是怎麼執行的,有沒有按照你想的方案執行,有沒有按照最高
效的方式執行,使用了衆多索引的哪一個,怎麼排序,怎麼合併數據的,有沒有造成不必要資源浪費等
等。官方數據顯示,執行 t-sql 存在問題,80%都可以在執行計劃中找到答案。

三、這對於圖形化的執行計劃分析

執行計劃,可以以文本,圖形化展示出來。我們主要以圖形化執行計劃主導進行分析,然而執行計劃中
包含78個可用的操作符。
圖片介紹:https://msdn.microsoft.com/zh-cn/library/ms175913(v=sql.90).aspx

四、清除執行計劃

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS感覺

五、如何查看執行計劃

5.1、連線
越粗表示掃描影響的行數愈多。
Actual Number of Rows 掃描中實際影響的的行數。
Estimated Number of Rows 預估掃描影響的行數。
Estimated row size 操作符生成的行的估計大小(字節)。
Estimated Data Size 預估影響的數據的大小。
5.2、 Tooltips ,當前步驟執行信息
Note:這個tips的信息告訴我們執行的對象是什麼,採用的操作操作是什麼,查找的數據是什麼,使用
的索引是什麼,排序與否,預估 cpu、I/O 影響行數,實際行數等信息。
5.3、Table Scan(表掃描)
當表中沒有聚集索引,又沒有合適索引的情況下,會出現這個操作。這個操作是很耗性能的,他的出現
也意味着優化器要遍歷整張表去查找你所需要的數據。
5.4、Clustered Index Scan(聚集索引掃描)、Index Scan(非聚集索引掃描)
這個圖標兩個操作都可以使用,一個聚集索引掃描,一個是非聚集索引掃描。
聚集索引掃描:聚集索引的數據體積實際是就是表本身,也就是說表有多少行多少列,聚集所有就有多
少行多少列,那麼聚集索引掃描就跟表掃描差不多,也要進行全表掃描,遍歷所有表數據,查找出你想
要的數據。
非聚集索引掃描:非聚集索引的體積是根據你的索引創建情況而定的,可以只包含你要查詢的列。那麼
進行非聚集索引掃描,便是你非聚集中包含的列的所有行進行遍歷,查找出你想要的數據。
5.5、Key Lookup(鍵值查找)
首先需要說的是查找,查找與掃描在性能上完全不是一個級別的,掃描需要遍歷整張表,而查找只需要
通過鍵值直接提取數據,返回結果,性能要好。
當你查找的列沒有完全被非聚集索引包含,就需要使用鍵值查找在聚集索引上查找非聚集索引不包含的
列。
5.6、 RID Lookoup (RID查找)
5.7、Clustered Index Seek(聚集索引查找)、Index Seek(非聚集索引查找)
聚集索引查找和非聚集索引查找都是使用該圖標。
聚集索引查找:聚集索引包含整個表的數據,也就是在聚集索引的數據上根據鍵值取數據。
非聚集索引查找:非聚集索引包含創建索引時所包含列的數據,在這些非聚集索引的數據上根據鍵值取
數據。
5.8、Hash Match
這個圖標有兩種地方用到,一種是表關聯,一種是數據聚合運算時。
再分別說這兩種運算的前面,我先說說Hashing(編碼技術)和Hash Table(數據結構)。
Hashing:在數據庫中根據每一行的數據內容,轉換成唯一符號格式,存放到臨時哈希表中,當需要原
始數據時,可以給還原回來。類似加密解密技術,但是他能更有效的支持數據查詢。
Hash Table:通過hashing處理,把數據以key/value的形式存儲在表格中,在數據庫中他被放在
tempdb中。
接下來,來說說Hash Math的表關聯跟行數據聚合是怎麼操作運算的。
表關聯
如上圖,關聯兩個數據集時,Hash Match會把其中較小的數據集,通過Hashing運算放入 HashTable
中,然後一行一行的遍歷較大的數據集與 HashTable 進行相應的匹配拉取數據。
數據聚合:當查詢中需要進行Count/Sum/Avg/Max/Min時,數據可能會採用把數據先放在內存中的
HashTable 中然後進行運算。
5.9、Nested Loops
這個操作符號,把兩個不同列的數據集彙總到一張表中。提示信息中的Output List中有兩個數據集,
下面的數據集(inner set)會一一掃描與上面的數據集(out set),掃描完爲止,這個操作纔算是完
成。
5.10、Merge Join
這種關聯算法是對兩個已經排過序的集合進行合併。如果兩個聚合是無序的則將先給集合排序再進行一
一合併,由於是排過序的集合,左右兩個集合自上而下合併效率是相當快的
5.11、Sort(排序)
對數據集合進行排序,需要注意的是,有些數據集合在索引掃描後是自帶排序的。
5.12、Filter(篩選)
根據出現在having之後的操作運算符,進行篩選
5.13、Computer Scalar
在需要查詢的列中需要自定義列,比如count(*) as cnt ,select name+''+age 等會出現此符號。

六、根據執行計劃做優化

1、對查詢進行優化,應儘量避免全表掃描(避免Table Scan),首先應考慮在 where 及 order by 涉及的列上建立索引。
2、應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。對於非聚集索引,如果使用!=或<>操作符,非聚集索引不生效;
3、應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:可以在數據庫設計時num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:
4、應儘量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全在·表掃描,如:可以這樣查詢:
5、下面的查詢也將導致全表掃描: 如果要走非聚集索引,like的時候,只能把%放到後面;會走索引若要提高效率,可以考慮全文檢索。
6、in 和 not in 也要慎用,否則會導致全表掃描,如:如果使用in ,有聚集索引,會根據索引查找,not int 聚集索引掃描(不推薦)
select id from t where num is null select id from t where num=0 select id from t where
num=10 or num=20 select id from t where num=10 union all select id from t where num=20
select id from t where name like '%abc%' select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了: between 或者是 not between 都會索引查找
7、如果在 where 子句中使用參數(變量),也會導致全表掃描。因爲SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作爲索引選擇的輸入項。如下面語句將進行全表掃描:可以改爲強制查詢使用索引:
8、應儘量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。
9、應儘量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。
10、不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
select id from t where num between 1 and 3 select id from t where num=@num select id
from t with(index(索引名)) where num=@num select id from t where num/2=100 select id
from t where num=100*2 select id from t where substring(name,1,3)='abc' --name以
abc開頭的id select id from t where datediff(day,createdate,'2005-11-30')=0 --'2005-11-30'生
成的id select id from t where name like 'abc%' select id from t where createdate>='2005-11-
30' and createdate<'2005-12-1'
11、在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。
12、不要寫一些沒有意義的查詢,如需要生成一個空表結構:這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
13、很多時候用 exists 代替 in 是一個好的選擇:用下面的語句替換:
14、並不是所有索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如一表中有字段sex,male、female幾乎各一半,那麼即使在sex上建了索引也對查詢效率起不了作用。
15、索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及update的效率,因爲 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有必要。
16、應儘可能的避免更新 clustered 索引數據列,因爲 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那麼需要考慮是否應將該索引建爲 clustered 索引。
17、儘量使用數字型字段,若只含數值信息的字段儘量不要設計爲字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因爲引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。
18、儘可能的使用 varchar/nvarchar 代替 char/nchar ,因爲首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。
19、任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。
select col1,col2 into #t from t where 1=0 create table #t(...) select num from a where num
in(select num from b) select num from a where exists(select 1 from b where num=a.num)
20、儘量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。
21、避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。
22、臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個數據集時。但是,對於一次性事件,最好使用導出表。
23、在新建臨時表時,如果一次性插入數據量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,爲了緩和系統表的資源,應先create table,然後insert。
24、如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。
25、儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該考慮改寫。
26、使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。 27、與臨時表一樣,遊標並不是不可使用。對小型數據集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。
28、儘量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。
29、儘量避免大事務操作,提高系統併發能力
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章