Sortieren Sie Verschüttungen nach Tempdb aufgrund von Varchar (max)


10

Auf einem Server mit 32 GB wird SQL Server 2014 SP2 mit einem maximalen Speicher von 25 GB ausgeführt. Wir haben zwei Tabellen. Hier finden Sie eine vereinfachte Struktur beider Tabellen:

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

mit folgenden nicht gruppierten Indizes:

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

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

Die Datenbank ist mit compatibility level120 konfiguriert .

Wenn ich diese Abfrage ausführe, kommt es zu Verschüttungen tempdb. So führe ich die Abfrage aus:

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

Wenn Sie das [remark]Feld nicht auswählen, treten keine Verschüttungen auf. Meine erste Reaktion war, dass die Verschüttungen aufgrund der geringen Anzahl geschätzter Zeilen auf dem Nested-Loop-Operator auftraten.

Also füge ich der Einstellungstabelle 5 datetime- und 5 integer-Spalten hinzu und füge sie meiner select-Anweisung hinzu. Wenn ich die Abfrage ausführe, treten keine Verschüttungen auf.

Warum treten die Verschüttungen nur auf, wenn sie [remark]ausgewählt sind? Es hat wahrscheinlich etwas damit zu tun, dass dies ein ist varchar(max). Was kann ich tun, um ein Verschütten zu vermeiden tempdb?

Das Hinzufügen OPTION (RECOMPILE)zur Abfrage macht keinen Unterschied.


select r.id, LEFT(remark, 512)Vielleicht können Sie es versuchen (oder welche vernünftige Teilstringlänge auch immer sein mag).
Mustaccio

@Forrest: Ich versuche, die Daten zu reproduzieren, die zur Simulation des Problems erforderlich sind. Auf den ersten Blick hat es mit der geringen Schätzung der verschachtelten Schleife zu tun. In meinen Dummy-Daten ist die geschätzte Anzahl der Zeilen viel höher und es wird nichts verschüttet
Frederik Vanderhaegen

Antworten:


10

Hier wird es mehrere mögliche Problemumgehungen geben.

Sie können die Speicherzuweisung manuell anpassen , obwohl ich diesen Weg wahrscheinlich nicht gehen würde.

Sie können auch einen CTE und TOP verwenden, um die Sortierung nach unten zu verschieben, bevor Sie die Spalte mit der maximalen Länge greifen. Es wird ungefähr so ​​aussehen wie unten.

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

Proof-of-Concept-Dbfiddle hier . Beispieldaten wären weiterhin willkommen!

Wenn Sie eine hervorragende Analyse von Paul White lesen möchten, lesen Sie hier.


7

Warum treten die Verschüttungen nur auf, wenn [Bemerkung] ausgewählt ist?

Der Überlauf tritt auf, wenn Sie diese Spalte einschließen, weil Sie nicht genügend Speicherplatz für die zu sortierenden großen Zeichenfolgendaten erhalten.

Sie erhalten nicht genügend Speicherplatz, da die tatsächliche Anzahl der Zeilen 10x höher ist als die geschätzte Anzahl der Zeilen (1.302 tatsächliche gegenüber 126 geschätzten).

Warum ist die Schätzung falsch? Warum glaubt SQL Server, dass es in dbo.Settings nur eine Zeile mit einer resourceidvon 38 gibt?

Es könnte sich um ein Statistikproblem handeln, das Sie überprüfen können, indem DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')Sie die Anzahl für diesen Histogrammschritt ausführen. Der Ausführungsplan scheint jedoch darauf hinzudeuten, dass die Statistiken so vollständig und aktuell sind, wie sie sein könnten.

Da Statistiken nicht helfen, ist Ihre beste Wahl wahrscheinlich ein Umschreiben der Abfrage - das Forrest in seiner Antwort behandelt hat.


3

Mir scheint, dass die whereKlausel in der Abfrage das Problem darstellt und die Ursache für die niedrigen Schätzungen ist, selbst wenn sie OPTION(RECOMPILE)verwendet wird.

Ich habe einige Testdaten erstellt und am Ende zwei Lösungen gefunden, bei denen das IDFeld resourcesentweder in einer Variablen (wenn es immer eindeutig ist) oder in einer temporären Tabelle gespeichert wird, wenn wir mehr als eine haben können ID.

Basistestaufzeichnungen

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

Fügen Sie die 'Seek'-Werte ein, um dieselbe ungefähre Ergebnismenge wie OP zu erhalten (1300 Datensätze).

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

Ändern Sie die Kompatibilitäts- und Aktualisierungsstatistik entsprechend dem OP

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

Ursprüngliche Abfrage

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

Meine Schätzungen sind mit einer geschätzten Zeile noch schlechter , während 1300 zurückgegeben werden. Und wie OP sagte, spielt es keine Rolle, ob ich hinzufügeOPTION(RECOMPILE)

Es ist wichtig zu beachten, dass die Schätzungen zu 100% korrekt sind, wenn wir die where-Klausel entfernen, was erwartet wird, da wir alle Daten in beiden Tabellen verwenden.

Ich habe die Indizes gezwungen, nur um sicherzustellen, dass wir dieselben wie in der vorherigen Abfrage verwenden, um den Punkt zu beweisen

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

Wie erwartet gute Schätzungen.

Was könnten wir also ändern, um bessere Schätzungen zu erhalten und dennoch nach unseren Werten zu suchen?

WENN @UID eindeutig ist, wie im Beispiel, das OP gegeben hat, könnten wir die Single id, von der zurückgegeben wurde, resourcesin eine Variable einfügen und diese Variable dann mit einer OPTION (RECOMPILE) suchen.

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

Das gibt 100% genaue Schätzungen

Was aber, wenn mehrere Ressourcen-UIDs in Ressourcen enthalten sind?

Fügen Sie einige Testdaten hinzu

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

Dies könnte mit einer temporären Tabelle behoben werden

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

Wieder mit genauen Schätzungen .

Dies wurde mit meinem eigenen Datensatz YMMV durchgeführt.


Geschrieben mit sp_executesql

Mit einer Variablen

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

Mit einem temporären Tisch

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

Immer noch 100% korrekte Schätzungen zu meinem Test

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.