T-SQL之變量導致索引無效
(一)問題提出
1,在開發中是否遇到一個情況,就是在where后寫明具體值時可以用到索引,使用變量時卻不行了呢?
2,是否開始懷疑MS SQL 出現了編譯問題。
(二)測試過程
1,建立測試數據
CREATE TABLE t_order ( orderid INT IDENTITY ( 1 , 1 ) PRIMARY KEY, ordertime DATETIME, productname VARCHAR(50)) GO --創建索引 CREATE INDEX idx_ordertime ON t_order ( ordertime) GO --插入1000000條記錄 WITH cte AS (SELECT NUMBER + 1 AS NUMBER FROM master..spt_values a WHERE a.TYPE = 'P' AND NUMBER < 1000) INSERT INTO t_order (ordertime, productname) SELECT Getdate() - a.NUMBER, LEFT(Newid(),10) FROM cte a CROSS JOIN cte b GO
2,分別查詢
SET STATISTICS io ON --查詢一采用變量 DECLARE @date DATETIME SET @date = Getdate() SELECT * FROM t_order WHERE ordertime > @date GO --查詢二采用變量給出具體值 SELECT * FROM t_order WHERE ordertime > Getdate()
3,對比執行計划發現相差太太太太大了。
查詢1掃描了整個表,查詢2確實很好的一個seek加Look up
(三) 原因分析以及驗證
1,原因分析
因為當你使用變量時,查詢語句在編譯時,並不做SET操作。換句話說,即是SET操作是編譯完成后,執行的時候才執行。所以編譯的時候MS SQL 並不知道◎date的值,所以不能產生一個正確的執行計划。
2,驗證
MS SQL在這種情況總按照一個固定的估計值在產生執行計划(即30%),所以做一個全表掃描更划算。讓我們來論證一下,我們對該表插入了1000000條記錄,按照30% ,所以預估行數就該是300000,查看執行計划,果然如此(注意紅色方框):
(四)解決方案
解決方案1:(使用option(RECOMPILE),在執行時重新編譯):
declare @date datetime set @date=GETDATE() select * from T_order where ordertime>@date option(RECOMPILE)
解決方案2:給定一個參數提示給該查詢
declare @date datetime set @date=GETDATE() select * from T_order where ordertime>@date option(OPTIMIZE FOR (@date='2012-04-29'))
解決方案3:封裝成存儲過程,有人就會疑問了,為什么存儲過程可以呢?在這里大家別把參數和變量混淆了,在SQL SERVER里面寫法都一樣,但意義不完全一樣。存儲過程的編譯實在第一次執行的時候才產生執行計划。
--創建存儲過程 CREATE PROC Sp_select_t_order @date DATETIME AS SELECT * FROM t_order WHERE ordertime > @date GO --執行存儲過程 DECLARE @date DATETIME SET @date = Getdate() EXEC Sp_select_t_order @date
解決方案4:參數化查詢
sp_executesql N'select * from T_order where ordertime>@date', N'@date datetime', @date='2012-04-29'
以上四種解決方案的執行計划都如下,實際環境推薦封裝成存儲過程: