在今天的文章里我想談下SQL Server里與索引相關的特殊性能問題。
問題描述假設下列的簡單查詢,在你的日常SQL Server里,這樣的查詢你已經看到過幾百遍了:
1 -- Results in an Index Scan2 SELECT * FROM Sales.SalesOrderHeader3 WHERE YEAR(OrderDate) = 2005 AND MONTH(OrderDate) = 74 GO
用那個簡單查詢,我們請求在特定年份特定月份里的銷售信息。并不復雜。遺憾的是這個查詢性能很差——即使在OrderDate列使用了非聚集索引。當你查看執行計劃時,你會看到查詢優化器選擇了在OrderDate列上的非聚集索引,但遺憾的是SQL Server進行的索引的全掃描,而不是高效的查找操作。
這真不是SQL Server的局限性,而是關系數據庫的工作和思考方式:)只要你在索引列上使用了表達式(函數調用,計算)(即所謂的篩選參數(Search Argument)),數據庫引擎必須去掃描那個索引,而不是進行查找操作。
解決方法在執行計劃里為了獲得可擴展的查找操作,你必須要換種方式重寫你的查詢來避免DATEPART函數的調用:
1 -- Results in an Index Seek2 SELECT * FROM Sales.SalesOrderHeader3 WHERE OrderDate >= '20050701' AND OrderDate < '20050801'4 GO
從重寫的查詢可以看到,查詢返回同樣的結果,但我們已經剔除了DATEPART函數的調用。當你查看執行計劃時,你會看到SQL Server進行了查找操作——在那個情況下,這個是所謂的局部范圍掃描(Partial Range Scan):SQL Server查找到第1個值,然后掃描到請求范圍的最有值。如果你想在索引列上下文調用函數,你必須保證在查詢里,這些函數調用的執行在你列的右側。我們來看一個具體的例子。下面查詢把CreditCardID索引列轉化為CHAR(4)數據類型:
1 -- Results in an Index Scan2 SELECT * FROM Sales.SalesOrderHeader3 WHERE CAST(CreditCardID AS CHAR(4)) = '1347'4 GO
當你仔細看執行計劃時,你會看到SQL Server再次掃描整個非聚集索引。如果你的表越來越大,這是真不能擴展的。如果你在查詢里在你索引列的右側執行轉化,你就可以在索引列上剔除函數調用,SQL Server就可以進行查找操作:
1 -- Results in an Index Seek2 SELECT * FROM Sales.SalesOrderHeader3 WHERE CreditCardID = CAST('1347' AS INT)4 GO
小結
從這篇文章里,你可以看到,在你的索引列里不直接調用任何函數或間接調用函數是非常重要的。不然的話SQL Server會掃描你的索引,而不是進行高效的查找操作。而且當你表越來越大時,掃描從不擴展。
如果你碰到這個特殊行為的其他好例子,想分享的話,歡迎留言。
感謝關注。
新聞熱點
疑難解答