最近遇到一個問題,當查詢使用到模糊查詢時,由于預估返回行數過高,執行計劃認為索引查找+Key Lookup的成本過高,因此采用Clustered Index Scan的方式,消耗大量邏輯IO,執行計劃較差。
經過測試,發現對于模糊查詢,NVARCHAR和VARCHAR的預估返回行數差距很大,因此拿出來供大家一起測試。
首先生成測試數據,分別創建TB101和TB102的表,表上有相同的聚集索引和非聚集索引,表中有100w數據,創建測試數據腳本如下:
DROP TABLE TB101GODROP TABLE TB102GOSELECT CAST(NCHAR(19968+20902*RAND(RID))+NCHAR(19968+20902/2*RAND(RID))+NCHAR(19968+20902/3*RAND(RID)) AS varchar(40)) AS RName,*INTO TB101FROM(SELECT ROW_NUMBER()OVER(ORDER BY T1.OBJECT_ID DESC) AS RID,T1.* FROM sys.all_objects T2CROSS JOIN sys.all_columns T1) AS TWHERE T.RID<1000000GOSELECT * INTO TB102 FROM TB101GOALTER TABLE TB102ALTER COLUMN RName NVARCHAR(40)GOCREATE UNIQUE CLUSTERED INDEX IDX_RID ON TB101(RID)GOCREATE UNIQUE CLUSTERED INDEX IDX_RID ON TB102(RID)GOCREATE INDEX IDX_Name ON TB101(RName)GOCREATE INDEX IDX_Name ON TB102(RName)GOEXEC sp_spaceused 'TB101'EXEC sp_spaceused 'TB102'GO
兩表使用空間相同,數據相同。
測試前先更新下統計:
--更新統計UPDATE STATISTICS TB101 WITH FULLSCANGOUPDATE STATISTICS TB102 WITH FULLSCAN
開始測試1
SELECT RName FROM TB101WHERE RName LIKE '你好%'
其執行計劃為:
測試2:
SELECT RName FROM TB102WHERE RName LIKE N'你好%'
感謝網友“害怕飛的鳥”的提醒,我們測試了以中文開頭的模糊查詢,需要測試以字母開頭的模糊查詢。
因此重新創建測試用例(生成新的測試數據目的為了避免查詢值落在統計的兩端,原理請參考大神高繼偉的SQL Server 統計信息(Statistics)-概念,原理,應用,維護)
準備測試數據:
SELECT CAST(NCHAR(65+25*RAND(RID))+NCHAR(24*RAND(RID))+NCHAR(19968+20902/2*RAND(RID))+NCHAR(19968+20902/3*RAND(RID)) AS varchar(40)) AS RName,*INTO TB103FROM(SELECT ROW_NUMBER()OVER(ORDER BY T1.OBJECT_ID DESC) AS RID,T1.* FROM sys.all_objects T2CROSS JOIN sys.all_columns T1) AS TWHERE T.RID<1000000GOSELECT * INTO TB104 FROM TB103GOALTER TABLE TB104ALTER COLUMN RName NVARCHAR(40)GOCREATE UNIQUE CLUSTERED INDEX IDX_RID ON TB103(RID)GOCREATE UNIQUE CLUSTERED INDEX IDX_RID ON TB104(RID)GOCREATE INDEX IDX_Name ON TB103(RName)GOCREATE INDEX IDX_Name ON TB104(RName)GO
測試1:
SELECT RName FROM TB103WHERE RName LIKE 'A你好%'
測試2:
SELECT RName FROM TB104WHERE RName LIKE N'A你好%'
通過上面四個測試,可以得出以下結論:
1. 當字段的數據類型為NVARCHAR時,無論模糊查詢以中文還是英文字母開頭,預估返回行數和實際返回行數相差不多
2. 當字段的數據類型為VARCHAR且模糊查詢以英文字母開頭,預估返回行數和實際返回行數相差不多
3. 當字段的數據類型為VARCHAR且模糊查詢以中文開頭,預估返回行數和實際返回行數相差較大。
--==============================================================
當預估返回行數與實際返回行數相差較大時,就很容易生成較差的執行計劃,如對于查詢:
SELECT * FROM TB101WHERE RName LIKE '你好%'
由于預估索引查找會返回50w的數據,查詢優化器引擎認為如果使用索引查找+Key Lookup就會消耗上200W+的邏輯IO, 效率會遠低于表掃描,因此有了下面的執行計劃:
而實際上,經過索引查找后,只會返回少量的數據行,這些行做Key Lookup也只會消耗少量的邏輯IO,因此索引查找+Key Lookup是最高效的。
解決辦法:
對于這種問題,可以有幾種辦法處理:
1. 強制索引查找
SELECT * FROM TB101 WITH(FORCESEEK)WHERE RName LIKE '你好%'
2. 使用隱式轉化
SELECT * FROM TB101WHERE RName LIKE N'你好%'
經過一次隱式轉換后,預估返回行數出奇地下降下來,生成了正確的執行計劃(看來隱式轉換也是有存在價值地哦)
3. 如果不想修改程序的話,可以考慮使用參數化和執行計劃指南來實現
--=========================================================================================
總結(以下結論未經過大神認證,請自行組鑒別正確性):
1. 當字段的數據類型為NVARCHAR時,無論模糊查詢以中文還是英文字母開頭,預估返回行數和實際返回行數相差不多
2. 當字段的數據類型為VARCHAR且模糊查詢以英文字母開頭,預估返回行數和實際返回行數相差不多
3.當字段的數據類型為VARCHAR且模糊查詢以中文開頭,預估返回行數和實際返回行數相差較大。
4. 隱式轉換未必會導致表掃描或索引掃描,也未必會導致執行計劃質量不優。
--===========================================================
新聞熱點
疑難解答