探討OPTION(COMPILE)問題
DECLARE
@AddressLine1 NVARCHAR(60) = NULL,
@AddressLine2 NVARCHAR(60) = NULL,
@City NVARCHAR(30) = NULL,
@PostalCode NVARCHAR(15) = NULL,
@StateProvinceID INT = NULL
SET @City = 'Bothell'
SET @PostalCode = '98011'
SET @StateProvinceID = 79
DECLARE @SQL NVARCHAR(MAX),@ColumnName VARCHAR(4000),@ParamDefinition NVARCHAR(500)
SET @ColumnName = 'a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid'
SET @SQL = 'SELECT ' + @ColumnName + ' FROM Person.Address AS a WHERE 1 = 1'
IF (@AddressLine1 IS NOT NULL)
SET @SQL = @SQL + ' AND a.AddressLine1 LIKE ''%'' + @AddressLine1 + ''%'''
IF (@AddressLine2 IS NOT NULL)
SET @SQL = @SQL + ' AND a.AddressLine2 LIKE ''%'' + @AddressLine2 + ''%'''
IF (@City IS NOT NULL)
SET @SQL = @SQL + ' AND a.City LIKE ''%'' + @City + ''%'''
IF (@PostalCode IS NOT NULL)
SET @SQL = @SQL + ' AND a.PostalCode LIKE ''%'' + @PostalCode + ''%'''
IF (@StateProvinceID IS NOT NULL)
SET @SQL = @SQL + ' AND a.StateProvinceID = @StateProvinceID'
SET @ParamDefinition = N'@AddressLine1 NVARCHAR(60),
@AddressLine2 NVARCHAR(60),
@City NVARCHAR(30),
@PostalCode NVARCHAR(15),
@StateProvinceID INT'
EXECUTE sp_executesql @SQL,@ParamDefinition,
@AddressLine1 = @AddressLine1,
@AddressLine2 = @AddressLine2,
@City = @City,
@PostalCode = @PostalCode,
@StateProvinceID = @StateProvinceID
GO
複製程式碼
此時我們將外部變數StateProvinceID型別修改為SMALLINT,然後再來執行查詢和快取計劃,此時會出現查詢計劃使用次數是為2,還是出現兩條次數都為1呢?
此時我們再來將動態SQL中內部變數StateProvinceID型別修改為SMALLINT,此時會出現查詢計劃使用次數是為3,還是出現兩條,次數分別為2和1呢?
由上可知,如果我們修改外部變數引數型別不會影響查詢計劃快取即會達到重用目的,若修改動態SQL內部變數引數型別則不會重用查詢計劃快取。
大多數情況下,我們可能不需要擔心上述問題。但是,在某些情況下,假設從查詢的執行到執行的引數變化很大,則會引起問題。
如果我們確定儲存過程通常執行正常,但有時執行緩慢,則很可能會看到上述問題。在這種情況下,我們可以做的是改變儲存過程,並新增WITH RECOMPILE選項。
講完OPTION(COMPILE),接下來我們講講如何建立高效能的儲存過程。有些童鞋可能會建立如下儲存過程。
CREATE PROC [dbo].[HighPerformanceExample]
(
@AddressLine1 NVARCHAR(60) = NULL,
@AddressLine2 NVARCHAR(60) = NULL,
@City NVARCHAR(30) = NULL,
@PostalCode NVARCHAR(15) = NULL,
@StateProvinceID SMALLINT = NULL
)
AS
SET NOCOUNT ON
SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid
FROM Person.Address AS a
WHERE (a.AddressLine1 = @AddressLine1 OR @AddressLine1 IS NULL) AND
(a.AddressLine2 = @AddressLine2 OR @AddressLine2 IS NULL) AND
(a.City = @City OR @City IS NULL) AND
(a.PostalCode = @PostalCode OR @PostalCode IS NULL) AND
(a.StateProvinceID = @StateProvinceID OR @StateProvinceID IS NULL)
--或者
SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid
FROM Person.Address AS a
WHERE a.AddressLine1 = COALESCE(@AddressLine1, a.AddressLine1) AND
a.AddressLine2 = COALESCE(@AddressLine2, a.AddressLine2) AND
a.City = COALESCE(@City, a.City) AND
a.PostalCode = COALESCE(@PostalCode, a.PostalCode) AND
a.StateProvinceID = COALESCE(@StateProvinceID, a.StateProvinceID)
--或者
SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid
FROM Person.Address AS a
WHERE a.AddressLine1 = CASE WHEN @AddressLine1 IS NULL THEN a.AddressLine1 ELSE @AddressLine1 END
AND a.AddressLine2 = CASE WHEN @AddressLine2 IS NULL THEN a.AddressLine1 ELSE @AddressLine2 END
AND a.City = CASE WHEN @City IS NULL THEN a.City ELSE @City END
AND a.PostalCode = CASE WHEN @PostalCode IS NULL THEN a.PostalCode ELSE @PostalCode END
AND a.StateProvinceID = CASE WHEN @StateProvinceID IS NULL THEN a.StateProvinceID ELSE @StateProvinceID END
GO
SET NOCOUNT OFF
複製程式碼
SELECT ...
FROM ...
WHERE ...
OPTION (RECOMPILE);
複製程式碼
如果我們要使用重新編譯,那麼我們是否需要考慮以下兩個問題呢?
如果我們知道一個特定的語句總是返回相同數量的行並使用相同的計劃(並且我們已測試過並知道這一點),那麼我們會正常建立儲存過程並讓計劃得到快取。
CREATE PROC [dbo].[HighPerformanceExample]
(
@AddressLine1 NVARCHAR(60) = NULL,
@AddressLine2 NVARCHAR(60) = NULL,
@City NVARCHAR(30) = NULL,
@PostalCode NVARCHAR(15) = NULL,
@StateProvinceID SMALLINT = NULL
)
AS
SET NOCOUNT ON
DECLARE @SQL NVARCHAR(MAX),@ColumnName VARCHAR(4000),@ParamDefinition NVARCHAR(500),@Recompile BIT = 1;
SET @ColumnName = 'a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid'
SET @SQL = 'SELECT ' + @ColumnName + ' FROM Person.Address AS a WHERE 1 = 1'
IF (@StateProvinceID IS NOT NULL)
SET @SQL = @SQL + ' AND a.StateProvinceID = @StateProvinceID'
IF (@AddressLine1 IS NOT NULL)
SET @SQL = @SQL + ' AND a.AddressLine1 LIKE @AddressLine1'
IF (@AddressLine2 IS NOT NULL)
SET @SQL = @SQL + ' AND a.AddressLine2 LIKE @AddressLine2'
IF (@City IS NOT NULL)
SET @SQL = @SQL + ' AND a.City LIKE @City'
IF (@PostalCode IS NOT NULL)
SET @SQL = @SQL + ' AND a.PostalCode LIKE @PostalCode'
IF (@StateProvinceID IS NOT NULL)
SET @Recompile = 0
IF (PATINDEX('%[%_?]%',@AddressLine1) >= 4
OR PATINDEX('%[%_?]%', @AddressLine2) = 0)
AND (PATINDEX('%[%_?]%', @City) >= 4
OR PATINDEX('%[%_?]%', @PostalCode) = 0)
SET @Recompile = 0
IF @Recompile = 1
BEGIN
SET @SQL = @SQL + N' OPTION(RECOMPILE)';
END;
SET @ParamDefinition = N'@AddressLine1 NVARCHAR(60),
@AddressLine2 NVARCHAR(60),
@City NVARCHAR(30),
@PostalCode NVARCHAR(15),
@StateProvinceID SMALLINT'
EXECUTE sp_executesql @SQL,@ParamDefinition,
@AddressLine1 = @AddressLine1,
@AddressLine2 = @AddressLine2,
@City = @City,
@PostalCode = @PostalCode,
@StateProvinceID = @StateProvinceID
GO
SET NOCOUNT OFF
複製程式碼