Sortir tumpahan ke tempdb karena varchar (maks)

10

Pada server dengan 32GB kami menjalankan SQL Server 2014 SP2 dengan memori maksimal 25GB kami memiliki dua tabel, di sini Anda menemukan struktur yang disederhanakan dari kedua tabel:

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

dengan indeks non-cluster berikut:

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

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

Basis data dikonfigurasikan dengan compatibility level120.

Ketika saya menjalankan kueri ini ada tumpahan ke tempdb. Inilah cara saya menjalankan kueri:

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

Jika tidak memilih [remark]bidang, tidak akan terjadi tumpahan. Reaksi pertama saya adalah bahwa tumpahan terjadi karena jumlah baris perkiraan yang rendah pada operator loop bersarang.

Jadi saya menambahkan 5 datetime dan 5 kolom integer ke tabel pengaturan dan menambahkannya ke pernyataan pilih saya. Saat saya mengeksekusi kueri, tidak ada tumpahan yang terjadi.

Mengapa tumpahan hanya terjadi saat [remark]dipilih? Mungkin ada hubungannya dengan fakta bahwa ini adalah varchar(max). Apa yang bisa saya lakukan untuk menghindari tumpah tempdb?

Menambahkan OPTION (RECOMPILE)ke kueri tidak membuat perbedaan.

Frederik Vanderhaegen
sumber
Mungkin Anda dapat mencoba select r.id, LEFT(remark, 512)(atau apa pun panjang substring yang masuk akal).
mustaccio
@Forrest: Saya mencoba mereproduksi data yang diperlukan untuk mensimulasikan masalah. Pada pandangan pertama itu ada hubungannya dengan estimasi rendah dari nested loop. Dalam data boneka saya, perkiraan jumlah baris jauh lebih tinggi dan tidak ada tumpahan
Frederik Vanderhaegen

Jawaban:

10

Akan ada beberapa kemungkinan penyelesaian di sini.

Anda dapat secara manual menyesuaikan hibah memori, meskipun saya mungkin tidak akan memilih rute itu.

Anda juga dapat menggunakan CTE dan TOP untuk mendorong sortir lebih rendah, sebelum meraih kolom panjang maks. Akan terlihat seperti di bawah ini.

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

Bukti konsep dbfiddle di sini . Data sampel masih akan dihargai!

Jika Anda ingin membaca analisis yang sangat baik oleh Paul White, baca di sini.

Forrest
sumber
7

Mengapa tumpahan hanya terjadi ketika [komentar] dipilih?

Tumpahan terjadi ketika Anda memasukkan kolom itu karena Anda tidak mendapatkan hibah memori yang cukup besar untuk data string besar yang diurutkan.

Anda tidak mendapatkan hibah memori yang cukup besar karena jumlah baris aktual adalah 10x lebih banyak dari perkiraan jumlah baris (1.302 aktual vs 126 diperkirakan).

Mengapa taksirannya mati? Mengapa SQL Server berpikir hanya ada satu baris dalam dbo.Settings dengan resourceid38?

Ini bisa menjadi masalah statistik, yang dapat Anda periksa dengan menjalankan DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')dan melihat jumlah untuk langkah histogram itu. Tetapi rencana eksekusi tampaknya menunjukkan bahwa statistiknya selengkap dan setanggal mungkin.

Karena statistik tidak membantu, taruhan terbaik Anda mungkin adalah penulisan ulang kueri - yang Forrest telah sampaikan dalam jawabannya.

Josh Darnell
sumber
3

Bagi saya tampaknya whereklausa dalam kueri memberikan masalah, dan merupakan penyebab dari estimasi rendah, bahkan jika OPTION(RECOMPILE)digunakan.

Saya membuat beberapa data uji, dan pada akhirnya muncul dengan dua solusi, menyimpan IDbidang dari resourcessalah satu variabel (jika selalu unik) atau tabel temp, jika kita dapat memiliki lebih dari satu ID.

Catatan uji dasar

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

Masukkan nilai 'Seek', untuk mendapatkan perkiraan hasil yang sama seperti OP (1300 catatan)

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

Ubah statistik & Perbarui statistik untuk mencocokkan OP

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

Permintaan asli

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

Perkiraan saya bahkan lebih buruk , dengan satu baris perkiraan, sedangkan 1300 dikembalikan. Dan seperti kata OP, tidak masalah jika saya tambahkanOPTION(RECOMPILE)

Satu hal penting yang perlu diperhatikan, adalah bahwa ketika kita menyingkirkan klausa di mana estimasi adalah 100% benar, yang diharapkan karena kita menggunakan semua data di kedua tabel.

Saya memaksa indeks hanya untuk memastikan kami menggunakan yang sama seperti pada permintaan sebelumnya, untuk membuktikan maksudnya

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

Seperti yang diharapkan, perkiraan bagus .

Jadi, apa yang bisa kita ubah untuk mendapatkan estimasi yang lebih baik tetapi masih mencari nilai-nilai kita?

JIKA @UID unik, seperti dalam contoh OP memberi, kita bisa menempatkan tunggal idyang dikembalikan dari resourcesdalam variabel, kemudian mencari variabel itu dengan 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);

Yang memberikan perkiraan akurat 100%

Tetapi bagaimana jika ada banyak sumber daya di dalam sumber daya?

tambahkan beberapa data uji

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

Ini bisa diselesaikan dengan tabel temp

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

Lagi dengan perkiraan yang akurat .

Ini dilakukan dengan dataset saya sendiri, YMMV.


Ditulis dengan sp_executesql

Dengan variabel

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

Dengan meja temp

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

Masih 100% perkiraan yang benar pada pengujian saya

Randi Vertongen
sumber