ワイルドカード(またはトップ)の追加に関するSQL Serverクエリの大幅な減速


52

SQL Server 2005データベースで追跡する2,000万匹の動物園があります。それらの約1%は黒で、約1%は白鳥です。私はすべての黒い白鳥の詳細を取得したかったので、私がやった結果ページを埋めたくありませんでした。

select top 10 * 
from animal 
where colour like 'black'  
and species like 'swan'

(はい、これらのフィールドはフリーテキストですが、両方ともインデックス付けされています)。クエリが約300ミリ秒で空のセットを返すため、このような動物はいません。「いいね」ではなく「=」を使用した場合、約2倍の速さでしたが、入力の手間を省くという予感があります。

頭の飼育係は彼が白鳥のいくつかを「黒っぽい」と入力したかもしれないと思うので、それに応じてクエリを修正します:

select top 10 * 
from animal  
where colour like 'black%' 
and species like 'swan'

どちらもありません(実際、「黒」以外の「黒%」動物はありません)が、クエリが空になるまでに約30秒かかります。

それは、「top」と「like%」の組み合わせだけが問題を引き起こしているようです

select count(*) 
from animal  
where colour like 'black%' 
and species like 'swan'

すぐに0を返し、さらに

select * 
from animal 
where colour like 'black%' 
and species like 'swan'

数秒で空を返します。

特に空の結果セットで、「top」と「%」がパフォーマンスの劇的な損失を引き起こすために共謀すべき理由は誰にもわかりませんか?

編集:明確にするために、私はFreeTextインデックスを使用していません。フィールドがエントリポイントでフリーテキストである、つまりデータベースで正規化されていないことを意味します。混乱、文言の質の低さで申し訳ありません。

回答:


76

これは、コストベースのオプティマイザーの決定です。

この選択で使用される推定コストは、異なる列の値間の統計的な独立性を前提としているため、正しくありません。

これは、偶数と奇数が負の相関関係にある「行の目標が不正になった」で説明されている問題に似ています。

簡単に再現できます。

CREATE TABLE dbo.animal(
    id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
    colour varchar(50) NOT NULL,
    species varchar(50) NOT NULL,
    Filler char(10) NULL
);

/*Insert 20 million rows with 1% black and 1% swan but no black swans*/
WITH T
     AS (SELECT TOP 20000000 ROW_NUMBER() OVER (ORDER BY @@SPID) AS RN
         FROM   master..spt_values v1,
                master..spt_values v2,
                master..spt_values v3)
INSERT INTO dbo.animal
            (colour,
             species)
SELECT CASE
         WHEN RN % 100 = 1 THEN 'black'
         ELSE CAST(RN % 100 AS VARCHAR(3))
       END,
       CASE
         WHEN RN % 100 = 2 THEN 'swan'
         ELSE CAST(RN % 100 AS VARCHAR(3))
       END
FROM   T 

/*Create some indexes*/
CREATE NONCLUSTERED INDEX ix_species ON  dbo.animal(species);
CREATE NONCLUSTERED INDEX ix_colour ON  dbo.animal(colour);

今すぐ試してください

SELECT TOP 10 *
FROM   animal
WHERE  colour LIKE 'black'
       AND species LIKE 'swan' 

これにより、以下のコストの計画が得られ0.0563167ます。

ここに画像の説明を入力してください

プランは、id列の2つのインデックスの結果間でマージ結合を実行できます。(マージ結合アルゴリズムの詳細はこちら)。

結合の結合では、両方の入力が結合キーで順序付けられている必要があります。

非クラスター化インデックスは(species, id)(colour, id)それぞれの順序で並べられます(非一意の非クラスター化インデックスには、明示的に追加されない場合、常に暗黙的にキーの最後に行ロケーターが追加されます)。ワイルドカードを使用しないクエリは、species = 'swan'およびへの等価シークを実行していcolour ='black'ます。各シークは先頭の列から正確な値を1つだけ取得するため、一致する行が並べ替えられidます。したがって、このプランは可能です。

クエリプラン演算子は、左から右に実行されます。左の演算子が子から行を要求し子が子から行を要求します(葉ノードに到達するまで続きます)。TOP10が受信された後のイテレータは、その子から任意の複数の行を要求して停止します。

SQL Serverには、行の1%が各述語と一致することを示すインデックスの統計があります。これらの統計は独立している(つまり、正または負の相関がない)と想定しているため、平均して最初の述語に一致する1,000行を処理すると、2番目に一致する10行を見つけて終了できます。(上記の計画は実際には1,000ではなく987を示していますが、十分に近いです)。

実際、述語は負の相関関係にあるため、実際の計画では、200,000の一致するすべての行を各インデックスから処理する必要があることが示されていますが、結合された行が0であるため、実際に必要な検索もゼロになるため、ある程度緩和されます。

と比べて

SELECT TOP 10 *
FROM   animal
WHERE  colour LIKE 'black%'
       AND species LIKE 'swan' 

次のプランを提供します 0.567943

ここに画像の説明を入力してください

末尾のワイルドカードの追加により、インデックススキャンが発生しました。2000万行のテーブルをスキャンする場合でも、プランのコストは非常に低くなります。

追加するとquerytraceon 9130、さらに情報が表示されます

SELECT TOP 10 *
FROM   animal
WHERE  colour LIKE 'black%'
       AND species LIKE 'swan'       
OPTION (QUERYTRACEON 9130)  

ここに画像の説明を入力してください

SQL Serverは、約100,000行をスキャンするだけで、述語に一致する10行が見つかり、TOP行の要求を停止できると見なすことがわかります。

繰り返しますが、これは 10 * 100 * 100 = 100,000

最後に、インデックスの交差プランを強制してみましょう

SELECT TOP 10 *
FROM   animal WITH (INDEX(ix_species), INDEX(ix_colour))
WHERE  colour LIKE 'black%'
       AND species LIKE 'swan' 

これにより、推定コスト3.4625の並列プランが得られます

ここに画像の説明を入力してください

主な違いは、colour like 'black%'述語が複数の異なる色に一致できるようになったことです。これは、その述部に一致する索引行がの順序でソートされることが保証されなくなったことを意味しますid

たとえば、インデックスシークlike 'black%'は次の行を返す場合があります

+------------+----+
|   Colour   | id |
+------------+----+
| black      | 12 |
| black      | 20 |
| black      | 23 |
| black      | 25 |
| blackberry |  1 |
| blackberry | 50 |
+------------+----+

各色の中ではIDが順序付けられていますが、異なる色のIDはそうでない場合があります。

その結果、SQL Serverは(結合ソート演算子を追加せずに)マージ結合インデックスの交差を実行できなくなり、代わりにハッシュ結合を実行することを選択します。ハッシュ結合はビルド入力をブロックしているため、最初の計画のように1,000だけをスキャンする必要があると仮定するのではなく、一致するすべての行をビルド入力から処理する必要があるという事実がコストに反映されます。

ただし、プローブ入力は非ブロッキングであり、それから987行を処理した後にプローブを停止できると誤って推定しています。

(ノンブロッキングとブロッキングイテレータの詳細はこちら)

追加の推定行とハッシュ結合のコストが高くなると、部分クラスター化インデックススキャンは安価になります。

もちろん実際には、「部分的な」クラスター化インデックススキャンはまったく部分的ではなく、プランを比較するときに想定される10万行ではなく、2000万行全体を一気に処理する必要があります。

の値を増やすTOP(または完全に削除する)と、最終的にCIスキャンでカバーする必要があると推定する行の数がその計画をより高価にし、インデックス交点計画に戻ります。私にとって、2つの計画のカットオフポイントはTOP (89)vs TOP (90)です。

クラスター化インデックスの幅に応じて異なる場合があります。

削除しTOPてCIスキャンを強制する

SELECT *
FROM   animal WITH (INDEX = 1)
WHERE  colour LIKE 'black%'
       AND species LIKE 'swan' 

88.0586私の例のテーブルでは、マシン上でコストがかかります。

動物園に黒い白鳥がいないこと、および100,000行を読み取るだけではなくフルスキャンを実行する必要があることをSQL Serverが認識している場合、この計画は選択されません。

私は上の複数のカラムの統計を試していないてきたanimal(species,colour)animal(colour,species)し、上の統計情報をフィルタリングanimal (colour) where species = 'swan'が、これらのヘルプはいずれも黒い白鳥が存在しないということを説得し、TOP 1010万の以上の行を処理するためにスキャン意志が必要。

これは、SQL Serverが何かを検索している場合はおそらく存在するとおそらく想定する「包含の仮定」によるものです。

2008+には、行目標をオフにする文書化されたトレースフラグ4138があります。これの効果は、TOP一致するすべての行を読み取らずに子演算子が早期に終了できるという仮定なしに計画がコストがかかることです。このトレースフラグを設定すると、自然に、より最適なインデックス交差計画が得られます。

SELECT TOP 10 *
FROM   animal
WHERE  colour LIKE 'black%'
       AND species LIKE 'swan'
OPTION (QUERYTRACEON 4138)       

ここに画像の説明を入力してください

このプランは、両方のインデックスシークで20万行すべてを読み取るのに正しくコストがかかりますが、キールックアップにはコストがかかります(推定2000対実際の0。TOP 10これは最大10に制限されますが、トレースフラグは考慮されません) 。それでも、計画は完全なCIスキャンよりも大幅に安価なので、選択されます。

もちろん、この計画一般的な組み合わせに最適ではないかもしれません。白鳥など。

animal (colour, species)理想的にanimal (species, colour)は、両方のシナリオで複合インデックスを使用すると、クエリの効率が大幅に向上します。

複合インデックスを最も効率的に使用するには、LIKE 'swan'をに変更する必要もあり= 'swan'ます。

次の表は、4つのすべての順列の実行プランに示されているシーク述語と残余述語を示しています。

+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
|                 WHERE clause                 |       Index       |                         Seek Predicate                         |              Residual Predicate              |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| colour LIKE 'black%' AND species LIKE 'swan' | ix_colour_species | colour >= 'black' AND colour < 'blacL'                         | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species LIKE 'swan' | ix_species_colour | species >= 'swan' AND species <= 'swan'                        | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species = 'swan'    | ix_colour_species | (colour,species) >= ('black', 'swan')) AND colour < 'blacL'    | colour LIKE 'black%' AND species = 'swan'    |
| colour LIKE 'black%' AND species = 'swan'    | ix_species_colour | species = 'swan' AND (colour >= 'black' and colour <  'blacL') | colour like 'black%'                         |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+

15

この興味をそそり、このQ / Aを検索し、つまずいたの は、TOPが実行計画にどのように(そしてなぜ)影響するかです。

基本的に、TOPを使用すると、それに続く演算子のコストが(自明ではない方法で)変更されるため、全体計画も変更されます(TOP 10の有無にかかわらずExecPlansを含めると素晴らしいでしょう)。クエリ。

お役に立てれば。

たとえば、データベースで試してみました:-topが呼び出されない場合、並列処理が使用されます-TOPでは、並列処理は使用されません

したがって、再度、実行計画を表示すると、より多くの情報が得られます。

ごきげんよう


-1

これは、MSSQL 2005の基礎となる性質と、クエリオプティマイザーがどの実行プランが最も効率的であるかを決定する方法によるものと思われます。

SQL変数を使用する場合、ネストされたループの代わりにハッシュマッチを使用するようにクエリオプティマイザーを「だまし」、より高度な並列処理を行います。

試してください:

DECLARE @topn INT = 10
SELECT TOP (@topn) *
FROM    animal
WHERE   colour LIKE 'black%' 
AND species LIKE 'swan'

5
TOP変数の値を難読化するということは、変数ではTOP 100なく仮定することを意味しTOP 10ます。これは、2つの計画の転換点が何であるかによって、助けになる場合もあれば、ならない場合もあります。
マーティンスミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.