Urutkan memori yang berlebihan

45

Mengapa kueri sederhana ini diberikan begitu banyak memori?

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

Untuk sekitar 50 baris, pengoptimal menyimpan hampir 500 MB untuk pengurutan:

Perkiraan rencana

Paul White
sumber

Jawaban:

42

Ini adalah bug di SQL Server (mulai 2008 hingga 2014).

Laporan bug saya ada di sini .

Kondisi penyaringan didorong ke bawah ke operator pemindaian sebagai predikat residual, tetapi memori yang diberikan untuk jenis ini keliru dihitung berdasarkan perkiraan kardinalitas pra-filter .

Untuk mengilustrasikan masalah ini, kita dapat menggunakan jejak bendera 9130 (tidak berdokumen dan tidak didukung) untuk mencegah Filter didorong ke operator pemindaian . Memori yang diberikan untuk pengurutan sekarang dengan benar didasarkan pada perkiraan kardinalitas keluaran Filter, bukan pemindaian:

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!

Perkiraan rencana

Untuk sistem produksi , langkah-langkah perlu diambil untuk menghindari bentuk rencana yang bermasalah (filter didorong ke pemindaian dengan pengurutan pada kolom lain). Salah satu cara untuk melakukan ini adalah dengan memberikan indeks pada kondisi filter dan / atau untuk memberikan urutan pengurutan yang diperlukan.

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

Dengan indeks ini di tempat, hibah memori yang diinginkan untuk pengurutan hanya 928KB :

Dengan indeks filter

Lebih jauh, indeks berikut ini dapat menghindari penyortiran sepenuhnya ( 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);

Dengan filter dan urutkan indeks

Diuji dan bug dikonfirmasi pada build berikut dari SQL Server x64 Edisi Pengembang:

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

Ini diperbaiki di SQL Server 2016 Paket Layanan 1 . Catatan rilis meliputi yang berikut ini:

Nomor bug VSTS 8024987
Pemindaian tabel dan pemindaian indeks dengan predikat push down cenderung melebih-lebihkan pemberian memori untuk operator induk.

Diuji dan dikonfirmasi tetap pada:

  • 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

Kedua model CE.

Paul White
sumber
5

Dari SQL 2012 dan seterusnya Anda bisa mencari perbedaan besar antara SerialRequiredMemorydan SerialDesiredMemory, misalnya sesuatu seperti ini:

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

Beberapa catatan lebih lanjut tentang atribut baru ini di sini . Kueri ini agak kasar dan siap tetapi memang mengambil kueri pengurutan berlebihan dari kotak dev SQL Server 2014 saya dengan rasio 975,47 ditambah beberapa paket eye-popping lainnya. Rasio 'normal' (setidaknya dari pengujian terbatas saya) tampaknya ~ 1.

HTH

wBob
sumber
3

Terima kasih atas semua bantuannya. Saya pikir saya akan mengirim versi terbaru dari permintaan di atas yang kami temukan bermanfaat.

-- 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
sumber