ทำไม SQL Server ใช้แผนการดำเนินการที่ดีขึ้นเมื่อฉันอินไลน์ตัวแปร


32

ฉันมีแบบสอบถาม SQL ที่ฉันพยายามปรับให้เหมาะสม:

DECLARE @Id UNIQUEIDENTIFIER = 'cec094e5-b312-4b13-997a-c91a8c662962'

SELECT 
  Id,
  MIN(SomeTimestamp),
  MAX(SomeInt)
FROM dbo.MyTable
WHERE Id = @Id
  AND SomeBit = 1
GROUP BY Id

MyTable มีสองดัชนี:

CREATE NONCLUSTERED INDEX IX_MyTable_SomeTimestamp_Includes
ON dbo.MyTable (SomeTimestamp ASC)
INCLUDE(Id, SomeInt)

CREATE NONCLUSTERED INDEX IX_MyTable_Id_SomeBit_Includes
ON dbo.MyTable (Id, SomeBit)
INCLUDE (TotallyUnrelatedTimestamp)

เมื่อฉันดำเนินการแบบสอบถามตรงตามที่เขียนไว้ข้างต้น SQL Server จะสแกนดัชนีแรกซึ่งส่งผลให้เกิดการอ่านแบบลอจิคัล 189,703 ครั้งและระยะเวลา 2-3 วินาที

เมื่อฉันอินไลน์@Idตัวแปรและดำเนินการค้นหาอีกครั้ง SQL Server จะค้นหาดัชนีที่สองส่งผลให้มีเพียงลอจิก 104 การอ่านและระยะเวลา 0.001 วินาที (ทันทีโดยทั่วไป)

ฉันต้องการตัวแปร แต่ฉันต้องการให้ SQL ใช้แผนที่ดี ในฐานะที่เป็นการแก้ปัญหาชั่วคราวฉันใส่คำใบ้ดัชนีไว้ในแบบสอบถาม อย่างไรก็ตามฉันพยายามหลีกเลี่ยงคำแนะนำดัชนีเมื่อเป็นไปได้ ฉันมักจะสมมติว่าหากตัวเพิ่มประสิทธิภาพการสืบค้นไม่สามารถทำงานได้มีบางสิ่งที่ฉันสามารถทำได้ (หรือหยุดทำ) เพื่อช่วยโดยไม่บอกอย่างชัดเจนว่าจะต้องทำอะไร

ดังนั้นทำไม SQL Server จึงเกิดแผนการที่ดีขึ้นเมื่อฉันอินไลน์ตัวแปร

คำตอบ:


44

ใน SQL Server มีรูปแบบที่พบได้ทั่วไปสามแบบของเพรดิเคตที่ไม่เข้าร่วม:

ด้วยค่าตัวอักษร :

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = 1;

ด้วยพารามิเตอร์ :

CREATE PROCEDURE dbo.SomeProc(@Reputation INT)
AS
BEGIN
    SELECT COUNT(*) AS records
    FROM   dbo.Users AS u
    WHERE  u.Reputation = @Reputation;
END;

ด้วยตัวแปรท้องถิ่น :

DECLARE @Reputation INT = 1

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = @Reputation;

ผลลัพธ์

เมื่อคุณใช้ค่าตามตัวอักษรและแผนของคุณไม่ใช่ a) สิ่งเล็กน้อยและ b) พารามิเตอร์ง่าย ๆ หรือ c) คุณไม่ได้เปิดใช้งานการกำหนดพารามิเตอร์แบบบังคับเครื่องมือเพิ่มประสิทธิภาพจะสร้างแผนพิเศษสำหรับค่านั้น

เมื่อคุณใช้พารามิเตอร์โปรแกรมเพิ่มประสิทธิภาพจะสร้างแผนสำหรับพารามิเตอร์นั้น (ซึ่งเรียกว่าการดมกลิ่นพารามิเตอร์ ) จากนั้นนำแผนนั้นมาใช้ซ้ำคำแนะนำการคอมไพล์ recompile ขาดการไล่แคชแผน ฯลฯ

เมื่อคุณใช้ตัวแปรท้องถิ่น , เพิ่มประสิทธิภาพทำให้แผนสำหรับ ... บางสิ่งบางอย่าง

หากคุณต้องเรียกใช้แบบสอบถามนี้:

DECLARE @Reputation INT = 1

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = @Reputation;

แผนจะมีลักษณะเช่นนี้:

ถั่ว

และจำนวนแถวโดยประมาณสำหรับตัวแปรท้องถิ่นนั้นจะเป็นดังนี้:

ถั่ว

แม้ว่าเคียวรีจะส่งคืนจำนวน 4,744,427

ไม่ทราบตัวแปรท้องถิ่นไม่ใช้ส่วน 'ดี' ของฮิสโตแกรมสำหรับการประมาณค่าเชิงเลข พวกเขาใช้การคาดเดาตามเวกเตอร์ความหนาแน่น

ถั่ว

SELECT 5.280389E-05 * 7250739 AS [poo]

นั่นจะทำให้คุณ382.86722457471ซึ่งเดาได้ว่าเครื่องมือเพิ่มประสิทธิภาพทำ

การเดาที่ไม่รู้จักเหล่านี้มักเป็นการคาดเดาที่ไม่ดีมากและมักจะนำไปสู่แผนการที่ไม่ดีและตัวเลือกดัชนีที่ไม่ดี

แก้ไขหรือไม่

ตัวเลือกของคุณโดยทั่วไปคือ:

  • คำแนะนำดัชนีที่เปราะบาง
  • คำแนะนำการคอมไพล์ซ้ำอาจมีราคาแพง
  • พารามิเตอร์ไดนามิก SQL
  • ขั้นตอนการจัดเก็บ
  • ปรับปรุงดัชนีปัจจุบัน

ตัวเลือกของคุณคือ:

การปรับปรุงดัชนีปัจจุบันหมายถึงการขยายดัชนีให้ครอบคลุมทุกคอลัมน์ที่ต้องการโดยการสืบค้น:

CREATE NONCLUSTERED INDEX IX_MyTable_Id_SomeBit_Includes
ON dbo.MyTable (Id, SomeBit)
INCLUDE (TotallyUnrelatedTimestamp, SomeTimestamp, SomeInt)
WITH (DROP_EXISTING = ON);

สมมติว่าIdค่าต่าง ๆ มีเหตุผลพอสมควรสิ่งนี้จะช่วยให้คุณวางแผนที่ดีและช่วยให้เครื่องมือเพิ่มประสิทธิภาพโดยให้วิธีการเข้าถึงข้อมูล 'ชัดเจน'

อ่านเพิ่มเติม

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการฝังพารามิเตอร์ได้ที่นี่:


12

@Idฉันจะคิดว่าคุณมีข้อมูลเบ้ที่คุณไม่ต้องการที่จะใช้คำแนะนำแบบสอบถามที่จะบังคับให้เพิ่มประสิทธิภาพสิ่งที่ต้องทำและที่คุณต้องการที่จะได้รับผลงานที่ดีสำหรับค่าเข้าทั้งหมดเป็นไปได้ของ คุณสามารถรับแผนแบบสอบถามเพื่อรับประกันว่าจะต้องอ่านตรรกะเพียงไม่กี่หยิบสำหรับค่าอินพุตใด ๆ ที่เป็นไปได้หากคุณต้องการสร้างดัชนีคู่ต่อไปนี้ (หรือเทียบเท่า):

CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;

ด้านล่างคือข้อมูลการทดสอบของฉัน ฉันวางแถว 13 M ลงในตารางและทำให้ครึ่งหนึ่งของพวกเขามีค่า'3A35EA17-CE7E-4637-8319-4C517B6E48CA'สำหรับIdคอลัมน์

DROP TABLE IF EXISTS dbo.MyTable;

CREATE TABLE dbo.MyTable (
    Id uniqueidentifier,
    SomeTimestamp DATETIME2,
    SomeInt INT,
    SomeBit BIT,
    FILLER VARCHAR(100)
);

INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT NEWID(), CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT '3A35EA17-CE7E-4637-8319-4C517B6E48CA', CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

คำค้นหานี้อาจดูแปลก ๆ เล็กน้อยในตอนแรก:

DECLARE @Id UNIQUEIDENTIFIER = '3A35EA17-CE7E-4637-8319-4C517B6E48CA'

SELECT
  @Id,
  st.SomeTimestamp,
  si.SomeInt
FROM (
    SELECT TOP (1) SomeInt, Id
    FROM dbo.MyTable
    WHERE Id = @Id
    AND SomeBit = 1
    ORDER BY SomeInt DESC
) si
CROSS JOIN (
    SELECT TOP (1) SomeTimestamp, Id
    FROM dbo.MyTable
    WHERE Id = @Id
    AND SomeBit = 1
    ORDER BY SomeTimestamp ASC
) st;

มันถูกออกแบบมาเพื่อใช้ประโยชน์จากการจัดลำดับของดัชนีเพื่อค้นหาค่า min หรือ max ด้วยการอ่านเชิงตรรกะ CROSS JOINมีเพื่อให้ได้ผลลัพธ์ที่ถูกต้องเมื่อมีไม่ตรงกับแถวใด ๆ สำหรับ@Idค่า แม้ว่าฉันจะกรองค่าที่ได้รับความนิยมมากที่สุดในตาราง (การจับคู่ 6.5 ล้านแถว) ฉันได้รับการอ่านเชิงตรรกะ 8 ครั้งเท่านั้น:

ตาราง 'MyTable' สแกนจำนวน 2, ตรรกะอ่าน 8

นี่คือแผนแบบสอบถาม:

ป้อนคำอธิบายรูปภาพที่นี่

ดัชนีทั้งสองหา 0 หรือ 1 แถว มันมีประสิทธิภาพมาก แต่การสร้างดัชนีสองรายการอาจเกินความจำเป็นสำหรับสถานการณ์ของคุณ คุณสามารถพิจารณาดัชนีต่อไปนี้แทน:

CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;

ตอนนี้แผนคิวรีสำหรับเคียวรีดั้งเดิม (พร้อมด้วยMAXDOP 1คำใบ้เพิ่มเติม) จะดูแตกต่างออกไปเล็กน้อย:

ป้อนคำอธิบายรูปภาพที่นี่

การค้นหาคีย์ไม่จำเป็นอีกต่อไป ด้วยเส้นทางการเข้าถึงที่ดีกว่าที่ควรใช้งานได้ดีกับอินพุตทั้งหมดคุณไม่ควรกังวลเกี่ยวกับเครื่องมือเพิ่มประสิทธิภาพในการเลือกแผนแบบสอบถามที่ไม่ถูกต้องเนื่องจากเวกเตอร์ความหนาแน่น อย่างไรก็ตามการค้นหาและดัชนีนี้จะไม่ได้มีประสิทธิภาพเท่ากับอีกแบบสอบถามหากคุณค้นหา@Idมูลค่าที่เป็นที่นิยม

ตาราง 'MyTable' จำนวนการสแกน 1, ตรรกะอ่าน 33757


2

ฉันไม่สามารถตอบได้ว่าทำไมที่นี่ แต่เป็นวิธีที่รวดเร็วและสกปรกเพื่อให้แน่ใจว่าแบบสอบถามทำงานในแบบที่คุณต้องการ:

DECLARE @Id UNIQUEIDENTIFIER = 'cec094e5-b312-4b13-997a-c91a8c662962'
SELECT 
  Id,
  MIN(SomeTimestamp),
  MAX(SomeInt)
FROM dbo.MyTable WITH (INDEX(IX_MyTable_Id_SomeBit_Includes))
WHERE Id = @Id
  AND SomeBit = 1
GROUP BY Id

นี่อาจก่อให้เกิดความเสี่ยงที่ตารางหรือดัชนีอาจมีการเปลี่ยนแปลงในอนาคตเช่นการเพิ่มประสิทธิภาพนี้จะผิดปกติ แต่ก็สามารถใช้งานได้หากคุณต้องการ หวังว่าใครบางคนสามารถให้คำตอบที่เป็นต้นเหตุให้กับคุณได้ตามที่คุณร้องขอแทนที่จะเป็นวิธีแก้ปัญหานี้

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