Sql-Server

WHERE IN 語句的超過 64 個參數使 MSSQL 讀取的行數過多

  • May 2, 2020

我有一些查詢需要獲取超過 64 個特定行,例如這個具有 65 個 ID 的範例。TableID 是主鍵,鍵入 BigInt。

SELECT * FROM TableA 
       WHERE TableID IN (260905384, 260915601, 260929877, 260939625, 260939946, 261096977, 261147037, 261152934, 261163936, 261357728, 261369122, 261376714, 261454472, 261488500, 261527284, 261584786, 261619749, 261679560, 261777653, 261786639, 261795246, 261795810, 261803724, 261821199, 261824173, 261827397, 261840197, 261848595, 261874545, 261889122, 261889355, 261929793, 261953069, 262106609, 262134069, 262134088, 262339745, 262354363, 262360015, 262571936, 262586920, 262591486, 262663776, 262703601, 262746674, 262792439, 262801544, 262826561, 262933229, 262933270, 262947539, 262958110, 263021588, 263032875, 263037208, 263039292, 263045038, 263085369, 263089147, 263091427, 263097644, 263100021, 263103339, 263104396, 263956373)

如果我檢查執行計劃它使用主鍵,但它執行 65 次並添加一個 Constand Scan 和 Nested Loops 操作項。但是 - 如果我將參數數量減少到 64 個,那麼它只會直接執行 1 次,沒有其他操作。

我可以看到,對於 65+ 個參數,Seek Predicates 只包含一個元素,如果參數數量為 64 個或更少,Seek Predicates 直接包含所有元素。

當參數數量超過64時,是否可以避免MSSQL多次執行參數?

在小表上差異並不大,但如果我將結果與其他表結合起來,讀取差異的數量就會變得很大。

要使用 StackOverflow2013 數據庫重現這一點,例如:

/* 63 rows: */
SELECT *
 FROM dbo.Users
 WHERE Id IN (-1,1,2,3,4,5,8,9,10,11,13,16,17,19,20,22,23,24,25,26,27,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,48,49,50,51,52,55,56,57,58,59,60,61,62,63,64,67,68,70,71,72,73,75,76,77,78);

/* 64 rows: */
SELECT *
 FROM dbo.Users
 WHERE Id IN (-1,1,2,3,4,5,8,9,10,11,13,16,17,19,20,22,23,24,25,26,27,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,48,49,50,51,52,55,56,57,58,59,60,61,62,63,64,67,68,70,71,72,73,75,76,77,78,79);

/* 65 rows: */
SELECT *
 FROM dbo.Users
 WHERE Id IN (-1,1,2,3,4,5,8,9,10,11,13,16,17,19,20,22,23,24,25,26,27,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,48,49,50,51,52,55,56,57,58,59,60,61,62,63,64,67,68,70,71,72,73,75,76,77,78,79,80);
GO

前兩個的實際執行計劃只顯示了聚集索引搜尋,但是第三個添加了一個常量掃描,並且估計的行數突然不正確:

執行計劃

在此範例中,沒有連接。但是,您可以想像,當您加入其他表時,這種不正確的估計可能會導致表掃描與索引查找 + 鍵查找之間的差異,並且如果估計值較低,則可能會選擇不正確的索引查找 + 鍵查找計劃其他表。

一個IN子句被“重寫”為這種形式,本質上是:

WHERE 
   TableID = 260905384
   OR TableID = 260915601
   OR TableID = 260929877
   ...

我聽說並觀察到,SQL Server 有 64 個OR謂詞的硬編碼限制,它將放入掃描或查找運算符。據我所知,這在任何地方都沒有公開記錄,而且我也不知道有任何方法可以改變它。

正如您所提到的,除了 64 個OR表達式之外,您最終會得到“恆定掃描”計劃,該計劃將多次搜尋或掃描到索引中(每個文字一個)。

將這麼多值文字值放入IN構造中通常被認為是一個壞主意。如果可能,請更改查詢的編寫方式。例如,您可以將所有這些值插入到臨時表中,然後對該列INNER JOIN上的該臨時表執行操作TableID

我嘗試使用臨時表並INNER JOIN在其上。在這種情況下,讀取次數從 4M 以上減少到只有 2K。持續時間從 4500 毫秒到 300 毫秒。

在我的特定情況下,MSSQL 最終將其作為表主鍵上的 Seek Predicates 執行此操作:

Seek Keys[1]: Start: [DB].[dbo].[TableA].TableID >= Scalar Operator((260905384)); End: [DB].[dbo].[TableA].TableID <= Scalar Operator((263956373))

如果它(正如 Martin Smith 在他的評論中所期望的那樣)對列表中的每個不同項目進行搜尋,情況會好得多。但是,由於某種我不明白的原因,它選擇了大於和小於方法。

所以回答接受了(不幸的是,我以訪客使用者的身份回答了這個問題,所以現在似乎不可能將其標記為“接受”)

引用自:https://dba.stackexchange.com/questions/266293