Ordinamento degli sversamenti su tempdb a causa di varchar (max)


10

Su un server con 32 GB stiamo eseguendo SQL Server 2014 SP2 con una memoria massima di 25 GB abbiamo due tabelle, qui trovi una struttura semplificata di entrambe le tabelle:

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

con i seguenti indici non cluster:

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

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

Il database è configurato con compatibility level120.

Quando eseguo questa query ci sono perdite tempdb. Ecco come eseguo la query:

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

Se non si seleziona il [remark]campo non si verificano perdite. La mia prima reazione è stata che gli sversamenti si sono verificati a causa del basso numero di righe stimate sull'operatore del ciclo nidificato.

Quindi aggiungo 5 colonne datetime e 5 colonne intere alla tabella delle impostazioni e le aggiungo alla mia dichiarazione select. Quando eseguo la query non si verificano perdite.

Perché gli sversamenti si verificano solo quando [remark]è selezionato? Probabilmente ha qualcosa a che fare con il fatto che questo è un varchar(max). Cosa posso fare per evitare di versare tempdb?

L'aggiunta OPTION (RECOMPILE)alla query non fa differenza.


Può essere che tu possa provare select r.id, LEFT(remark, 512)(o qualsiasi lunghezza ragionevole della sottostringa potrebbe essere).
Mustaccio

@Forrest: sto cercando di riprodurre i dati necessari per simulare il problema. A prima vista ha a che fare con la stima bassa del ciclo nidificato. Nei miei dati fittizi il numero stimato di righe è molto più elevato e non si verificano fuoriuscite
Frederik Vanderhaegen,

Risposte:


10

Ci saranno diverse possibili soluzioni qui.

È possibile regolare manualmente la concessione di memoria, anche se probabilmente non seguirei questa strada.

Puoi anche usare un CTE e un TOP per spingere l'ordinamento verso il basso, prima di afferrare la colonna di lunghezza massima. Sembrerà qualcosa di simile al di sotto.

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

Dfiddle prova del concetto qui . I dati di esempio sarebbero ancora apprezzati!

Se vuoi leggere un'eccellente analisi di Paul White, leggi qui.


7

Perché gli sversamenti si verificano solo quando si seleziona [osservazione]?

Lo sversamento si verifica quando si include quella colonna perché non si ottiene una concessione di memoria sufficiente per i dati di stringa di grandi dimensioni ordinati.

Non si ottiene una concessione di memoria sufficiente perché il numero effettivo di righe è 10 volte superiore al numero stimato di righe (1.302 effettive contro 126 stimate).

Perché il preventivo è disattivato? Perché SQL Server pensa che ci sia solo una riga in dbo.Settings con un resourceid38?

Potrebbe essere un problema di statistiche, che puoi verificare eseguendo DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')e vedere i conteggi per quel passaggio dell'istogramma. Ma il piano di esecuzione sembra indicare che le statistiche sono complete e aggiornate come potrebbero essere.

Dato che le statistiche non aiutano, la tua scommessa migliore è probabilmente una riscrittura della query - che Forrest ha trattato nella sua risposta.


3

A me sembra che la whereclausola nella query stia dando il problema ed è la causa delle stime basse, anche se OPTION(RECOMPILE)viene utilizzata.

Ho creato alcuni dati di test e alla fine ho trovato due soluzioni, memorizzando il IDcampo resourcesin una variabile (se è sempre unica) o in una tabella temporanea, se ne possiamo avere più di una ID.

Record di test di 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

Inserire i valori "Cerca" per ottenere lo stesso gruppo di risultati approssimativo dell'OP (1300 record)

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

Modifica la compatibilità e aggiorna le statistiche in modo che corrispondano a OP

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

Query originale

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

Le mie stime sono anche peggiori , con una riga stimata, mentre vengono restituite 1300. E come affermato da OP, non importa se aggiungoOPTION(RECOMPILE)

Una cosa importante da notare è che quando ci liberiamo della clausola where le stime sono corrette al 100%, il che è previsto poiché stiamo utilizzando tutti i dati in entrambe le tabelle.

Ho costretto gli indici solo per assicurarmi di usare gli stessi della query precedente, per dimostrare il punto

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

Come previsto, buone stime.

Quindi, cosa potremmo cambiare per ottenere stime migliori ma cercare ancora i nostri valori?

Se @UID è univoco, come nell'esempio OP fornito, potremmo mettere il singolo da idcui è stato restituito resourcesin una variabile, quindi cercare quella variabile con un'OPZIONE (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);

Che fornisce stime accurate al 100%

Ma cosa succede se ci sono più risorseUID nelle risorse?

aggiungere alcuni dati di test

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

Questo potrebbe essere risolto con una tabella temporanea

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

Ancora una volta con stime accurate .

Questo è stato fatto con il mio set di dati, YMMV.


Scritto con sp_executesql

Con una variabile

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

Con una tabella temporanea

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

Stime ancora corrette al 100% sul mio test

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.