Zu viel Arbeitsspeicher gewährt

45

Warum wird dieser einfachen Abfrage so viel Speicherplatz eingeräumt?

-- Demo table
CREATE TABLE dbo.Test
(
    TID integer IDENTITY NOT NULL,
    FilterMe integer NOT NULL,
    SortMe integer NOT NULL,
    Unused nvarchar(max) NULL,

    CONSTRAINT PK_dbo_Test_TID
    PRIMARY KEY CLUSTERED (TID)
);
GO
-- 100,000 example rows
INSERT dbo.Test WITH (TABLOCKX)
    (FilterMe, SortMe)
SELECT TOP (100 * 1000)
    CHECKSUM(NEWID()) % 1000,
    CHECKSUM(NEWID())
FROM sys.all_columns AS AC1
CROSS JOIN sys.all_columns AS AC2;
GO    
-- Query
SELECT
    T.TID,
    T.FilterMe,
    T.SortMe,
    T.Unused
FROM dbo.Test AS T 
WHERE 
    T.FilterMe = 567
ORDER BY 
    T.SortMe;

Für geschätzte 50 Zeilen reserviert das Optimierungsprogramm fast 500 MB für die Sortierung:

Geschätzter Plan

Paul White
quelle

Antworten:

42

Dies ist ein Fehler in SQL Server (von 2008 bis einschließlich 2014).

Mein Fehlerbericht ist hier .

Die Filterbedingung wird als verbleibendes Vergleichselement in den Scanoperator gedrückt, aber der für die Sortierung zugewiesene Speicher wird auf der Grundlage der Vorfilter-Kardinalitätsschätzung fälschlicherweise berechnet .

Zur Veranschaulichung des Problems können wir das (nicht dokumentierte und nicht unterstützte) Ablaufverfolgungsflag 9130 verwenden, um zu verhindern, dass der Filter in den Scanoperator gedrückt wird . Der der Sortierung zugewiesene Speicher basiert nun korrekt auf der geschätzten Kardinalität der Filterausgabe und nicht auf dem Scan:

SELECT
    T.TID,
    T.FilterMe,
    T.SortMe,
    T.Unused
FROM dbo.Test AS T 
WHERE 
    T.FilterMe = 567
ORDER BY 
    T.SortMe
OPTION (QUERYTRACEON 9130); -- Not for production systems!

Geschätzter Plan

Für ein Produktionssystem müssen Schritte unternommen werden, um die problematische Planform zu vermeiden (ein Filter, der in einen Scan mit einer Sortierung in einer anderen Spalte eingefügt wird). Eine Möglichkeit, dies zu tun, besteht darin, einen Index für die Filterbedingung bereitzustellen und / oder die erforderliche Sortierreihenfolge anzugeben.

-- Index on the filter condition only
CREATE NONCLUSTERED INDEX IX_dbo_Test_FilterMe
ON dbo.Test (FilterMe);

Mit diesem Index beträgt die gewünschte Speicherzuweisung für die Sortierung nur 928 KB :

Mit Filterindex

Im weiteren Verlauf kann der folgende Index die Sortierung vollständig umgehen ( Zero Memory Grant):

-- Provides filtering and sort order
-- nvarchar(max) column deliberately not INCLUDEd
CREATE NONCLUSTERED INDEX IX_dbo_Test_FilterMe_SortMe
ON dbo.Test (FilterMe, SortMe);

Mit Filter und Sortierindex

Getestet und Fehler bestätigt in den folgenden Versionen von SQL Server x64 Developer Edition:

2014   : 12.00.2430 (RTM CU4)
2012   : 11.00.5556 (SP2 CU3)
2008R2 : 10.50.6000 (SP3)
2008   : 10.00.6000 (SP4)

Dies wurde in SQL Server 2016 Service Pack 1 behoben . Die Versionshinweise enthalten Folgendes:

VSTS-Fehlernummer 8024987
Tabellenscans und Indexscans mit Pushdown-Vergleichselement überschätzen häufig die Speicherzuweisung für den übergeordneten Operator.

Getestet und bestätigt am:

  • Microsoft SQL Server 2016 (SP1) - 13.0.4001.0 (X64) Developer Edition
  • Microsoft SQL Server 2014 (SP2-CU3) 12.0.5538.0 (X64) Developer Edition

Beide CE-Modelle.

Paul White
quelle
5

Ab SQL 2012 könnte eine große Diskrepanz zwischen SerialRequiredMemoryund festgestellt werden SerialDesiredMemory, z. B .:

-- Search plan cache for Memory Grant issues
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp

-- Collect more info about the plan here if required, eg usecounts, objtype etc, 
SELECT IDENTITY( INT, 1, 1 ) rowId, query_plan
INTO #tmp
FROM sys.dm_exec_cached_plans cp WITH(NOLOCK)
    CROSS APPLY sys.dm_exec_query_plan(plan_handle)
GO


;WITH cte AS
(
SELECT
    rowId,
    query_plan,
    m.c.value ('@SerialRequiredMemory', 'INT' ) AS SerialRequiredMemory,
    m.c.value ('@SerialDesiredMemory', 'INT' ) AS SerialDesiredMemory

FROM #tmp t
    CROSS APPLY t.query_plan.nodes ( '//*:MemoryGrantInfo[@SerialDesiredMemory[. > 0]]' ) m(c)
), cte2 AS (
SELECT *,
    CAST( CAST( SerialDesiredMemory AS DECIMAL(10,2) ) / CAST( SerialRequiredMemory AS DECIMAL(10,2) ) AS DECIMAL(10,2) ) Desired_to_Required_ratio
FROM cte
)
SELECT TOP 20
    rowId,
    query_plan,
    SerialRequiredMemory SerialRequiredMemory_KB,
    SerialDesiredMemory SerialDesiredMemory_KB,
    CAST( SerialRequiredMemory / 1024. AS DECIMAL(10,2) ) SerialRequiredMemory_MB,
    CAST( SerialDesiredMemory / 1024. AS DECIMAL(10,2) ) SerialDesiredMemory_MB,
    Desired_to_Required_ratio
FROM cte2
WHERE Desired_to_Required_ratio > 100
ORDER BY Desired_to_Required_ratio DESC

Einige weitere Hinweise zu diesen neuen Attributen hier . Diese Abfrage ist etwas rau und bereit, hat aber die übermäßige Sortierabfrage aus meiner SQL Server 2014-Entwicklungsbox mit einem Verhältnis von 975,47 und ein paar anderen aufsehenerregenden Plänen übernommen. Das 'normale' Verhältnis (zumindest aus meinen begrenzten Tests) scheint bei ~ 1 zu liegen.

HTH

wBob
quelle
3

Danke für all die Hilfe. Ich dachte, ich würde eine aktualisierte Version der obigen Abfrage senden, die wir hilfreich fanden.

-- Search plan cache for Memory Grant issues
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp

-- Collect more info about the plan here if required, eg usecounts, objtype etc, 
SELECT IDENTITY( INT, 1, 1 ) rowId, query_plan, db = DB_NAME(CAST(pa.value AS int))
INTO #tmp
FROM sys.dm_exec_cached_plans cp WITH(NOLOCK)
    CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle)
    OUTER APPLY sys.dm_exec_plan_attributes(cp.plan_handle) pa 
    WHERE pa.attribute = 'dbid' 
GO

;WITH cte AS
(
SELECT
    rowId,
    query_plan,
    m.c.value ('@SerialRequiredMemory', 'INT' ) AS SerialRequiredMemory,
    m.c.value ('@SerialDesiredMemory', 'INT' ) AS SerialDesiredMemory,
    db
FROM #tmp t
    CROSS APPLY t.query_plan.nodes ( '//*:MemoryGrantInfo[@SerialDesiredMemory[. > 0]]' ) m(c)
), cte2 AS (
SELECT *,
    CAST( CAST( SerialDesiredMemory AS DECIMAL(10,2) ) / CAST( SerialRequiredMemory AS DECIMAL(10,2) ) AS DECIMAL(10,2) ) Desired_to_Required_ratio
FROM cte
)
SELECT TOP 20
    rowId,
    query_plan,
    SerialRequiredMemory SerialRequiredMemory_KB,
    SerialDesiredMemory SerialDesiredMemory_KB,
    CAST( SerialRequiredMemory / 1024. AS DECIMAL(10,2) ) SerialRequiredMemory_MB,
    CAST( SerialDesiredMemory / 1024. AS DECIMAL(10,2) ) SerialDesiredMemory_MB,
    Desired_to_Required_ratio,
    db
FROM cte2
WHERE Desired_to_Required_ratio > 100
ORDER BY Desired_to_Required_ratio DESC
Doug B
quelle