เรียงลำดับการรั่วไหลไปยัง tempdb เนื่องจาก varchar (สูงสุด)


10

บนเซิร์ฟเวอร์ที่มี 32GB เราใช้ SQL Server 2014 SP2 ที่มีหน่วยความจำสูงสุด 25GB เรามีสองตารางที่นี่คุณจะพบโครงสร้างที่เรียบง่ายของทั้งสองตาราง:

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

ด้วยดัชนีที่ไม่ใช่คลัสเตอร์ต่อไปนี้:

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

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

ฐานข้อมูลถูกกำหนดค่าด้วยcompatibility level120

เมื่อฉันทำงานนี้แบบสอบถามtempdbมีการรั่วไหลไป นี่คือวิธีที่ฉันเรียกใช้คิวรี:

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

หากไม่ได้เลือก[remark]ฟิลด์จะไม่มีการหกเกิดขึ้น ปฏิกิริยาแรกของฉันคือการเกิดการรั่วไหลเนื่องจากจำนวนแถวที่ประเมินในโอเปอเรเตอร์วนซ้ำในระดับต่ำ

ดังนั้นฉันจึงเพิ่ม 5 datetime และคอลัมน์จำนวนเต็ม 5 คอลัมน์ในตารางการตั้งค่าและเพิ่มลงในคำสั่งเลือกของฉัน เมื่อฉันดำเนินการแบบสอบถามไม่มีการรั่วไหลเกิดขึ้น

เหตุใดการรั่วไหลจึงเกิดขึ้นเฉพาะเมื่อ[remark]ถูกเลือก? varchar(max)แต่ก็มีบางสิ่งบางอย่างอาจจะทำอย่างไรกับความจริงที่ว่านี้คือ สิ่งที่ฉันสามารถทำได้เพื่อหลีกเลี่ยง spilling เพื่อtempdb?

การเพิ่มOPTION (RECOMPILE)ลงในคิวรีทำให้ไม่มีความแตกต่าง


คุณอาจลองselect r.id, LEFT(remark, 512)(หรือความยาวซับสตริงที่สมเหตุสมผลก็ได้)
mustaccio

@ ฟอเรสต์: ฉันพยายามที่จะทำซ้ำข้อมูลที่จำเป็นในการจำลองปัญหา ตั้งแต่แรกเห็นมันเกี่ยวข้องกับการประมาณค่าต่ำของลูปซ้อนกัน ในข้อมูลจำลองของฉันจำนวนแถวโดยประมาณจะสูงกว่ามากและไม่มีการรั่วไหลเกิดขึ้น
Frederik Vanderhaegen

คำตอบ:


10

จะมีวิธีแก้ไขปัญหาที่เป็นไปได้หลายอย่างที่นี่

คุณสามารถปรับการอนุญาตหน่วยความจำด้วยตนเองแม้ว่าฉันอาจจะไม่ไปที่เส้นทางนั้น

คุณยังสามารถใช้ CTE และ TOP เพื่อดันการเรียงลำดับที่ต่ำกว่าก่อนที่จะคว้าคอลัมน์ความยาวสูงสุด มันจะมีลักษณะดังนี้

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

หลักฐานของแนวคิด dbfiddle ที่นี่ ข้อมูลตัวอย่างจะยังคงได้รับการชื่นชม!

หากคุณต้องการอ่านบทวิเคราะห์ที่ยอดเยี่ยมโดย Paul White อ่านที่นี่


7

เหตุใดการรั่วไหลจึงเกิดขึ้นเฉพาะเมื่อเลือก [หมายเหตุ]

การรั่วไหลเกิดขึ้นเมื่อคุณรวมคอลัมน์นั้นเนื่องจากคุณไม่ได้รับหน่วยความจำขนาดใหญ่พอสำหรับข้อมูลสตริงขนาดใหญ่ที่เรียงลำดับ

คุณไม่ได้รับการจัดสรรหน่วยความจำที่ใหญ่พอเนื่องจากจำนวนแถวจริงคือ 10 เท่ามากกว่าจำนวนแถวโดยประมาณ (ประมาณ 1,302 จริงเทียบกับ 126 โดยประมาณ)

ทำไมการประมาณการจึงปิด เหตุใด SQL Server จึงคิดว่ามีเพียงหนึ่งแถวใน dbo การตั้งค่าที่มีresourceid38 แถว?

อาจเป็นปัญหาสถิติซึ่งคุณสามารถตรวจสอบได้ด้วยการเรียกใช้DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')และดูจำนวนขั้นตอนฮิสโตแกรมนั้น แต่แผนการดำเนินการดูเหมือนว่าจะระบุว่าสถิตินั้นสมบูรณ์และทันสมัยตามที่ควรจะเป็น

เนื่องจากสถิติไม่ได้รับการช่วยเหลือทางออกที่ดีที่สุดของคุณน่าจะเป็นข้อความค้นหาใหม่ - ฟอเรสต์ซึ่งครอบคลุมคำตอบของเขา


3

สำหรับฉันดูเหมือนว่าwhereประโยคในแบบสอบถามกำลังให้ปัญหาและเป็นสาเหตุของการประมาณการที่ต่ำแม้ว่าOPTION(RECOMPILE)จะถูกใช้

ฉันสร้างข้อมูลทดสอบและในที่สุดก็มาพร้อมกับโซลูชันสองวิธีจัดเก็บIDเขตข้อมูลจากresourcesตัวแปร (ถ้าเป็นค่าที่ไม่ซ้ำกัน) หรือตารางชั่วคราวหากเราสามารถมีมากกว่าหนึ่งIDได้

บันทึกการทดสอบพื้นฐาน

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

แทรกค่า 'ค้นหา' เพื่อไปยังชุดผลลัพธ์โดยประมาณเช่นเดียวกับ OP (1300 บันทึก)

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

เปลี่ยนสถิติที่เข้ากันได้และอัปเดตเพื่อให้ตรงกับ OP

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

ข้อความค้นหาต้นฉบับ

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

ประมาณการของฉันยิ่งแย่ลงไปอีกโดยมีแถวที่ประมาณการไว้หนึ่งแถวขณะที่ส่งคืน 1300 และเช่นเดียวกับ OP ที่ระบุไว้มันไม่สำคัญว่าถ้าฉันเพิ่มOPTION(RECOMPILE)

สิ่งสำคัญที่ควรทราบคือเมื่อเรากำจัดส่วนที่ประมาณการประมาณถูกต้อง 100% ซึ่งคาดว่าเนื่องจากเราใช้ข้อมูลทั้งหมดในตารางทั้งสอง

ฉันบังคับดัชนีเพียงเพื่อให้แน่ใจว่าเราใช้ดัชนีเดียวกับในแบบสอบถามก่อนหน้าเพื่อพิสูจน์จุด

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

เป็นที่คาดหวังที่ดีประมาณการ

ดังนั้นสิ่งที่เราสามารถเปลี่ยนเพื่อรับประมาณการที่ดีขึ้น แต่ยังคงหาค่าของเรา?

IF @UID นั้นไม่เหมือนกันดังตัวอย่างใน OP ที่ให้มาเราสามารถใส่ single idที่ส่งคืนมาจากresourcesตัวแปรแล้วค้นหาตัวแปรนั้นด้วย 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);

ซึ่งให้การประมาณการที่แม่นยำ 100%

แต่จะเกิดอะไรขึ้นถ้ามีหลาย resourceUID ในทรัพยากร

เพิ่มข้อมูลการทดสอบ

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

อาจแก้ไขได้ด้วยตารางชั่วคราว

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

อีกครั้งกับความถูกต้องประมาณการ

สิ่งนี้ทำกับชุดข้อมูลของฉันเอง YMMV


เขียนด้วย sp_executesql

ด้วยตัวแปร

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

ด้วยตารางชั่วคราว

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

ยังคงประมาณการที่ถูกต้อง 100% ในการทดสอบของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.