この例のように、64個のIDを持つ64個を超える特定の行を取得する必要があるクエリがあります。 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回実行され、スキャンとネストされたループの操作操作項目が追加されます。ただし、パラメーターの数を64に減らすと、他の操作なしで直接1回だけ実行されます。
65以上のパラメーターを使用すると、Seek Predicatesには1つの要素しか含まれず、パラメーターの数が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
最初の2つの 実際の実行計画 はクラスター化インデックスのシークのみを示していますが、3番目は定数スキャンを追加しており、推定行数は突然正しくありません。
この例では、結合はありません。ただし、他のテーブルに結合すると、この誤った推定により、テーブルスキャンとインデックスシーク+キールックアップに違いが生じる可能性があり、ローボールの推定では、誤ったインデックスシーク+キールックアッププランが選択される可能性があると想像できます。他のテーブル。
IN
句は、基本的にこの形式に「書き換え」られます。
WHERE
TableID = 260905384
OR TableID = 260915601
OR TableID = 260929877
...
SQL Serverには、64のハードコード化された制限OR
がスキャンまたはシークオペレーターに置かれるという述語があると聞いて、観察しました。これは、私が知る限り、どこにも公開されておらず、変更する方法も知りません。
64 OR
式を超えると、前述のように、インデックスへの複数のシークまたはスキャン(リテラルごとに1つ)を実行する「定数スキャン」プランになります。
多数の値をリテラル値としてIN
構造に配置することは、一般に悪い考えと見なされます。可能であれば、クエリの記述方法を変更してください。たとえば、これらのすべての値を一時テーブルに挿入してから、INNER JOIN
をTableID
列の一時テーブルテーブルに追加します。
一時テーブルとINNER JOIN
上に。この場合、読み取り回数は4Mを超えていたのが2Kに減少しました。 4500msから300msまでの期間。
私の特定のケースでは、MSSQLはテーブルの主キーのシーク述語としてこれを行うことになりました。
Seek Keys[1]: Start: [DB].[dbo].[TableA].TableID >= Scalar Operator((260905384)); End: [DB].[dbo].[TableA].TableID <= Scalar Operator((263956373))
(マーティン・スミスもコメントで期待しているように)リスト内の個別のアイテムごとにシークを行っていれば、はるかに優れていたでしょう。ただし、何らかの理由で理解できません。代わりに、多かれ少なかれアプローチを選択します。
だから、回答は受け入れられました(残念ながら、私はゲストユーザーとして質問をしたので、今は「受け入れられた」とマークすることは不可能のようです)