T-SQL注意事項(1)——SET NOCOUNT ON的去與留

用了一段時間T-SQL以後,哪怕本身沒用過,也多多少少看過SSMS中的SET NOCOUNT ON命令,不少性能優化文章中都有提到這個東西,它們建議儘量使用這個命令減小網絡傳輸的壓力,那麼今天來看看它是不是個雞肋。sql

 

SET NOCOUNT的做用

首先來看看這個命令的做用,根據 官方說明:阻止在結果集中返回顯示受 Transact-SQL 語句或存儲過程影響的行計數的消息。在說明中的「 備註」部分有這麼一段話,注意紅字部分:
當 SET NOCOUNT 爲 ON 時,將不向客戶端發送存儲過程當中每一個語句的 DONE_IN_PROC 消息。  若是存儲過程當中包含一些並不返回許多實際數據的語句,或者若是過程包含 Transact-SQL 循環,網絡通訊流量便會大量減小,所以,將 SET NOCOUNT 設置爲 ON 可顯著提升性能  

若是你們對上面的DONE_IN_PROC有興趣,能夠看看這篇文章: https://msdn.microsoft.com/en-us/library/dd340553.aspx,可是我以爲不必過於糾結。數據庫

下面建立一個測試例子,並作演示:編程

 

 

[sql]  view plain  copy
 
  1. USE tempdb  
  2. GO  
  3.    
  4. IF object_id('NocountDemo', 'U') IS NOT NULL  
  5.     DROP TABLE NocountDemo  
  6. GO  
  7.    
  8. CREATE TABLE NocountDemo(  
  9.     id INT identity(1, 1),  
  10.     NAME VARCHAR(64)  
  11.     )  
  12. GO  
  13.    
  14. /*  
  15. 插入測試數據  
  16. */  
  17. INSERT INTO NocountDemo  
  18. SELECT NAME  
  19. FROM master..spt_values  
  20.    
  21. /*  
  22. 常規使用  
  23. */  
  24. SELECT *  
  25. FROM NocountDemo  
  26. GO  
  27.    
  28. /*  
  29. 使用SET NOCOUNT選項  
  30. */  
  31. SET NOCOUNT ON;  
  32.    
  33. SELECT *  
  34. FROM NocountDemo  
  35.    
  36. SET NOCOUNT OFF;  

 

首先看看默認狀況,也就是SET NOCOUNT OFF的結果:緩存

 

 

 

而後看看開啓NOCOUNT選項的結果:安全

 

 

 

 

 

默認狀況下,任何SQL語句成功完成後都會返回一些信息,而NOCOUNT用於控制是否返回影響行數,須要提醒的是,哪怕不是真正的SQL語句,好比開啓包含實際執行計劃功能,當執行計劃成功完成後(注意是完成),也會返回影響行數到客戶端(也就是這裏的SSMS)。性能優化

可是從實操層面,你們是否幾乎沒有關注過這個信息?確實,是否成功執行完不少時候不須要查看這個信息,一些SELECT語句天然會返回數據結果。因此實際上這個信息經常是非必要的。服務器

做爲最佳實踐,通常建議不返回影響行數,特別是存儲過程,不過有時候它又有存在價值,好比應用程序嵌入的SQL語句的調試。網絡

在不少性能優化的文章中(甚至前面提到的官方文檔)都提到,爲了減輕網絡壓力,建議啓用這個設置(注意一點,這個設置和其餘不少設置不一樣,它默認是「開啓」的,也就是說咱們須要作「關閉」操做,而不少操做是須要作「開啓」操做)。這個緣由貌似很合理,不過做爲DBA的習慣,我更願意深究底層原理,因此下面再看個簡單例子,插入一條數據後,循環更新100萬次,並獲取時間差:架構

 

 

[sql]  view plain  copy
 
  1. SET NOCOUNT OFF;  
  2.   
  3. DECLARE @i INT = 1;  
  4. DECLARE @x TABLE (a INT);  
  5.   
  6. INSERT @x (a)  
  7. VALUES (1);  
  8.   
  9. SELECT SYSDATETIME() AS [開始時間];  
  10.   
  11. WHILE @i < 1000000  
  12. BEGIN  
  13.     UPDATE @x  
  14.     SET a = 1;  
  15.   
  16.     SET @i += 1;  
  17. END  
  18.   
  19. SELECT SYSDATETIME() AS [結束時間];  

 

 

在我本機上執行狀況以下圖:併發

 

 

 

本文重點關注的是影響行數,因此咱們先看影響行數的狀況,選擇結果集中的【消息】頁

 

從右邊的滾動條能夠大概預估這個行數應該不少。實際上每次insert都有一行,外加循環外層的SELECTSYSDATETIME()  ,總共10000002行,而後關閉返回影響行數,即便用SET NOCOUNT命令從新執行:

 

 

 

爲了不有人以爲測試沒考慮併發問題,我使用SQLQueryStress工具分別對上面兩套語句模擬200個線程執行100次(本機配置過低沒法執行太屢次):

默認狀況反覆執行屢次:

 

 

開啓NOCOUNT的狀況下:

 

 

對比一下時間,差別時間不明顯,若是多執行幾回可能會出現反而更慢的狀況,難道網上說的是假的?先別下定論,那問題在哪裏?咱們再回過頭看看別人的描述裏面的關鍵字「網絡傳輸」,貌似發現問題了,由於一直以來都在單機上面測試,並且檢查一下SQL Server配置管理器中的網絡協議,Shared Memory是開啓了,也就是說數據都在內存中傳輸,跟「網絡」沒什麼關係:

 

 

 

那麼看來問題就是這個緣由,如今隨便找臺機器再試一下,我在國際版的Azure上開了個SQL Azure(開VM再裝SQL Server實在耗時並且費用很多),若是沒有條件的能夠找些測試服務器甚至別人的電腦試試。

如今我在本機直接連到美國的SQL Azure上,而後再次分別執行上面的兩個語句(爲了不時間太久,把100萬次改成10萬次):

 

 

 

 

 

 

 

 

 

時間對比以後發現,其實相差不大,咱們不妨靜下來想一下,即便100萬次,產生的數據也就幾十上百Bytes或者KB,對今時今日的硬件來講不可能出現明顯的性能提高,之因此網上有這個說法,極可能是當初的硬件資源存在侷限,沒法知足今天看起來不算大的數據量。可是無論如何,我的看來,這些確實也有必定的消耗,做爲編程習慣,在非必要的狀況下仍是建議不返回,畢竟真的沒什麼人用。

 

問題提煉

對於這個問題,我想到了兩件事情:如何對待別人的「善意」和減小網絡傳輸

 

1.如何對待別人的「善意」

 

最近上下班路上在看一本書,看完再分享一下,書上多處地方提到一個句子:One thing doesn’t fit all。說白了就是「放之四海而皆準」的反義詞。我我的挺贊同這個觀點,爲何非要用一個產品去實現全部功能呢?今時今日大量系統的架構都使用了不少混合技術,這也證實了這個觀點的可行性。那麼迴歸這個問題,當初別人提出這個優化或編程建議時,確實可能存在網絡性能問題、甚至客戶端(本例中的SSMS)的內存資源壓力從而形成性能壓力,正如20年前Oracle優化書籍中提到的一個表索引數不要超過5個、索引葉子節點不要緩存到內存這類建議同樣,當年的硬件資源確實沒辦法很好支持這些特性。再看今天,軟硬件已經有了長足的發展,不少當初是對的設置今天看起來已經無關重要甚至是錯誤的。因此在對待不少所謂的「軍規」、「鐵律」時,仍是要本身實測一下或者論證一下。從本例中看,它不會有什麼嚴重的後果和風險,因此是能夠測試的,可是有些風險比較大的最好慎重測試。我我的認爲,做爲一些編程規範,不妨保留下來,由於它和下面這個有關係。在編程的時候要有減小資源使用的慣性思惟。

那何時有用呢?前面提到了——查錯

在我初爲DBA的時候,有一次一個開發問我:爲何明明插入了一條數據到一個表裏面,而且成功了,表卻沒有數據。一開始我覺得是回滾,問她要完整的語句,拿過來以後發現就一個簡單的insert命令,沒有顯式事務。而後我本身執行了一下,確實沒數據,再看一下影響行數,居然有兩條(1 行受影響),這就引發個人注意了,檢查表觸發器,果真有一個觸發器,而且功能就是一旦有插入動做,立刻觸發刪除。也就是來一個死一個,來兩個死一雙。

基於這些緣由,我認爲具體問題具體分析纔是最有意義的,哪來那麼多所謂的意見建議,不考慮具體環境的建議都是耍流氓。

 

2.減小網絡傳輸  

之因此當初有這個說法,很大程度是由於網絡傳輸壓力。那麼咱們藉助這個問題,引伸其餘資源合理使用的場景,這裏說的是網絡問題,那就說網絡問題吧,單純從數據庫層面來講,一般網絡壓力在哪裏呢?據本人瞭解,大概在這些方面:

a)        須要傳輸的數據量,這是真正的網絡壓力。量越大壓力越明顯,那麼一般咱們要作的就是在返回數據時,儘量控制數據量,好比不要在非必要狀況下使用SELECT *,儘量在離開數據層的時候就把數據量控制下來。

b)        其餘功能傳輸的信息,好比SQL Server一些高可用或者負載分離功能,就拿複製功能來講,爲了使訂閱服務器的數據與發佈服務器的數據一致,發佈服務器會根據實際配置實時或定時發送事務日誌到訂閱端重作,日誌產生越多,須要傳輸的量就越多,對於這部分你能干預的地方就不多,非要作的話,能夠在配置發佈項時只選擇須要同步的行與列。

c)        SQL語句,這部分實際上也不大,可是正如不少編碼規範中說的,儘量使用存儲過程,其中一個理由就是直接傳輸SQL代碼不只不安全,並且量一大的話,也是有開銷的,有些SQL代碼有還幾百KB,對於使用頻繁的系統而言,這也是一筆開銷。固然不是說禁用,具體狀況具體分析吧。另外多說一句,對於超過8KB的SQL代碼,SQL Server不緩存執行計劃,意味着你要每次重編譯可能徹底同樣的SQL代碼,從而形成CPU、內存的壓力。

d)        須要導入、導出數據庫的其餘格式文件,如TXT、Excel等,某些系統須要傳輸一些文件到服務器再進行導入或者導出數據到文件而後經過某些方式傳輸出數據庫服務器以外,這部分能夠經過修改業務邏輯來下降,可是能下降程度可能不高,那麼對於這種狀況,能夠考慮把數據庫服務器和應用程序放在一個局域網中,而後把文件最終須要傳輸的發生地從數據庫服務器移到應用程序所在的服務器,因爲應用程序容易橫向擴展,因此能夠經過一些技術把應用程序的負載下降,這樣即便文件傳輸的量不能下降,最起碼對數據庫層服務器的資源爭用能有必定的緩解。

 

總的來講,我我的建議保留這個功能的常態化關閉、排錯時開啓的說法。最重要的做用實際仍是警示,讓使用者時刻注意對資源的合理使用。

相關文章
相關標籤/搜索