Expert 診斷優化系列------------------語句調優三板斧

    前面三篇經過CPU、內存、磁盤三巨頭,講述瞭如何透過如今看本質,怎樣定位服務器三巨頭反映出的問題。爲了方便閱讀給出連接:html

SQL SERVER全面優化-------Expert for SQL Server 診斷系列

    經過三篇文章的基本介紹,能夠看出系統的語句若是不優化,可能會致使三巨頭都出現異常的表現。因此本篇開始介紹系統中的重頭戲--------------SQL語句!sql

開篇前的囉嗦 

  • 什麼是SQL 語句 ?

    

   這就是SQL 語句! 帥氣吧!還有呢!數據庫

  

   

 

   這也是SQL語句!服務器

 

 

   博主真能騙人,我讀書少也知道,這是「車、馬、炮」的 「車」 ! 沒錯,此篇文章裏會以「車」來表明你的SQL 語句,讓你知道怎樣讓你的「車」從16手報廢車改裝成------------------ |法拉利|函數

   注:SQL語句優化的細節,一本書都寫不全,因此這裏只講述「改裝思想!」工具

改裝有順序------常開的愛車下手

   你的系統中有成千上萬的語句,那麼優化語句從何入手呢 ? 固然是系統中運行最頻繁,最核心的語句了。廢話很少說,上例子:oop

   

   這是一天的語句執行狀況,裏面柱狀圖表示的是對應執行時間段內語句的次數,整體看起來長時間語句很是多。post

   下面看一下具體的語句執行狀況:性能

   

 

   排位第一的語句執行次數38508次,是一個存儲過程(RPC:Completed 表示存儲過程結束,不知道這個的請看profiler的使用說明)。其中的一條語句(SP:StmtCompleted)也就是排在第二位的語句,存儲過程的執行時間大部分消耗這條子語句上!優化

這個例子能夠看出業務系統中使用最頻繁,且遠遠高於其餘處理的語句就是這個執行3W8Q屢次的語句。

   那麼看到這樣的數據,想作優化固然要從這條語句下手了!它就是你最常開的車了! 這個例子中只要解決了這條語句的性能問題,整個系統性能就能夠有一個質的飛躍。

 

--------------------------------上面的狀況,你很專注,只喜歡開一種車!----------------------------

   這個例子中,你喜歡開的車就比較多了,也就是說須要你關注,而且優化的語句較多。(不少語句執行次數都很頻繁,也就是系統中使用到的頻繁功能較多)

   

 

   

 

   系統優化須要按部就班,從系統最頻繁的語句出發,逐個解決語句問題。

   有人看到這會說,博主你有工具,能收集、能統計,我啥也沒有咋整?不要急後文腳本都會奉上

改裝前的知識儲備

  知識儲備很重要,語句的優化涉及的地方不少不少,要麼爲何說能夠寫本書呢?

  1. 你知道什麼是執行計劃麼?如何在語句執行的同時,看到執行計劃?
  2. 你知道索引有幾種?有什麼區別麼?
  3. 你知道有索引和沒索引,語句執行的區別麼?
  4. 你知道什麼是統計信息麼?
  5. 你知道什麼是臨時表,表變量,CTE?有什麼區別?
  6. 什麼是事務?什麼是隔離級別?
  7. 你知道什麼是邏輯讀,什麼是物理讀,什麼是預讀麼?怎麼查看你執行消耗的IO資源?
  8. 你知道什麼是等待?怎麼查看你運行的語句是否在等待?等待反應出的問題是什麼?
  9. 你瞭解SQL的鎖機制麼?
  10. 你瞭解TempDB麼?什麼樣的語句會使用TempDB?
  11. 編譯與重編譯?
  12. 查詢提示是幹什麼的?
  13. .....
  14. .....
  15. .....
  16. .....

常見的改裝方式

------------------------------------新手區-----------高手勿進-------------------------------------  

  是否是就無法短期內,掌握大部分語句的優化技巧呢? 這是能夠的,簡單介紹一下語句簡單粗暴的調優方式:

  

開啓執行計劃,讓執行計劃告訴你,語句慢的緣由

  

 

透過計劃,一眼看出索引


   

 

   當語句執行後,執行計劃中會提示你這條運行的語句中是否缺乏索引,右鍵綠色部分"缺乏索引提示",點擊缺乏索引詳細信息,生成對應的索引腳本,創在在數據庫中。

   在次執行語句驗證是否有效,若是還繼續提示索引缺失,繼續按照此方法建立索引。

   

 

 

   索引對於一個語句的影響很大,一個有效的索引能夠縮短語句的執行時間,而且下降CPU、IO、內存等消耗。也就是說不但讓你的語句執行快,更下降了寶貴的系統資源消耗!

   執行計劃中除了能夠看出缺失的索引,也能夠看出語句的主要消耗在哪。知道了主要消耗,咱們也就能夠針對這個消耗進行優化。如例子中94%的開銷在表的掃描上。當看到這個開銷很大而且是一個掃描的時候,第一反應要看掃描的表,有沒有篩選條件「where」條件,或 「關聯條件join" 若是有條件,那就看爲何沒有先用條件過濾數據!是否是沒有索引? 是否是建立的索引不能用(隱式轉換?列上有函數?等等,具體爲何不能使用索引,請自行百度) 

 

   高能提示:不要小看索引,感受這都是小兒科。在我親身經歷的衆多客戶之中,大面積缺乏索引的系統能夠佔到三層以上。或是軟件開發完,對數據庫就沒有創建索引,或是隨着系統的日積月累,數據量、功能也隨之增長,系統得不到一個及時的跟蹤優化致使。

 

    

下降語句的複雜度

  講一個我本身的故事,我剛從業的時候對數據庫的優化了解不深,一度認爲本身寫的SQL 好牛逼,由於如今給我,我真是看不懂。一個語句兩張A4紙都打印不下!各類子查詢,視圖嵌套,函數嵌套,UNION ALL等等等。

不可否認這種語句寫出來之後,有種小自豪感!由於別人根本看不懂,改也改不了!這種語句在對於SQL 的優化器來講就是災難,下面簡單的說下優化器拿到一條語句怎麼樣做出執行計劃:

  首先傳入一個語句,若是有視圖,則會把你視圖內的代碼和外層代碼通過二次編譯變成一個大語句(多層視圖都會編譯成一個),而後從錶鏈接開始,優化器會根據統計信息,和一些預查詢(如執行所須要的字段類型長度,數據量等)針對你的條件選用一個表做爲驅動表,而後繼續和其餘的表關聯,並選用關聯方式(hash、merge、nested loop)等,每次關聯順序和方式的都基於SQL的預估,也就是關聯的越多,最後的預估可能越不許確,進而致使選用一個比較差的計劃。爲何有好的不選卻選出一個差的呢?由於優化器不會把你全部執行的可能都驗證一次,而後選擇一個最好的。這裏選出來的「最優」的只是一個相對值。

  介紹的有點跑題了,下面咱們說一降低低語句複雜度的經常使用方式:最經常使用的就是臨時表,好比先把條件篩選性較強的幾張表關聯,而後把結果放入臨時表,在用臨時表和其餘表關聯。能夠理解成我有10張表關聯,我先拿5張表出來關聯,而後把結果放入臨時表,再跟另外5張表關聯。這樣這個查詢的複雜度由10張表的聯合變成 5+6,這樣下降了複雜語句複雜度。

  複雜視圖也是如此,在視圖和外層關聯前,放入臨時表,再跟外層關聯。

  子查詢也是如此,能夠分離出來成爲臨時表的子查詢,先分離出來。

  

  狀況不少種,最終目的就是下降語句複雜性,讓語句分多個步驟執行,這樣也可讓優化器每次選出一個比較穩定的計劃(一個語句執行有時快有時慢,也極可能是語句的複雜性致使的)。

  

  高能提示:部分系統核心處理的語句比較複雜,且已經不少年前留下的遺產了,經歷了一代又一代,我真心不敢碰。那麼恭喜你中獎了,好好分析下業務,經過臨時表拆分語句仍是有可能的!

       臨時表和表變量,最大的區別是表變量做爲中間過程表不能插入太多數據,若是數據插入的多嚴重影響性能。

 

 

下降並行度,使用並行提高性能

  這個小標題好像有些矛盾!解釋一下下降並行度是由於如今的服務器配置CPU數都很大64或更多的隨處可見,系統選用並行計劃時,使用過多的CPU 反而會使性能降低具體請參見:Expert 診斷優化系列------------------你的CPU高麼?

  首先看一個等待: CXPACKET

  

   

   CXPACKET 是最多見的等待之一,等待 並行計劃 CPU的調度,或線程上的資源等待,請參見

sys.dm_os_waiting_tasks 引起的疑問(上)

sys.dm_os_waiting_tasks 引起的疑問(中)

sys.dm_os_waiting_tasks 引起的疑問(下)

   當你看見如圖的等待狀況時,說明你係統中並行度須要調整了!請參見系列中的CPU篇,這裏不過多介紹。

 

   另外一種狀況,語句能夠經過並行來提高執行時間,這裏也不過多介紹,請參見SQL提示介紹-強制並行

使用一切方法下降讀次數

  一個語句運行起來消耗的讀次數也少,說能夠間接說明這個語句優化程度較高,讀取的頁數少也會下降內存和磁盤的壓力。

   優化時能夠開set statistics io on 來觀察語句的IO消耗狀況。下降IO的主要方式就是添加索引和下降語句複雜度。

   注:重點關注讀次數多的表!

   

   這裏就不細說了!

 

不能忽視的硬件問題

  前三篇一直在強調語句很影響服務器資源。但不能忽略的一點就是,語句的運行好壞也很依賴於資源,硬件資源就比如路面環境。語句這車再好,路沒有那麼寬,也不平坦,再好的車也跑不起來。

反過來就算硬件足夠好,路夠寬也夠好,沒有好車也是跑不起來的!

  

 --------------博客地址---------------------------------------------------------------------------------------

Expert 診斷優化系列 http://www.cnblogs.com/double-K/

-----------------------------------------------------------------------------------------------------

  總結:語句運行的效率是系統的關鍵,而運行最頻繁的語句就是關鍵中的關鍵。找出系統運行頻率高且效率較差的語句進行優化,是優化思路中的核心。

     80%的優化不須要你有高深的技術積累,程咬金的三板斧輪上去,也會掃倒一大片的。請參見 」常見改裝「中的三種手段。

     剩下20%的優化就須要對知識的不斷積累,在實際場景中得到更好的知識提高。

     

     硬件和語句相互依賴,都是最優的那天然是好,可是做爲技術人員咱們保證系統語句是最優的,也是一種責任的體現!

     

     本文只是很是簡單的介紹常規優化的思路和方法,不足之處請諒解。後續文章中也會針對等待、執行計劃、tempDB等繼續細說系統的優化。

 

   PS: 優化須要使用各類手段,反覆嘗試才能達到一個最好的效果,優化無止境。

 -------------------------乾貨到了---------------------------------------------------------------------------    

 沒有本身的SQL工具怎麼找出執行頻繁的語句呢?

  1. profiler 對系統進行監控(不會的小夥伴,快去百度吧)  
  2. DMV視圖 ,篩選條件請自行修改

 

with aa as (
SELECT  
--執行次數 
QS.execution_count, 
--查詢語句 
SUBSTRING(ST.text,(QS.statement_start_offset/2)+1, 
((CASE QS.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) 
ELSE QS.statement_end_offset END - QS.statement_start_offset)/2) + 1 
) AS statement_text, 
--執行文本 
ST.text, 
--執行計劃 
qs.last_elapsed_time,
qs.min_elapsed_time,
qs.max_elapsed_time,
QS.total_worker_time, 
QS.last_worker_time, 
QS.max_worker_time, 
QS.min_worker_time 
FROM 
sys.dm_exec_query_stats QS 
--關鍵字 
CROSS APPLY 
sys.dm_exec_sql_text(QS.sql_handle) ST 
WHERE 
QS.last_execution_time > '2016-02-14 00:00:00' and  execution_count > 500

-- AND ST.text LIKE '%%' 
--ORDER BY 
--QS.execution_count DESC

)
select text,max(execution_count) execution_count --,last_elapsed_time,min_elapsed_time,max_elapsed_time 
from aa
where [text] not  like '%sp_MSupd_%' and  [text] not like '%sp_MSins_%' and  [text] not like '%sp_MSdel_%' 
group by text
order by 2  desc

 

 

 

 怎麼查看本身系統缺失的索引?適合大批量建立索引

這裏的DMV信息只是記錄自上次SQL Server啓動之後的信息項,也就是說每次重啓以後這部分信息就丟失了,因此對於生產系統,建議確保運行了一段週期以後再進行查看。

在咱們從新建立彙集索引的時候,SQL Server會默認的從新生成所有非彙集索引,若是表數據量特別大,這個過程會很漫長,若是不指定ONLINE的話,這個過程會是鎖定索引B-Teee的,這就意味着是阻塞的,業務就要停下來等待完成操做。

------------------缺失索引-----------------------
SELECT migs.group_handle, mid.* 
FROM sys.dm_db_missing_index_group_stats AS migs 
INNER JOIN sys.dm_db_missing_index_groups AS mig 
ON (migs.group_handle = mig.index_group_handle) 
INNER JOIN sys.dm_db_missing_index_details AS mid 
ON (mig.index_handle = mid.index_handle) 
WHERE migs.group_handle = 2
----------------------------------無用索引----------------------
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(O.Schema_ID) AS SchemaName 
, OBJECT_NAME(I.object_id) AS TableName 
, I.name AS IndexName 
INTO #TempNeverUsedIndexes 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?]; INSERT INTO #TempNeverUsedIndexes 
SELECT 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(O.Schema_ID) AS SchemaName 
, OBJECT_NAME(I.object_id) AS TableName 
, I.NAME AS IndexName 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
LEFT OUTER JOIN sys.dm_db_index_usage_stats S ON S.object_id = I.object_id 
AND I.index_id = S.index_id 
AND DATABASE_ID = DB_ID() 
WHERE OBJECTPROPERTY(O.object_id,''IsMsShipped'') = 0 
AND I.name IS NOT NULL 
AND S.object_id IS NULL' 
SELECT * FROM #TempNeverUsedIndexes 
ORDER BY DatbaseName, SchemaName, TableName, IndexName 
DROP TABLE #TempNeverUsedIndexes

--------------------------常常被大量更新,可是卻基本不適用的索引項--------------------
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
DB_NAME() AS DatabaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, s.user_updates 
, s.system_seeks + s.system_scans + s.system_lookups 
AS [System usage] 
INTO #TempUnusedIndexes 
FROM sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?]; INSERT INTO #TempUnusedIndexes 
SELECT TOP 20 
DB_NAME() AS DatabaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, s.user_updates 
, s.system_seeks + s.system_scans + s.system_lookups 
AS [System usage] 
FROM sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE s.database_id = DB_ID() 
AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
AND s.user_seeks = 0 
AND s.user_scans = 0 
AND s.user_lookups = 0 
AND i.name IS NOT NULL 
ORDER BY s.user_updates DESC' 
SELECT TOP 20 * FROM #TempUnusedIndexes ORDER BY [user_updates] DESC 
DROP TABLE #TempUnusedIndexes

 

 ----------------------------------------------------------------------------------------------------

注:此文章爲原創,歡迎轉載,請在文章頁面明顯位置給出此文連接!
若您以爲這篇文章還不錯請點擊下右下角的推薦,很是感謝!

  引用高大俠的一句話 :「拒絕SQL Server背鍋,從我作起!」

爲了方便閱讀給出系列文章的導讀連接:

SQL SERVER全面優化-------Expert for SQL Server 診斷系列

相關文章
相關標籤/搜索