http://www.cnblogs.com/shanksgao/p/4254942.html 高兄這篇文章很好的談論了由於數據隱式轉換造成執行計划不准確,從而造成了死鎖。那如果在事情出現之前發現了這類潛在的風險豈不是更好?
那么我們來看一個簡單的例子,如代碼清單1所示。
1: SELECT *
2: FROM HumanResources.Employee
3: WHERE NationalIDNumber = 243322160
4:
5: SELECT *
6: FROM HumanResources.Employee
7: WHERE NationalIDNumber = '243322160'
代碼清單1.
NationalIDNumber列定義是Nvarchar,而參數第一個為INT類型,第二個為Varchar類型。那么就存在隱式轉換,由高繼偉提到的數據類型轉換優先級(https://msdn.microsoft.com/zh-cn/library/ms190309.aspx)可以看到,第一列Nvarchar和INT屬性類型,INT數據類型優先級高,需要把列NationalIDNumber轉換為INT類型,因此涉及到需要把所有該列值轉換為INT,因此只能通過掃描操作,從而影響性能。
而代碼清單1中第二個查詢,NationalIDNumber列為Nvarchar類型,而參數為varchar類型,根據數據類型優先級,需要將Varchar轉換為Navrchar,因此僅僅需要對參數進行隱式轉換,因此不影響性能。
如何在出現問題之前找到出問題的查詢?
在SQL Server中,執行計划會被緩存起來,以便后續進行復用。SQL Server提供了一系列DMV可以查看這些執行計划。由於執行計划的本質是XML,因此通過XQUERY查詢特定的執行計划變為可能。
在執行計划中,存在隱式轉換的節點會存在類似如代碼清單2所示的字段:
1: <Convert DataType="int" Style="0" Implicit="true">
2: <ScalarOperator>
3: <Identifier>
4: <ColumnReference Database="[AdventureWorks2012]" Schema="[HumanResources]" Table="[Employee]" Column="NationalIDNumber" />
5: </Identifier>
6: </ScalarOperator>
7: </Convert>
代碼清單2.對列進行轉換的執行計划片段
前面提到,只有對列而不是參數進行隱式轉換時,才會影響性能。而在代碼清單2中對列進行隱式轉換的執行計划會引用具體的數據庫名稱、架構名稱、表名稱、列名稱。而對參數進行隱式轉換的僅僅是引用參數,如代碼清單3所示。
1: <Convert DataType="nvarchar" Length="8000" Style="0" Implicit="true">
2: <ScalarOperator>
3: <Identifier>
4: <ColumnReference Column="@1" />
5: </Identifier>
6: </ScalarOperator>
7: </Convert>
既然我們已經知道產生問題的執行計划特征,那么我們就可以利用DMV和Xquery找出這些執行計划,代碼如代碼清單4所示:
1: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
2: DECLARE @dbname SYSNAME
3: SET @dbname = QUOTENAME(DB_NAME());
4: WITH XMLNAMESPACES
5: (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
6: SELECT stmt.value('(@StatementText)[1]', 'varchar(max)') AS SQL_Text ,
7: t.value('(ScalarOperator/Identifier/ColumnReference/@Schema)[1]',
8: 'varchar(128)') AS SchemaName ,
9: t.value('(ScalarOperator/Identifier/ColumnReference/@Table)[1]',
10: 'varchar(128)') AS TableName ,
11: t.value('(ScalarOperator/Identifier/ColumnReference/@Column)[1]',
12: 'varchar(128)') AS ColumnName ,
13: ic.DATA_TYPE AS ConvertFrom ,
14: ic.CHARACTER_MAXIMUM_LENGTH AS ConvertFromLength ,
15: t.value('(@DataType)[1]', 'varchar(128)') AS ConvertTo ,
16: t.value('(@Length)[1]', 'int') AS ConvertToLength ,
17: query_plan
18: FROM sys.dm_exec_cached_plans AS cp
19: CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp
20: CROSS APPLY query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple')
21: AS batch ( stmt )
22: CROSS APPLY stmt.nodes('.//Convert[@Implicit="1"]') AS n ( t )
23: JOIN INFORMATION_SCHEMA.COLUMNS AS ic ON QUOTENAME(ic.TABLE_SCHEMA) = t.value('(ScalarOperator/Identifier/ColumnReference/@Schema)[1]',
24: 'varchar(128)')
25: AND QUOTENAME(ic.TABLE_NAME) = t.value('(ScalarOperator/Identifier/ColumnReference/@Table)[1]',
26: 'varchar(128)')
27: AND ic.COLUMN_NAME = t.value('(ScalarOperator/Identifier/ColumnReference/@Column)[1]',
28: 'varchar(128)')
29: WHERE t.exist('ScalarOperator/Identifier/ColumnReference[@Database=sql:variable("@dbname")][@Schema!="[sys]"]') = 1
代碼清單4.找出隱式轉換的執行計划
對於本例的結果如圖1所示。
圖1.找出隱式轉換的結果
小結
本篇文章提供了通過執行計划緩存找出對性能影響的隱式轉換,在出現問題之前進行調優。對於開發人員來講,注意書寫T-SQL的數據類型可以在后續避免很多問題。
注:由於代碼清單4使用了XQuery,因此在執行計划緩存很大時,會比較慢。

