在 SQL Server 查詢中,不經意思的隐匿資料類型轉換可能導緻極大的查詢性能問題,比如一個看起來沒有任何問題簡單的條件:WHERE c = N’x’ ,如果 c 的資料類型是 varchar,并且表中包含大量的資料,這個查詢可能導緻極大的性能開銷,因為這個操作會導緻列 c 的資料類型轉換為 nvarchar與常量值比對,在 SQL Server 2008 及之後的版本中,這種操作做了增強,一定程度上降低了性能開銷,參考SQL Server 2008 處理隐式資料類型轉換在執行計劃中的增強 。
不過在實際應用中發現,這種增強有時候似乎沒有起到作用,還是會存在很大的性能問題。
最近找時間做了一個測試,找出了一種可能的問題。
1. 建立一個測試表
USE tempdb GOCREATE TABLE _t( c varchar(50) );CREATE INDEX IX_c ON _t( c );GO-- 加入 10000 條資料INSERT _tSELECT (9999 + id) FROM( SELECT TOP 10000 id = ROW_NUMBER()
OVER( ORDER BY GETDATE() ) FROM sys.all_columns a, sys.all_columns )ID
2. 通過執行計劃看下查詢計劃
-- Rebuild索引,確定無索引碎片和統計資訊準确
ALTER INDEX IX_c ON _t REBUILD;GO
SET SHOWPLAN_ALL ON
GO
SELECT * FROM _t WHERE c = N'10005b';
GO
SET SHOWPLAN_ALL OFF;
注意EstimateRows列,該列值為1,表示評估的滿足條件的資料是1條,現在看起來一切正常 。

3.把資料變一下,将大量資料變成相同值
-- 将 5000 條資料值變成一樣,重建索引之後重新測試
UPDATE _t SET c = '15000' WHERE c >= '15000'
ALTER INDEX IX_c ON _t REBUILD;
GO
SET SHOWPLAN_ALL ON
GO
SELECT * FROM _t WHERE c = N'10005';
GO
SET SHOWPLAN_ALL OFF;
然後我們發現評估的記錄數變大了
4. 繼續加大相同值的比例
-- 繼續加大相同值的比例,重建索引之後重新測試
UPDATE _t SET c = '11000' WHERE c >= '11000' AND c < '15000'
ALTER INDEX IX_c ON _t REBUILD;
GO
SET SHOWPLAN_ALL ON
GO
SELECT * FROM _t WHERE c = N'10005';
GO
SET SHOWPLAN_ALL OFF;
GO
-- 繼續加大相同值的比例,重建索引之後重新測試
UPDATE _t SET c = '10100' WHERE c >= '10100' AND c < '11000'
ALTER INDEX IX_c ON _t REBUILD;
GO
SET SHOWPLAN_ALL ON
GO
SELECT * FROM _t WHERE c = N'10005';
GO
SET SHOWPLAN_ALL OFF;
相應的,預估的行數也在增加
如果我們使用正确的資料類型,WHERE c = ‘10005’,則始終可以得到正确的預估行數。
我不确定 SQL Server是按照什麼标準來預估這種情況下的記錄數,從執行計劃看,它将 nvarchar 值通過 GetRangeThroughConvert 評估出一個範圍,實際執行的是一個範圍 seek,在試驗中,查詢的值是一個常量,可以準确評估,難道這個轉換之後,把常量當變量評估了,是以是一個泛泛的評估結果值。
這個問題看起來不大,但在實際應用中,如果表的資料量很大,并且不是平均分布的話,這種錯誤的預估結果帶來的性能影響是很大的,比如明明滿足條件的很少,可以 seek, 但評估的結果很大,執行計劃變 Scan了,在複雜的執行計劃中,這個帶來的影響更大。
看起來,2008(包括R2)還沒有那麼省心,這種問題還得控制,特别是程式中,.Net過來的參數通常都是 nvarchar類型,這種導緻性能問題的情況遇到N多了 。
最後啰嗦一下的是,在 SQL Server 2014中,沒有再發現這個問題(不知道 2012中怎麼樣)
原文釋出時間為:2018-09-3
本文作者:鄒建
本文來自雲栖社群合作夥伴“
資料和雲”,了解相關資訊可以關注“
”。