web-dev-qa-db-ja.com

データが歪んでいるため、ネストされたループの推定値が非常に低い

SQL Server 2016 SP2では、ネストされたループ演算子の推定値が非常に低い query があります。見積もりが低いため、このクエリはtempdbにも影響します。

正しい場合、SQL Server 2014+はCoarse Histogram Estimationを使用して結合の推定行数を計算します。
しかし、クエリを実行すると、SQL Serverは密度ベクトルを使用して推定行数を計算します。
where句がない場合、SQL ServerはCoarse Histogram Estimationのみを使用していますか?

通常、フィルター統計を使用して、歪んだデータを含むテーブルがある場合の推定を改善します。しかし、この場合はうまくいかないようです。

ネストされたループの推定を改善する方法はありますか?

次のコードを使用すると、データを再現できます。

create table MyTable
(
    id int identity,
    field varchar(50),
    constraint  pk_id primary  key clustered (id)
)
go

create table SkewedTable
(
    id int identity,
    startdate datetime,
    myTableId int,
    remark varchar(50),
    constraint  pk_id primary  key clustered (id)
)

set nocount on

insert into MyTable select top 1000 [name] from master..spt_values
go

insert into SkewedTable select GETDATE(),FLOOR(Rand()*(1000))+1,REPLICATE(N'A',FLOOR(Rand()*(40))+1)
go 1000

insert into SkewedTable select GETDATE(),FLOOR(Rand()*(1000))+1,REPLICATE(N'A',FLOOR(Rand()*(40))+1)
go 

CREATE NONCLUSTERED INDEX [ix_field] ON [dbo].[MyTable]([field] ASC)
go

CREATE NONCLUSTERED INDEX [ix_mytableid] ON [dbo].[SkewedTable]([myTableId] ASC)
go

--95=varchar in sys.messages
set nocount off

;with cte as
( 
    select GETDATE() as startdate ,95 as myTableId, REPLICATE(N'B',FLOOR(Rand()*(40))+1) as remark
    union all
    select * from cte
)
insert into skewedtable select top 40000 * from cte
option(maxrecursion 0)
go

update statistics mytable with fullscan
go

update statistics skewedtable with fullscan
go

通常、フィルター統計を使用して、歪んだデータを含むテーブルがある場合の推定を改善します。しかし、この場合はうまくいかないようです。

次のフィルタリングされた統計が役立つはずです。

CREATE STATISTICS [stats id (field=varchar)]
ON dbo.MyTable (id)
WHERE field = 'varchar'
WITH FULLSCAN;

これは、matchfield = 'varchar'というid値のdistributionに関するオプティマイザ情報を提供し、結合の選択性の見積もりが大幅に改善されました。

Actual execution plan

上記の 実行計画 は、フィルターされた統計を使用して正確に正しい推定を示し、オプティマイザがハッシュ結合を選択するようにしています(コスト上の理由から)。

このdistribution情報は、結合ヒストグラムを一致させるために推定器が使用する正確な方法(細かい、または coarse alignment )、または一般的なものよりもはるかに重要です仮定(例:単純な結合、ベースコンテインメント)。

それができない場合の選択肢は、前の質問への回答で概説されているように広くなります varchar(max)によりtempdbに流出をソート 。私の好みはおそらく中間の一時テーブルでしょう。

7
Paul White 9

フィルターされたインデックスに完全に同意します。この回答は、@ PaulWhiteが言及した他のオプションを拡張するために追加され、中間の一時テーブルを使用して、結果的にSORT演算子を削除します

インデックスを追加するか、既存のインデックスを変更できます:

CREATE INDEX IX_SkewedTable_MytableId_startdate
ON SkewedTable(myTableId,startdate)
INCLUDE(remark);

中間の一時テーブルに値を挿入します

CREATE TABLE  #temp2(param int);
INSERT INTO  #temp2(param)
SELECT t.id 
FROM mytable t 
WHERE t.field = 'varchar';

enter image description here

一時テーブルにインデックスを追加します

CREATE INDEX IX_ID on #temp2(param);

次に、CTEを使用して、クエリプランから並べ替え演算子を削除します

;WITH CTE AS
(
select TOP(999999999999)
s.myTableId,s.id,s.remark from 
SkewedTable s
order by startdate
)
SELECT s.id , s.remark
from CTE  s
INNER JOIN #temp2 
on s.myTableId = #temp2.param
OPTION(RECOMPILE)

@Forrestで述べたように、ソートを低くする ここ

結果:

enter image description here

これはソート演算子を削除します。

0
Randi Vertongen