Trier les déversements à tempdb en raison de varchar (max)

10

Sur un serveur de 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 les index non cluster 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 level120.

Lorsque j'exécute cette requête, 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 ne sélectionnez pas le [remark]champ, aucun déversement ne se produit. Ma première réaction a été que les déversements se sont produits en raison du faible nombre de lignes estimées sur l'opérateur de boucle imbriquée.

J'ajoute donc 5 colonnes datetime et 5 entiers à la table des paramètres et les ajoute à mon instruction select. Lorsque j'exécute la requête, aucun déversement ne se produit.

Pourquoi les déversements se produisent-ils uniquement lorsque [remark]est sélectionné? Cela a probablement quelque chose à voir avec le fait qu'il s'agit d'un varchar(max). Que puis-je faire pour éviter de renverser tempdb?

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

Frederik Vanderhaegen
la source
Vous pouvez peut-être essayer select r.id, LEFT(remark, 512)(ou quelle que soit la longueur de la sous-chaîne sensible).
mustaccio
@Forrest: J'essaie de reproduire les données nécessaires pour simuler le problème. À première vue, il s'agit de l'estimation basse de la boucle imbriquée. Dans mes données fictives, le nombre estimé de rangées est beaucoup plus élevé et aucun déversement ne se produit
Frederik Vanderhaegen

Réponses:

10

Il y aura plusieurs solutions de contournement possibles ici.

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

Vous pouvez également utiliser un CTE et TOP pour pousser le tri plus bas, avant de saisir la colonne de longueur maximale. Il 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 . Des exemples de données seraient toujours appréciés!

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

Forrest
la source
7

Pourquoi les déversements se produisent-ils uniquement lorsque [remarque] est sélectionné?

Le déversement se produit lorsque vous incluez cette colonne, car vous n'obtenez pas suffisamment de mémoire pour les grandes données de chaîne en cours de tri.

Vous n'obtenez pas une allocation de mémoire suffisamment importante car le nombre réel de lignes est 10 fois supérieur au nombre estimé de lignes (1 302 réel contre 126 estimé).

Pourquoi le devis est-il désactivé? Pourquoi SQL Server pense-t-il qu'il n'y a qu'une seule ligne dans dbo.Settings avec un resourceidde 38?

Il peut s'agir d'un problème de statistiques, que vous pouvez vérifier en exécutant DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')et voir les nombres pour cette étape d'histogramme. Mais le plan d'exécution semble indiquer que les statistiques sont aussi complètes et à jour qu'elles pourraient l'être.

Étant donné que les statistiques ne sont pas utiles, votre meilleur pari est probablement une réécriture de la requête - que Forrest a couverte dans sa réponse.

Josh Darnell
la source
3

Pour moi, il semble que la whereclause de la requête pose problème et soit la cause des faibles estimations, même si elle OPTION(RECOMPILE)est utilisée.

J'ai créé quelques données de test, et finalement j'ai trouvé deux solutions, en stockant le IDchamp à partir resourcesd'une variable (si elle est toujours unique) ou d'une table temporaire, si nous pouvons en avoir plus d'une ID.

Enregistrements 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 «Seek» pour obtenir le même jeu de résultats approximatif 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 compatibilité et de mise à jour pour correspondre à OP

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

Requête d'origine

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 encore pires , avec une ligne estimée, tandis que 1300 sont retournées. Et comme OP l'a dit, peu importe si j'ajouteOPTION(RECOMPILE)

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

J'ai forcé les index juste pour m'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, de bonnes estimations.

Alors, que pourrions-nous changer pour obtenir de meilleures estimations tout en recherchant nos valeurs?

SI @UID est unique, comme dans l'exemple donné par OP, nous pourrions mettre le single idqui a été renvoyé resourcesdans une variable, puis rechercher cette variable avec une OPTION (RECOMPILE)

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

Ce qui donne des estimations précises à 100%

Mais que se passe-t-il s'il y a plusieurs resourceUID dans les ressources?

ajouter des données de test

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

Cela pourrait être résolu avec une table temporaire

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

Encore une fois avec des estimations précises .

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 temporaire

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

100% toujours des estimations correctes sur mon test

Randi Vertongen
la source