最近監控到類似這樣一個慢查詢:
select delete_flag,delete_timefrom D_OrderInfo WHERE ( OrderId is not null and OrderId = N'xxxx')
D_OrderInfo表上有一個OrderId的索引,但OrderId字段是Varchar類型。
由於開發框架MyBatis自動生成Where條件不會指定參數類型,字符串類型的參數到了SQLServer裡就自動成了NVARCHAR(4000)類型了,坑人的是,不指定參數類型也就罷了,還自動加了個OrderId Is NOT NULL這樣一個非SARG的條件,執行計劃成了這樣:
---------------------------------------------------------------------------------------------
如果沒有OrderId IS NOT NULL這個條件,執行計劃會是這樣的:
由於參數類型Nvarchar比索引字段類型varchar優先級要高,不能直接轉換,但SQLServer優化器最終還是將他轉成了一個範圍值,最終的等號查詢也變成了類似一個小範圍查詢。
可以從Index Seek這一步的詳細信息可以看出:
------------------------------------------------------------------------
如果參數類型匹配,那麼執行計劃會是想像中的那樣(雖然沒有包含到,還是有Key Lookup):
當然,有點小小強迫症的我最終希望的寫法是這樣的:
select delete_flag,delete_timefrom D_OrderInfo WHERE OrderId = 'xxxx'
執行計劃當然也會是這樣的:
只是,只是不知道最終開發大神能改成什麼樣......
開發大神的解決方案:連接字符串中配置:
sendStringParametersAsUnicode=false
後記:
默認情況下,Java 中的字符數據作為Unicode 進行處理;Java String 對象表示Unicode 字符數據。在JDBC 驅動程序中,唯一可以不遵守此規則的是ASCII 流getter 和setter 方法,這屬於比較特殊的情況,因為這些方法使用的字節流帶有單個已知代碼頁(ASCII) 的隱式假定。
此外,JDBC 驅動程序提供了sendStringParametersAsUnicode 連接字符串屬性。此屬性可用於指定作為ASCII 而不是Unicode 來發送的字符數據的預定義參數。
作為性能方面的一項增強功能,可以通過設置sendStringParametersAsUnicode 連接字符串屬性將String 參數以非Unicode 格式傳遞到SQL Server。 sendStringParametersAsUnicode 的默認設置為“true”,這意味著String 參數將作為Unicode 進行發送。
如果sendStringParametersAsUnicode 設置為“false”,則連接上的所有String 參數將使用數據庫默認的排序規則發送到服務器。
參考:
http://d.hatena.ne.jp/gnarl/20110706/1309945379
https://technet.microsoft.com/zh-cn/library/ms378857(SQL.90).aspx
https://technet.microsoft.com/zh-cn/library/ms378988(v=sql.90).aspx