SQL Server 執行計划操作符詳解(1)——斷言(Assert)


前言:

 

很多很多地方對於語句的優化,一般比較靠譜的回復即使——把執行計划發出來看看。當然那些只看語句就說如何如何改代碼,我一直都是拒絕的,因為這種算是純蒙。根據本人經驗,大量的性能問題單純從語句來看很難發現瓶頸,同一個語句,由於環境的不同,差距非常大,所以比較合適的還是分析執行計划。

那么對於執行計划,一般使用圖形化執行計划就差不多了,但是用過的人也有一些疑惑,里面的圖標(稱為操作符)並不非常直觀。所以從本文開始,會整理一些不怎么常見但由比較重要的操作符並進行解釋,對於那些表掃描、索引掃描、聚集索引掃描、索引查找、聚集索引查找這些非常常見的操作符,暫時不打算介紹。

只有了解一些重要且常見的操作符,才能對語句進行准確有效的性能分析和優化。

本系列文章預計包含下面操作符:    

  1. 斷言:Assert (英文版本圖形化界面的名字,中文版本中XML格式的執行計划和TEXT格式的執行計划的名字。下同)    
  2. 串聯:Concatenation   
  3. 計算標量:Compute Scalar  
  4. 鍵查找:Key Lookup      
  5. 假脫機:Spools      
  6. 表假脫機:Lazy Spool   
  7. 索引假脫機:Index Spool      
  8. 行計數假脫機:Row CountSpool
  9. 流聚合:Stream Aggregate  
  10. 排序:Sort     
  11. 合並聯接:Merge Join 
  12. 合並間隔:Merge Interval   
  13. 拆分、折疊:Split,Collapse

 接下來從斷言開始介紹。原文出處:http://blog.csdn.net/dba_huangzj/article/details/50261747


斷言:


Assert運算符是一個物理運算符。在執行計划中,如果為中文版圖形化執行計划,被稱為“斷言”,在英文版及非圖形化執行計划中顯示為Assert。

其圖標為:Assert 運算符圖標

Assert 運算符用於驗證條件。例如,驗證引用完整性或確保標量子查詢返回一行。對於每個輸入行,Assert 運算符都要計算執行計划的 Argument 列中的表達式。如果此表達式的值為 NULL,則通過 Assert 運算符傳遞該行,並且查詢執行將繼續。如果此表達式的值非空,則將產生相應的錯誤。


斷言與Check約束:

 

先來看看這段代碼,在服務器執行時,先創建測試環境,使用TempDB是不錯的選擇:


USE tempdbGO

IF OBJECT_ID('TableAssert') IS NOT NULL
DROP TABLE TableAssert
GO

CREATE TABLE TableAssert (
ID INTEGER
,Gender CHAR(1)
)
GO

ALTER TABLE TableAssert ADD CONSTRAINT ck_Gender_M_F CHECK (Gender IN ('M','F'))
GO

選中下面代碼,不要執行,選擇“顯示估計的執行計划”,如圖:


代碼如下:

INSERT INTO TableAssert(ID ,Gender    )VALUES (1,'X')GO

從上圖可見有一個操作符叫“斷言(Assert)”,那么這個里面是什么東西呢?把鼠標移到這個操作符上面可以看到下圖:


注意上面的解釋:用於驗證指定的條件是否存在,這個解釋很直觀,並且看謂詞部分,說明了實際驗證的內容,判斷Gender字段的插入值是否屬於F/M兩種,如果不是則返回NULL。

斷言操作符會針對驗證返回值進行處理,如果驗證返回NULL,則返回錯誤信息,也就是如果你直接執行INSERT語句就可以看到報錯:

 

原文出處:http://blog.csdn.net/dba_huangzj/article/details/50261747


斷言與外鍵約束:


下面來看個關於外鍵約束的例子:

use tempdbgoALTER TABLE TableAssert ADD ID_Genders INT GO  IF OBJECT_ID('TableFOREIGN') IS NOT NULL   DROP TABLE TableFOREIGN GO CREATE TABLE TableFOREIGN(ID Integer PRIMARY KEY, Gender CHAR(1))  GO  INSERT INTO TableFOREIGN(ID, Gender) VALUES(1, 'F') INSERT INTO TableFOREIGN(ID, Gender) VALUES(2, 'M') INSERT INTO TableFOREIGN(ID, Gender) VALUES(3, 'N') GO  ALTER TABLE TableAssert ADD CONSTRAINT fk_Tab2 FOREIGN KEY (ID_Genders) REFERENCES TableFOREIGN(ID) GO  

同樣,我們使用估計執行計划測試一下INSERT語句:


語句如下:

INSERT INTO TableAssert(ID, ID_Genders, Gender) VALUES(1, 4, 'X')

這次我們使用另外一個工具:SET SHOWPLAN_TEXT ON 按這種方式執行:

SET SHOWPLAN_TEXT ONGOINSERT INTO TableAssert(ID, ID_Genders, Gender) VALUES(1, 4, 'X')

會看到兩個結果,第一個是語句,不用關,我們看第二個結果:

|--Assert(WHERE:(CASE WHEN NOT [Pass1009] AND [Expr1008] IS NULL THEN (0) ELSE NULL END))       |--Nested Loops(Left Semi Join, PASSTHRU:([tempdb].[dbo].[TableAssert].[ID_Genders] IS NULL), OUTER REFERENCES:([tempdb].[dbo].[TableAssert].[ID_Genders]), DEFINE:([Expr1008] = [PROBE VALUE]))            |--Assert(WHERE:(CASE WHEN [tempdb].[dbo].[TableAssert].[Gender]<>'F' AND [tempdb].[dbo].[TableAssert].[Gender]<>'M' THEN (0) ELSE NULL END))            |    |--Table Insert(OBJECT:([tempdb].[dbo].[TableAssert]), SET:([tempdb].[dbo].[TableAssert].[ID] = [@1],[tempdb].[dbo].[TableAssert].[ID_Genders] = [@2],[tempdb].[dbo].[TableAssert].[Gender] = [Expr1004]), DEFINE:([Expr1004]=CONVERT_IMPLICIT(char(1),[@3],0)))            |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[TableFOREIGN].[PK__TableFOR__3214EC27173876EA]), SEEK:([tempdb].[dbo].[TableFOREIGN].[ID]=[tempdb].[dbo].[TableAssert].[ID_Genders]) ORDERED FORWARD)

這個結果內容較多可能不直觀,讀者可以執行測試看結果。

可以看到里面有兩次Assert,自下而上地閱讀,第一個Assert(也就是下面那個,針對於圖形化界面而言是右邊那個,因為圖形化執行計划是從右到左地閱讀)是前面用於CHECK約束的,如果返回0則繼續運行語句,否則返回錯誤。

對於第二個Assert用於檢測兩表關聯的結果,其中“[Expr1008] IS NULL”(注意[Expr1008]不是固定的,根據每台機器可能返回不同值,在本人機器上的SQL 2008/2012分別執行都得到不同的[Expr]值),我們需要知道[Expr1008]是什么,內容中有DEFINE:([Expr1008] = [PROBE VALUE]),這就是表關聯的結果。如果INSERT語句中ID_Gender的值已經存在與TableFOREIGN,那么這個Probe(探測器)會返回關聯值。否則返回NULL。所以這個“斷言”是檢查TableForeign中的值,如果沒有找到INSERT中傳入的值,斷言會返回一個異常。

如果ID_Genders的值為NULL,那么SQL Server不能返回異常,而是返回“0”並繼續運行語句。如果運行上面的INSERT語句,SQL Server會返回異常,因為值為’X’,違反了check約束:


但是如果把X換成F再運行,還是會報錯,因為違反了外鍵約束:

但是當把4換成NULL或1或2或3之后,再運行插入語句,就不會產生異常:

原文出處:http://blog.csdn.net/dba_huangzj/article/details/50261747

斷言與子查詢:


斷言操作符同樣可以用於檢查子查詢,對於標量子查詢不能返回多個值,但是有時候寫法和數據的變動會引發多值錯誤。此時斷言扮演着校驗標量子查詢是否返回一個值的角色。

下面來看看這兩個語句:

INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert), 'F')    INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert), 'F')

用上面的方法查看一下執行計划:

SET SHOWPLAN_TEXT ONGOINSERT INTO TableAssert(ID,Gender) VALUES((SELECT ID FROM TableAssert), 'F') INSERT INTO TableAssert(ID,Gender) VALUES((SELECT ID FROM TableAssert), 'F')

觀察語句大概可以知道發生什么情況,第一個insert會成功(除非你已經修改過里面的數據),因為VALUES中的SELECT部分只返回一個值,但是第二個INSERT由於VALUES中的SELECT有兩個值(第一個INSERT加入的),所以會報錯。

結果如下: 

|--Assert(WHERE:([Expr1013]))      |--Compute Scalar(DEFINE:([Expr1013]=CASE WHEN[tempdb].[dbo].[TableAssert].[Gender]<>'F' AND[tempdb].[dbo].[TableAssert].[Gender]<>'M' THEN (0) ELSE NULL END))           |--Table Insert(OBJECT:([tempdb].[dbo].[TableAssert]),SET:([tempdb].[dbo].[TableAssert].[ID] =[Expr1009],[tempdb].[dbo].[TableAssert].[Gender] =[Expr1010],[tempdb].[dbo].[TableAssert].[ID_Genders] = NULL))                 |--Top(TOP EXPRESSION:((1)))                      |--ComputeScalar(DEFINE:([Expr1009]=[Expr1012], [Expr1010]='F'))                           |--Nested Loops(LeftOuter Join)                                |--ConstantScan                               |--Assert(WHERE:(CASE WHEN [Expr1011]>(1) THEN (0) ELSE NULL END))                                     |--StreamAggregate(DEFINE:([Expr1011]=Count(*),[Expr1012]=ANY([tempdb].[dbo].[TableAssert].[ID])))                                         |--Table Scan(OBJECT:([tempdb].[dbo].[TableAssert]))

注意最內層的Assert:


可以看到SQL Server創建一個StreamAggregate(流匯聚,可從預估執行計划中看到其解釋,后續會專門介紹)去計算子查詢會返回多少數據,然后把這個值傳遞給斷言用於檢測。

作為已經商業化二十幾年的產品,其核心(查詢優化器)已經經過了很多年的積累和改進,高版本的SQL Server(如2008 R2及以上版本,這個沒有絕對標准),會對語句和表結構的當前情況來判斷是否需要使用“斷言,Assert”操作符。比如:

INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert WHERE ID = 1), 'F') INSERT INTO TableAssert(ID, Gender) VALUES((SELECT TOP 1 ID FROM TableAssert), 'F')
原文出處:http://blog.csdn.net/dba_huangzj/article/details/50261747

先不執行,開啟估計執行計划再看圖形化界面,可以看到如下結果:


因為優化器檢測到第二個語句里面包含了TOP 1,僅返回一行數據,所以沒有必要引入斷言來檢測。


總結:


到這里為止,對這個操作符的介紹已經完畢,下一篇會介紹串聯操作符。對於這個斷言操作符,我們需要知道它是用來“驗證”某些條件,但是每個操作符的引入都必將帶來一定的開銷,可是這些操作符的引入又是必須的,因為需要它們完成一些任務。如果需要改進,不妨先看看它是用來檢驗什么,比如上面提到的子查詢,可以通過使用TOP 1、添加唯一約束等方式來減少這種校驗。但是所有改進都應該做充分的測試和論證。

原文出處:http://blog.csdn.net/dba_huangzj/article/details/50261747




注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com