web-dev-qa-db-fra.com

La boucle imbriquée a des estimations très basses en raison de données asymétriques

Sur SQL Server 2016 SP2, nous avons une Query qui a une estimation très faible sur l'opérateur de boucle imbriquée. En raison de l'estimation faible, cette requête se répand également à TEMPDB.

Si je suis correct SQL Server 2014+ Utilisations Estimation de l'histogramme grossière Pour calculer le nombre estimé de lignes sur une jointure.
[.____] Mais lorsque j'exécute la requête, SQL Server utilise le Vecteur de densité Pour calculer le nombre de lignes estimées.
[.____] Est-ce que SQL Server n'utilise que l'estimation l'estimation de l'histogramme grossière S'il n'y a pas where clause?

Normalement, j'utiliserais Statistiques filtrées Pour améliorer les estimations lorsque j'ai une table avec des données asymétriques. Mais dans ce cas qui ne semble pas fonctionner.

Existe-t-il un moyen d'améliorer les estimations sur la boucle imbriquée?

En utilisant le code suivant, vous pouvez reproduire les données:

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
5

Normalement, j'utiliserais Statistiques filtrées Pour améliorer les estimations lorsque j'ai une table avec des données asymétriques. Mais dans ce cas qui ne semble pas fonctionner.

Vous devriez trouver la statistique filtrée suivante utile:

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

Cela donne aux informations d'optimisation sur la distribution de id valeurs que correspondezfield = 'varchar', donner une estimation bien meilleure sélectivité pour la jointure:

Actual execution plan

Le Execution Plan ci-dessus montre exactement les estimations correctes avec la statistique filtrée, ce qui a conduit l'optimiseur à choisir une jointure de hachage (pour des raisons de coût).

Ceci Distribution Les informations sont beaucoup plus importantes que la méthode exacte utilisée par l'estimateur pour correspondre aux histogrammes de jointure (fine ou alignement grossier ), ou même les hypothèses générales (par exemple, une jointure simple, un confinement de base).

Si vous ne pouvez pas le faire, vos options sont largement décrites dans la réponse à votre question précédente Trier les déversements sur Tempdb en raison de Varchar (max) . Ma préférence serait probablement une table temporaire intermédiaire.

7
Paul White 9

Tout d'accord avec l'index filtré, cette réponse est ajoutée pour développer sur l'autre option que @Paulwhite mentionnée, pour utiliser une table tempétrée intermédiaire et se débarrasser de l'opérateur SORT

Vous pouvez ajouter un index ou modifier l'index existant:

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

Insérez les valeurs dans une table de température intermédiaire

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

enter image description here

Ajouter un index sur la table Temp

CREATE INDEX IX_ID on #temp2(param);

puis utilisez un CTE pour supprimer l'opérateur de tri du plan de requête

;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)

Comme mentionné par @Forrest pour pousser le tri inférieur ici

Résultat:

enter image description here

qui supprime l'opérateur de tri.

0
Randi Vertongen