2017-05-18 27 views
1

我有一個包含1,941,092行的數據庫和一個聚集列存儲索引。我在前些天查詢時注意到一些奇怪的行爲,並想要解釋,所以我寫了一些查詢來隔離問題。爲什麼在select語句中檢查null參數會導致查詢執行速度太慢?

查詢

DECLARE @loannumber INT = 2222222; 

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    @loanNumber IS NULL 
    OR LOAN_NUMBER = @loanNumber; 

查詢乙

DECLARE @loannumber INT = 2222222; 

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    LOAN_NUMBER = @loanNumber; 

兩個查詢產生相同的結果集。查詢A的經過時間爲1分39秒;查詢B的經過時間是11秒。從我所知道的情況來看,查詢B不檢查null參數,執行速度提高了87%。

的查詢執行計劃如下:在

Query A       | Query B 
-----------------------------------|----------------------------------- 
Select        | Select 
Cost: 0%       | Cost: 0% 
-----------------------------------|----------------------------------- 
Filter        | Parallelism 
Cost: 6%       | (Gather Streams) 
            | Cost: 1% 
-----------------------------------|----------------------------------- 
Columnstore Index Scan (Clustered) | Columnstore Index Scan (Clustered) 
Cost: 94%       | Cost: 99% 

的選擇統計如下:

Statistic    | Query A | Query B | 
-------------------------|---------|---------| 
Cache Plan Size   | 224 KB | 432 KB | 
Degree of Parallellism |  1 |  8 | 
Estimated Operator Cost | 0 (0%) | 0 (0 %) | 
Estimated Subtree Cost | 22.103 | 6.32266 | 
Estimated Number of Rows | 660449 | 1.00017 | 

執行批次一再顯示了同樣的結果 - 緩存不似乎會影響結果。

問題

爲什麼一個空參數檢查產生如此截然不同的結果?

請注意,我沒有尋找替代方法來編寫查詢。我正在尋找解釋爲什麼會發生這種情況。

+0

您是否在運行這兩個查詢之前清除了緩衝區高速緩存和計劃高速緩存 –

+0

我不確定您的情況,但在過去,我有通過添加查詢提示實現了顯着的加速。看到這個頁面,有很多種:https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query –

+0

你的很多行有一個NULL值那個領域?根據統計數據,它看起來像大約一半的行是NULL。 –

回答

2

Erland Sommarskog explains why這樣的catch-all查詢是不好的,並提供了幾種方法來解決它們。在這種特殊情況下,空檢查會強制查詢以行模式而不是批模式運行。

如果您檢查每個執行計劃中的操作,您會看到空檢查將強制執行計劃中的所有操作以行模式工作。這意味着SQL Server解壓縮列存儲索引,重新構建所有行,然後纔開始掃描和過濾操作。

你會看到類似這樣的:

SELECT <- FILTER <- Columnstore Index Scan 

如果你將鼠標懸停在篩選和索引掃描節點,你會看到估計的執行模式是行

沒有空查你」就會有以下exeuction計劃:

SELECT <- Parallelism <- Filter <- Columnstore Index scan 

過濾器和索引掃描在批處理模式下這種情況下工作,這意味着服務器能夠實際使用列存儲到字典找到實際包含參數值的一個塊。

正如Erland Sommarskog所建議的那樣,您可以使用OPTIMIZE FOR提示讓優化器生成適合特定參數值的執行計劃。在這種情況下,兩個查詢將具有相同的使用批處理模式的執行計劃。

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    @loanNumber IS NULL 
    OR LOAN_NUMBER = @loanNumber 
OPTION (OPTIMIZE FOR (@loanNumber = -1)) ; 

即使使用此修復程序,如果您有很多參數,查詢可能會非常快速地變得非常複雜。我不想想象如果你有比直接OR或AND條件更復雜的東西會是什麼樣子。

一個簡單的解決方案將通過使用像EF或Dapper這樣的ORM來在客戶端生成查詢來擺脫「可選參數」。它們都會在需要時生成參數化查詢,因此您不會失去任何性能或安全優勢。 ORM將只生成您指定的過濾條件,因此查詢將會簡單很多

+0

我發現OPTION(RECOMPILE)產生了最好的性能。奇怪的是,當我使用OPTIMIZE FOR(@loanNumber = x)時,性能下降了。 –

1

看看OR運算符。第一個條件要求在沒有任何過濾器的情況下觀察表格中的所有記錄。 服務器必須爲這種情況制定執行計劃,並在此之後應用第二種情況。

相關問題