更改過程或觸發器中的SET選項將致使從新編譯

SQL Prompt根據數據庫的對象名稱、語法和代碼片斷自動進行檢索,爲用戶提供合適的代碼選擇。自動腳本設置使代碼簡單易讀--當開發者不大熟悉腳本時尤爲有用。SQL Prompt安裝便可使用,能大幅提升編碼效率。本教程介紹了SQL Prompt的性能規則PE012,該規則將建議您是否在存儲過程或觸發器中檢測到SET語句的使用,這可能會致使沒必要要的從新編譯,儘管問題涉及其餘類型的批處理。sql

有時,因爲某種顯而易見的緣由,您將有一個存儲過程或觸發器間歇地花費更長的時間運行。您已經檢查了索引,排除了諸如參數嗅探之類的問題,可是間歇性的性能問題仍然存在。SET爲了更改執行設置,是否能夠像您在批處理中發出語句那樣簡單呢?若是這樣作,則多是因爲SQL Server須要從新編譯該過程或重複觸發而致使了該問題。數據庫

從新編譯沒有什麼特別的錯誤,實際上,強制執行某些查詢在每次執行時從新編譯是很常見的,正是爲了不與參數嗅探、濫用Execute()或一應俱全的查詢有關的不良性能問題。可是,若是從新編譯變得過多,尤爲是對於頻繁或昂貴的查詢,則可能會成爲問題,值得調查緣由,我將向您展現如何使用擴展事件。緩存

什麼是從新編譯?安全

當SQL Server執行臨時批處理或查詢或諸如存儲過程或觸發器之類的對象時,SQL Server將爲每一個批處理或對象以及該批處理或對象中的每一個查詢編譯針對當前狀態進行優化的執行計劃數據庫,其對象及其數據。SQL Server的優化器設計此計劃須要花費時間和資源,可是必須在代碼能夠傳遞到執行引擎以前完成。幸運的是,咱們傾向於重複執行相同的查詢或過程,可能使用不一樣的參數,所以SQL Server將其生成的大多數計劃存儲在計劃緩存中,而且不管咱們使用什麼參數值,都將確保全部計劃均可以安全地重用。當咱們再次執行相同的批處理或對象時,只要有可能,它將簡單地重用其緩存的計劃。數據結構

可是,有時咱們會從新執行存儲過程,或者從新提交批處理或查詢優化器以前已見過的緩存,而且針對該優化器在緩存中具備優化的計劃,可是因爲某些緣由,它沒法重用該計劃並編譯一個新的。這是從新編譯,而且因爲各類緣由而發生。若是執行引擎檢測到表已更改或其統計信息已發生重大變化,它將自動發生,這時它將標記要從新編譯訪問該表的查詢的全部緩存計劃。下次運行其中一個查詢時,優化器將生成新計劃,而舊計劃將被刪除。ide

咱們還能夠經過將OPTION (RECOMPILE)提示附加到查詢來強制優化器不斷從新編譯計劃。該查詢的計劃可能仍在高速緩存中,但不會被重用。一般這樣作是爲了處理因爲參數嗅探,使用「catch-all」過程,濫用Execute()等等所致使的不穩定性能。sqlserver

爲了節省時間和資源,SQL Server會在可能的狀況下進行語句級的從新編譯。若是批處理或存儲過程當中僅一個語句的計劃因數據結構或數據的基礎更改而無效,或者只有一個語句具備OPTION (RECOMPILE)提示,則僅從新編譯受影響的語句的計劃,而不從新編譯整個批處理或存儲。性能

有時,從新編譯既不會因數據結構或數據的更改而自動觸發,也不會因爲使用提示而被強制執行。咱們在同一數據庫上從新執行相同的查詢,存在一個匹配的緩存計劃,由於提交的查詢的SQL文本和與該緩存計劃相關聯的SQL文本徹底匹配(包括空格和回車符),可是該計劃沒有被重用。測試

再次,有幾種可能的緣由,咱們將不在這裏進一步討論,例如,對未在過程當中靜態建立的臨時表的引用,或者缺乏模式驗證,而咱們將要解決的緣由是緩存的計劃是使用與提交查詢的鏈接所使用的SET選項不一樣的SET選項建立的。優化

「影響計劃重用」的SET選項

更改某些SET選項的值(有時稱爲「影響計劃重用」的選項)將更改查詢的運行方式及其結果。所以,當優化器檢查其緩存計劃是否匹配時,它包括檢查在編譯緩存計劃中使用的SET選項是否與發佈批次的鏈接中使用的SET選項匹配。若是它們不匹配,則它將不會重複使用現有計劃,而是會編譯一個新計劃。

這意味着您能夠看到多個緩存的計劃,除了這些SET選項的細節外,它們基本上是相同的。

這些「計劃重用影響」選項,按字母順序排列,ANSI_DEFAULTS、ANSI_NULL_DFLT_OFF、ANSI_NULL_DFLT_ON、ANSI_NULLS、ANSI_PADDING、ANSI_WARNINGS、ARITHABORT、CONCAT_NULL_YIELDS_NULL、DATEFIRST、DATEFORMAT、FORCEPLAN、LANGUAGE、NO_BROWSETABLE、NUMERIC_ROUNDABORT和QUOTED_IDENTIFIER。

當SQL Server在編譯過程當中執行「恆定摺疊」時,會檢測到這些SET語句,而且彷佛在舊版本的SQL Server中,每次調用該過程時,將其中某些SET選項更改成某些值可能會致使從新編譯。可是,在最新版本的SQL Server中,不多聽到此問題。

可是,明智的改變是SET選項,在批處理開始時,甚至在觸發器過程內更改選項,能夠致使編譯新計劃,只有在執行徹底相同的批處理或對象,具備徹底相同的設置時,才能夠從新使用該計劃。雖然以這種方式從新編譯計劃不多會引發主要的性能問題,但確實會帶來CPU成本,而且可能會引發問題,尤爲是對於編譯成本高且執行頻率高的複雜查詢,甚至可能同時出現這兩種狀況在多語句程序中。

更改鏈接設置

對於ODBC、ADO或JDBC鏈接,爲鏈接的默認設置指定任何更改的方法是,在首次創建鏈接後執行初步的SET語句批處理。鏈接字符串中沒有容許該操做的選項:必須由SET語句完成。在SSMS中,您可使用「查詢」菜單(「查詢」 >「查詢選項」)爲鏈接的執行行爲指定高級和ANSI標準選項。在進行開發和測試時,值得將它們設置爲與生產系統鏈接所使用的相同。這些設置僅反映創建鏈接時的執行設置。若是隨後在鏈接中的批次中更改設置,則這些設置將用於後續批次。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(上)

您會注意到,此選項卡(和ANSI選項卡,沒有顯示)中的SET選項沒有涵蓋全部「計劃-重用-影響」選項。其他的操做必須在經過SET選項語句創建新鏈接時完成。

經過更改SET選項更改結果

如前所述,會話SET選項的更改在某些狀況下可能致使錯誤或警告,或者致使查詢的結果不一樣。快速演示值得一提,在這裏,我將在每批開始時簡單地更改幾個SET選項的值:


在ARITHABORT設置爲ON的狀況下,查詢遇到0除時,查詢將以錯誤(咱們捕獲到這個錯誤)結束,所以返回2行。當咱們關閉此選項時,同一查詢將返回3行:

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(上)

若是檢查每一個批次的計劃,除了這些SET選項的值(打開SELECT操做符的屬性以查看它們)以外,您將看到它們是相同的:

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(上)

如下查詢將向咱們展現計劃緩存中的狀況(我已經在PhilFactor數據庫中完成了此操做,所以您須要進行更改)。


獲得這個結果…

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(上)

因爲SET選項設置不一樣(235和4331),每一個批次都有本身的編譯計劃。您會注意到,該計劃的一個屬性set_options,爲您提供了全部SET選項的位圖值,其中大多數選項爲on或off。

每次更改這些設置選項中的一個時,您都會看到專門爲該選項集建立的新計劃,這顯然會增長對緩存的要求以及編譯計劃所花費的CPU時間。若是您對這兩個批次執行十次,您將看到使用了適當的計劃,而無需從新編譯。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(上)

在存儲過程當中更改SET選項

到目前爲止,咱們僅處理批處理,可是若是因爲某種緣由要確保使用特定設置執行各個過程該怎麼辦?

我已經將相同的邏輯封裝在三個存儲過程當中,前兩個對咱們的兩個選項使用了特定的設置,而第三個沒有任何SET選項語句。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(下)

我對這三個過程分別執行了兩次,首先是在全部選項均使用「默認」設置的會話中進行,其中ARITHABORT和ANSI_WARNINGS均處於ON狀態(set_options = 4347),而後從前者處於ON狀態然後者處於OFF狀態的會話中(4331),最後從兩個都關閉的會話中(235)。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(下)

咱們總共看到9個計劃,每次從具備不一樣set_options值的鏈接執行該計劃時,都會爲每一個過程編譯一個新計劃。換句話說,若是調用批處理的執行設置與編譯該過程的任何執行計劃時有效的執行設置不匹配,則會使用新的set選項建立一個新的緩存計劃。若是咱們使用鏈接相同set_options值從新執行相同的存儲過程,則該計劃將被重用。

調用第一個存儲過程(顯式設置ARITHABORT爲ON)始終返回2行,而調用第二個存儲過程始終返回3行。在不使用SET語句的狀況下調用過程時,它僅取決於調用鏈接的設置。

若是您更改了過程當中的設置,則它們僅在該過程當中有效,所以它們不會影響調用該過程的批處理。全部9個計劃都顯示了用於執行調用批處理的鏈接的SET選項值。

在過程和觸發器中捕捉「影響計劃的重用」的SET語句的使用

SQL Prompt中的性能規則(PE012)看起來是否SET在存儲過程和觸發器(儘管不是批處理)中作出了任何「影響計劃重用」的SET語句。您還可使用SQL Change Automation運行檢查,以在數據庫構建源中發現此問題。SQL Monitor還支持代碼分析。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(下)

不過請注意:這種現象不只適用於過程或觸發器,並且還適用於任何臨時批處理、使用sp_executesql執行的批、準備好的查詢和動態SQL。若是發出「影響計劃重用」SET語句,則對於其中任何一個的緩存計劃都沒法如此輕鬆地重用,而且在SQL Server的早期版本中,每次使用都會有從新編譯的風險。

咱們優先使用存儲過程和觸發器來處理動態Transact-SQL批處理,由於它們更易於重用。它們是參數化的,所以SQL文本永不更改,從而促進了重用。在準備好的批次或過程當中更改設置時,設置選項僅用於執行準備好的批次或過程,

批處理也能夠重用,可是若是經過sp_executesql或Prepare方法(而不是動態SQL或Execute方法)執行批處理,SQL Server發現這樣作更容易。

更糟糕的是,在執行臨時批處理時,SET選項中的任何更改都會從該批處理中泄漏出來,從而使鏈接保留其新設置:您必須顯式還原設置,可是在該點以前當即停止該批處理的錯誤,將沒法執行代碼。而後,優化器可能須要編譯新計劃,以針對您在該鏈接上執行的全部後續批處理和過程的這些新設置。

很難檢測到此錯誤,它加強了如下通常建議:在創建鏈接後,這些語句必須始終做爲初步批處理執行,而且隨後避免任何更改。這意味着全部此類SET語句在代碼中都是可疑的,應被視爲「SQL代碼氣味」。很難證實它們的合理性。

調查過分從新編譯

在擴展事件不可用或過於粗糙的SQL Server版本中,可使用SQL Server Profiler。儘管SP:Recompile跟蹤事件能夠僅用於報告過程和觸發器的語句級從新編譯,但SQL:StmtRecompile也能夠用於跟蹤和調試從新編譯,它能夠檢測存儲過程,觸發器,臨時批處理的從新編譯,使用sp_executesql,準備好的查詢和動態SQL執行的批處理。SP:Recompile和SQL:StmtRecompile的event子類列包含一個整數代碼,指出從新編譯的緣由。

經過擴展事件,事情變得更加文明。咱們能夠得到有關從新編譯及其緣由的完整報告。這是一個簡單的會話,用於報告各個編譯。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(下)

這樣,咱們能夠得到單個從新編譯的詳細信息。我一般在sqlserver.username字段上添加會話事件過濾器,以僅針對特定用戶(運行測試代碼的測試用戶的名稱)得到從新編譯。不然會產生不少噪音。

SQL Prompt使用教程:更改過程或觸發器中的SET選項將致使從新編譯(下)

總結

若是您發現代碼中包含涉及「計劃重用影響」選項的SET語句,那麼這就是代碼的味道,您應該調查緣由。

您固然能夠作一些狡猾而聰明的事情,可是在我從事SQL Server開發的工做中,我從未發現過。這不只是存儲過程或觸發器中的不良作法,並且還可能以任何批次執行屢次。若是須要設置語言、ANSI選項或錯誤處理兼容性,則在建立鏈接並建立單個標準時進行設置。若是這樣作失敗,則會致使SQL Server執行沒必要要的從新編譯。

當我寫這些SET語句的使用是「很差的」時,我並不但願暗示批處理的從新編譯必定是很差的:有時它們避免了一些隱匿的性能問題之一,而且它們不多會影響性能只要不沉迷於SQL代碼,應用程序的氣味就沒必要要了。例如,當咱們建立要重用的批處理時,咱們老是經過與參數sp_ExecuteSQL一塊兒使用來促進代碼重用,或者在應用程序中,咱們正確地使用綁定參數。爲了謹慎起見,咱們使用表變量。

相關文章
相關標籤/搜索