案例環境:
操作系統版本 : Windows Server 2008 R2 Standard SP1
數據庫版本 : Microsoft SQL Server 2012 (SP1) - 11.0.3000.0 (X64)
案例介紹:
由於不能將生產環境的代碼和數據貼上來,所以我構造了下面一個小案例,當然沒法和生產環境的案例一致。只能是接近而已。但是足以反映問題本質就足夠了。
DROP TABLE ProductPrice;
GO
CREATE TABLE ProductPrice
(
ProductName VARCHAR(14),
Sequence INT ,
ProductPrice FLOAT
)
GO
構造8000條測試數據,然后將數據插入臨時表#tmp(其實完全可以不用臨時表,只因為生產環境也是臨時表,故模擬接近案例環境)
DECLARE @index INT =1;
DECLARE @subindex INT;
WHILE @index <= 800
BEGIN
SET @subindex = 1;
WHILE @subindex <=10
BEGIN
INSERT INTO ProductPrice
SELECT 'product' + convert(varchar,@index), @subindex, rand()*1000;
SET @subindex = @subindex +1;
END;
SET @index = @index +1;
END
SELECT * INTO #tmp FROM ProductPrice;
GO
本來開發人人員也許是要使用動態SQL語句獲取下面這樣一段SQL語句(隨意構造小例子,形似神不似)
DECLARE @sqlText NVARCHAR(MAX) ='';
SELECT @sqlText=@sqlText+ quotename(productname)+
'=CAST(MAX(CASE WHEN [productname]='+QUOTENAME(productname,'''')
+' THEN [productPrice] END) AS VARCHAR)'
FROM #tmp
GROUP BY ProductName
SELECT datalength(@sqlText);
但是由於疏忽或是對動態SQL不了解,寫成了這樣一個SQL語句,結果執行時間一下子飈增到7分多鍾。
DECLARE @sqlText NVARCHAR(MAX) ='';
SELECT @sqlText=@sqlText+ quotename(productname)+
'=CAST(MAX(CASE WHEN [productname]='+QUOTENAME(productname,'''')
+' THEN [productPrice] END) AS VARCHAR)'
FROM #tmp ;
SELECT datalength(@sqlText);
看來SQL對於處理非常長的字符串對象有一定的性能問題,於是為了驗證我的想法,我又構造了下面一個例子。創建臨時表#tmp,數據來源於 sys.all_columns
DROP TABLE #tmp;
GO
SELECT * INTO #tmp FROM sys.all_columns;
GO
7364 行受影響)
然后我們來看一下下面SQL語句
DECLARE @output NVARCHAR(MAX)
SELECT @output=ISNULL(@output,'') + QUOTENAME(name) + REPLICATE('it is only a test ', 200)
FROM #tmp
那么我們來看看這條SQL的執行計划,如下所示,很普通的執行計划,看不出有啥特別之處。但是執行性能那叫一個糟糕透頂!
SET SHOWPLAN_ALL ON;
GO
DECLARE @output NVARCHAR(MAX)
SELECT @output=ISNULL(@output,'') + QUOTENAME(name) + REPLICATE('it is only a test ', 200)
FROM #tmp
StmtText的內容,如下所示:
DECLARE @output NVARCHAR(MAX)
SELECT @output=ISNULL(@output,'') + QUOTENAME(name) + REPLICATE('it is only a test ', 200)
FROM #tmp
|--Compute Scalar(DEFINE:([Expr1004]=isnull([@output],CONVERT_IMPLICIT(nvarchar(max),'',0))+quotename([tempdb].[dbo].[#tmp].[name])+N'it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is only a test it is onl'))
|--Table Scan(OBJECT:([tempdb].[dbo].[#tmp]))
雖然能理解處理大對象需要很多資源,會產生一定的性能問題,但是執行時間這么長,還是讓我覺得有點不可思議,但是又不清楚具體原因!