web-dev-qa-db-fra.com

Trier les déversements sur Tempdb en raison de Varchar (max)

Sur un serveur avec 32 Go, nous exécutons SQL Server 2014 SP2 avec une mémoire maximale de 25 Go, nous avons deux tables, vous trouverez ici une structure simplifiée des deux tables:

CREATE TABLE [dbo].[Settings](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceId] [int] NULL,
    [typeID] [int] NULL,
    [remark] [varchar](max) NULL,
    CONSTRAINT [PK_Settings] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Resources](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceUID] [int] NULL,
 CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

avec des index non clusters suivants:

CREATE NONCLUSTERED INDEX [IX_UID] ON [dbo].[Resources]
(
    [resourceUID] ASC
)

CREATE NONCLUSTERED INDEX [IX_Test] ON [dbo].[Settings]
(
    [resourceId] ASC,
    [typeID] ASC
)

La base de données est configurée avec compatibility level 120.

Quand j'exécute cela - Query Il y a des déversements à tempdb. Voici comment j'exécute la requête:

exec sp_executesql N'
select r.id,remark
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Si vous ne sélectionnez pas le champ [remark], aucun déversement ne se produit. Ma première réaction était que les déversements se produisaient en raison du faible nombre de lignes estimées sur l'opérateur de boucle imbriquée.

Donc, j'ajoute 5 DateTime et 5 colonnes entier sur la table des paramètres et les ajoutent à mon instruction SELECT. Quand j'exécute la requête, aucun déversement ne se produit.

Pourquoi les déversements ne se produisent que lorsque [remark] est sélectionné? Il a probablement quelque chose à voir avec le fait que ceci est une varchar(max). Que puis-je faire pour éviter de répandre à tempdb?

Ajout OPTION (RECOMPILE) à la requête ne fait aucune différence.

10

Il va y avoir plusieurs solutions de contournement possibles ici.

Vous pouvez ajuster manuellement la subvention de la mémoire, bien que je n'irais probablement pas cette voie.

Vous pouvez également utiliser un CTE et un haut pour pousser le tri inférieur, avant de saisir la colonne de longueur maximale. Cela ressemblera à quelque chose comme ci-dessous.

WITH CTE AS (
SELECT TOP 1000000000 r.ID, s.ID AS ID2, s.typeID
FROM Resources r
inner join Settings s on resourceid=r.id
where resourceUID=@UID
ORDER BY s.typeID
)
SELECT c.ID, ca.remark
FROM CTE c
CROSS APPLY (SELECT remark FROM dbo.Settings s WHERE s.id = c.ID2) ca(remark)
ORDER BY c.typeID

Preuve de concept dbfiddle ici . Les échantillons de données seraient toujours appréciés!

Si vous voulez lire une excellente analyse de Paul White, Lire ici.

10
Forrest

Pourquoi les déversements ne se produisent que lorsque [remarque] est sélectionné?

Le déversement se produit lorsque vous incluez cette colonne, car vous n'obtenez pas une subvention de mémoire assez grande pour les grandes données de chaîne triés.

Vous n'obtenez pas une subvention de mémoire assez importante car le nombre réel de lignes est 10 fois plus que le nombre estimé de lignes (1 302 vs 126 réels estimés).

Pourquoi l'estimation est-elle éteinte? Pourquoi SQL Server pense-t-il qu'il n'y a qu'une seule rangée dans Dbo.Settings avec un resourceid de 38?

Il pourrait s'agir d'une question de statistiques, que vous pouvez vérifier en exécutant DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test') et voir les comptes pour cette étape d'histogramme. Mais le plan d'exécution semble indiquer que les statistiques sont aussi complètes et à jour que possible.

Puisque les statistiques ne l'aident pas, votre meilleur pari est probablement une réécriture de requête - qui Forrest a couvert sa réponse.

7
Josh Darnell

Pour moi, il semble que la clause where dans la requête donne le problème et est la cause du budget faible, même si OPTION(RECOMPILE) est utilisé.

J'ai créé des données de test et à la fin avec deux solutions, stockant le champ ID à partir de resources dans une variable (s'il est toujours unique) ou une table Temp, si nous peut avoir plus d'un ID's.

Records de test de base

SET NOCOUNT ON
DECLARE @i int= 1;
WHILE @i <= 10000
BEGIN
INSERT INTO [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(@i,@i,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here'); -- 23254 character length on each value
INSERT INTO  [dbo].[Resources](resourceUID)
VALUES(@i);
SET @i += 1;
END

Insérez les valeurs "Recherchez", comme pour accéder aux mêmes résultatsset approximatifs que OP (1300 enregistrements)

INSERT INTO  [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(38,38,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here')
GO 1300

Modifier les statistiques de Compat & Mettre à jour en option

ALTER DATABASE StackOverflow SET COMPATIBILITY_LEVEL = 120;
UPDATE STATISTICS settings WITH FULLSCAN;
UPDATE STATISTICS resources WITH FULLSCAN;

Query original

exec sp_executesql N'
select r.id
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Mes estimations sont même pire , avec une rangée estimée, tandis que 1300 sont retournées. Et comme OP énoncé, peu importe si j'ajoute OPTION(RECOMPILE)

Une chose importante à noter, c'est que lorsque nous nous débarrassons de la clause où les estimations sont correctes à 100%, ce qui est attendu puisque nous utilisons toutes les données dans les deux tableaux.

J'ai forcé les index juste pour vous assurer que nous utilisons les mêmes que dans la requête précédente, pour prouver le point

exec sp_executesql N'
select r.id,remark
FROM Resources r with(index([IX_UID]))
inner join Settings WITH(INDEX([IX_Test])) 
on resourceid=r.id
ORDER BY typeID',
N'@UID int',
@UID=38

Comme prévu, bon estimations.

Alors, que pourrions-nous changer pour obtenir de meilleures estimations mais recherchent toujours nos valeurs?

Si @UID est unique, comme dans l'exemple OP donné, nous pourrions mettre le seul id qui a été renvoyé de resources dans une variable, puis recherchez cette variable avec une option (recompiler)

DECLARE @UID int =38 , @RID int;
SELECT @RID=r.id from 
Resources r where resourceUID = @UID;

SELECT @uid, remark 
from Settings 
where resourceId = @uid 
Order by typeID
OPTION(RECOMPILE);

Qui donne 100% précis estimations

Mais que s'il existe plusieurs ressources dans les ressources?

Ajoutez des données de test

INSERT INTO Resources(ResourceUID)
VALUES (38);
go 50

Cela pourrait être résolu avec une table Temp

CREATE TABLE #RID (id int)
DECLARE @UID int =38 
INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID

À nouveau avec précision estimations .

Cela a été fait avec mon propre jeu de données, YMMV.


écrit avec sp_executesql

avec une variable

exec sp_executesql N'
DECLARE  @RID int;
    SELECT @RID=r.id from 
    Resources r where resourceUID = @UID;

    SELECT @uid, remark 
    from Settings 
    where resourceId = @uid 
    Order by typeID
    OPTION(RECOMPILE);',
N'@UID int',
@UID=38

avec une table Temp

exec sp_executesql N'

CREATE TABLE #RID (id int)

INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID',
N'@UID int',
@UID=38

toujours des estimations correctes à 100% sur mon test

4
Randi Vertongen