ดัชนีบนคอลัมน์ที่คำนวณแล้วต้องการค้นหาคีย์เพื่อรับคอลัมน์ในนิพจน์ที่คำนวณ


24

ฉันมีคอลัมน์ที่คำนวณแล้วยังคงอยู่บนโต๊ะซึ่งเป็นเพียงคอลัมน์ที่ต่อกันเช่น

CREATE TABLE dbo.T 
(   
    ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
    A VARCHAR(20) NOT NULL,
    B VARCHAR(20) NOT NULL,
    C VARCHAR(20) NOT NULL,
    D DATE NULL,
    E VARCHAR(20) NULL,
    Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL 
);

ในสิ่งนี้Compไม่ซ้ำกันและ D เป็นค่าที่ถูกต้องตั้งแต่วันที่ของการรวมกันของแต่ละครั้งA, B, Cดังนั้นฉันใช้แบบสอบถามต่อไปนี้เพื่อรับวันที่สิ้นสุดสำหรับแต่ละวันA, B, C(โดยพื้นฐานแล้วคือวันที่เริ่มต้นถัดไปสำหรับค่า Comp เดียวกัน):

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1
WHERE   t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;

ฉันเพิ่มดัชนีไปยังคอลัมน์ที่คำนวณแล้วเพื่อช่วยในแบบสอบถามนี้ (และอื่น ๆ ):

CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;

แผนแบบสอบถามทำให้ฉันประหลาดใจ ฉันคิดว่าเนื่องจากฉันมีส่วนคำสั่งระบุD IS NOT NULLและฉันกำลังเรียงลำดับCompและไม่อ้างอิงคอลัมน์ใด ๆ นอกดัชนีที่ดัชนีในคอลัมน์คำนวณสามารถใช้สแกน t1 และ t2 แต่ฉันเห็นดัชนีคลัสเตอร์ การสแกน

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

ดังนั้นฉันจึงบังคับให้ใช้ดัชนีนี้เพื่อดูว่ามันให้ผลที่ดีกว่า:

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;

ซึ่งให้แผนนี้

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

สิ่งนี้แสดงว่ามีการใช้การค้นหาคีย์โดยมีรายละเอียดดังนี้:

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

ตอนนี้ตามเอกสารของ SQL-Server:

คุณสามารถสร้างดัชนีในคอลัมน์จากการคำนวณที่กำหนดด้วยค่าที่กำหนดได้ แต่ไม่ชัดเจนนิพจน์หากคอลัมน์ถูกทำเครื่องหมาย PERSISTED ในคำสั่ง CREATE TABLE หรือ ALTER TABLE ซึ่งหมายความว่า Database Engine เก็บค่าที่คำนวณในตารางและอัพเดตเมื่อคอลัมน์อื่น ๆ ที่ขึ้นกับคอลัมน์ที่คำนวณนั้นถูกอัพเดต โปรแกรมฐานข้อมูลใช้ค่าที่เก็บไว้เหล่านี้เมื่อสร้างดัชนีในคอลัมน์และเมื่อมีการอ้างอิงดัชนีในแบบสอบถาม ตัวเลือกนี้ช่วยให้คุณสามารถสร้างดัชนีในคอลัมน์ที่คำนวณได้เมื่อโปรแกรมฐานข้อมูลไม่สามารถพิสูจน์ได้อย่างแม่นยำว่าฟังก์ชันที่ส่งคืนนิพจน์คอลัมน์ที่คำนวณแล้วโดยเฉพาะอย่างยิ่งฟังก์ชัน CLR ที่สร้างขึ้นใน. NET Framework นั้นเป็นทั้งกำหนดและแม่นยำ

ดังนั้นถ้าอย่างที่เอกสารบอกว่า"โปรแกรมฐานข้อมูลจัดเก็บค่าที่คำนวณในตาราง"และค่ายังถูกเก็บไว้ในดัชนีของฉันทำไมการค้นหาคีย์จึงต้องได้รับ A, B และ C เมื่อไม่ได้อ้างอิง แบบสอบถามทั้งหมดหรือไม่ ฉันคิดว่ามันถูกใช้เพื่อคำนวณคอมพ์ แต่ทำไม นอกจากนี้ทำไมแบบสอบถามสามารถใช้ดัชนีในt2แต่ไม่ได้อยู่t1?

แบบสอบถามและ DDL บน SQL Fiddle

NB ฉันติดแท็ก SQL Server 2008 เพราะนี่เป็นเวอร์ชันที่ปัญหาหลักของฉันเปิดอยู่ แต่ฉันก็มีพฤติกรรมเหมือนกันในปี 2555

คำตอบ:


20

เหตุใดจึงต้องมีการค้นหาคีย์เพื่อรับ A, B และ C เมื่อไม่มีการอ้างอิงในแบบสอบถามเลย ฉันคิดว่ามันถูกใช้เพื่อคำนวณคอมพ์ แต่ทำไม?

คอลัมน์A, B, and C มีการอ้างอิงในแผนแบบสอบถาม - T2พวกเขาจะถูกใช้โดยแสวงหาบน

นอกจากนี้ทำไมแบบสอบถามสามารถใช้ดัชนีใน t2 ได้ แต่ไม่ใช่ใน t1

เครื่องมือเพิ่มประสิทธิภาพตัดสินใจว่าการสแกนดัชนีคลัสเตอร์นั้นราคาถูกกว่าการสแกนดัชนีที่ไม่ได้กรองและทำการค้นหาเพื่อดึงค่าสำหรับคอลัมน์ A, B และ C

คำอธิบาย

คำถามที่แท้จริงคือทำไมเครื่องมือเพิ่มประสิทธิภาพรู้สึกถึงความต้องการในการดึงข้อมูล A, B และ C สำหรับดัชนีที่ค้นหาได้ทั้งหมด เราคาดว่ามันจะอ่านCompคอลัมน์โดยใช้การสแกนดัชนีแบบ nonclustered แล้วทำการค้นหาในดัชนีเดียวกัน (นามแฝง T2) เพื่อค้นหาบันทึก 1 อันดับสูงสุด

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

เมื่อเครื่องมือเพิ่มประสิทธิภาพพบคิวรีย่อยที่มีความสัมพันธ์กันมันจะพยายาม 'คลาย' ลงในฟอร์มที่พบว่ามีเหตุผลง่ายกว่า หากไม่พบความเรียบง่ายที่มีประสิทธิภาพมากกว่านั้นก็ใช้วิธีเขียนคำสืบค้นย่อยที่สัมพันธ์กันใหม่เป็นแบบสมัคร (การรวมแบบสัมพันธ์ที่มีความสัมพันธ์):

สมัครเขียนใหม่

มันเกิดขึ้นเพียงเพื่อที่ว่านี้มีผลบังคับใช้ทำให้คลี่ต้นไม้แบบสอบถามตรรกะในรูปแบบที่ไม่ทำงานได้ดีกับการฟื้นฟูโครงการ (ขั้นตอนภายหลังที่มีลักษณะตรงกับการแสดงออกทั่วไปคอลัมน์คำนวณในสิ่งอื่น ๆ )

ในกรณีของคุณวิธีการสอบถามที่เขียนโต้ตอบกับรายละเอียดภายในของการเพิ่มประสิทธิภาพดังกล่าวว่าคำนิยามของการแสดงออกของการขยายตัวไม่ตรงกลับไปที่คอลัมน์คำนวณและคุณจบลงด้วยการแสวงหาที่อ้างอิงคอลัมน์แทนของคอลัมน์คำนวณA, B, and C Compนี่คือสาเหตุที่แท้จริง

วิธีแก้ปัญหา

แนวคิดหนึ่งในการแก้ไขปัญหาผลข้างเคียงนี้คือการเขียนคิวรีเป็นการใช้ด้วยตนเอง:

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
CROSS APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

น่าเสียดายที่แบบสอบถามนี้จะไม่ใช้ดัชนีที่กรองอย่างที่เราหวังไว้ การทดสอบความไม่เท่าเทียมกันในคอลัมน์Dภายในการปฏิเสธการใช้NULLsดังนั้นภาคแสดงความซ้ำซ้อนที่เห็นได้ชัดWHERE T1.D IS NOT NULLนั้นได้รับการปรับให้เหมาะสม

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

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
OUTER APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

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

สมัครงานนอกแผน

โดยทั่วไปน่าจะเป็นที่ต้องการมากกว่าการเพิ่ม A, B และ C เป็นINCLUDEdคอลัมน์ในดัชนีที่กรองเพราะมันระบุถึงสาเหตุของปัญหาและไม่ต้องการขยายดัชนีโดยไม่จำเป็น

คอลัมน์ที่คำนวณไว้

ในฐานะที่เป็นหมายเหตุด้านข้างคุณไม่จำเป็นต้องทำเครื่องหมายคอลัมน์ที่คำนวณเป็นPERSISTEDถ้าคุณไม่รังเกียจที่จะนิยามคำจำกัดความซ้ำในCHECKข้อ จำกัด :

CREATE TABLE dbo.T 
(   
    ID integer IDENTITY(1, 1) NOT NULL,
    A varchar(20) NOT NULL,
    B varchar(20) NOT NULL,
    C varchar(20) NOT NULL,
    D date NULL,
    E varchar(20) NULL,
    Comp AS A + '-' + B + '-' + C,

    CONSTRAINT CK_T_Comp_NotNull
        CHECK (A + '-' + B + '-' + C IS NOT NULL),

    CONSTRAINT PK_T_ID 
        PRIMARY KEY (ID)
);

CREATE NONCLUSTERED INDEX IX_T_Comp_D
ON dbo.T (Comp, D) 
WHERE D IS NOT NULL;

คอลัมน์ที่คำนวณนั้นจำเป็นต้องมีPERSISTEDในกรณีนี้เท่านั้นหากคุณต้องการใช้NOT NULLข้อ จำกัด หรืออ้างอิงCompคอลัมน์โดยตรง (แทนที่จะทำซ้ำคำจำกัดความของมัน) ในCHECKข้อ จำกัด


2
+1 BTW ฉันเจอกรณีอื่นของการค้นหาที่ไม่จำเป็นในขณะที่มองสิ่งนี้ซึ่งคุณอาจสนใจหรือไม่สนใจก็ได้ SQL ซอ
Martin Smith

@MartinSmith ใช่นั่นน่าสนใจ กฎทั่วไปอีกข้อหนึ่งเขียนใหม่ ( FOJNtoLSJNandLASJN) ซึ่งส่งผลให้สิ่งต่าง ๆ ไม่ทำงานอย่างที่เราคาดหวังและทิ้งขยะ (BaseRow / Checksums) ที่มีประโยชน์ในแผนบางประเภท (เช่นเคอร์เซอร์) แต่ไม่ต้องการที่นี่
พอลไวท์พูดว่า GoFundMonica

อ่าChkตรวจสอบแล้ว! ขอบคุณฉันไม่แน่ใจเกี่ยวกับเรื่องนั้น แต่เดิมฉันคิดว่ามันอาจจะเป็นสิ่งที่ต้องทำกับข้อ จำกัด ของการตรวจสอบ
Martin Smith

6

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

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;

สิ่งนี้ให้ผลการวางแผนต้นทุนต่ำที่ดีโดยใช้ดัชนีของคุณและมีการอ่านต่ำกว่าตัวเลือกอื่น ๆ อย่างมีนัยสำคัญ (และผลลัพธ์เดียวกันสำหรับข้อมูลการทดสอบของคุณ)

Plan Explorer มีค่าใช้จ่ายสำหรับสี่ตัวเลือก: ดั้งเดิม;  ต้นฉบับพร้อมคำใบ้;  ใช้ภายนอกและตะกั่ว

ฉันสงสัยว่าข้อมูลจริงของคุณมีความซับซ้อนมากขึ้นดังนั้นอาจมีบางสถานการณ์ที่แบบสอบถามนี้มีความแตกต่างทางอรรถศาสตร์ของคุณ แต่บางครั้งก็แสดงคุณลักษณะใหม่ที่สามารถสร้างความแตกต่างได้อย่างแท้จริง

ฉันทำการทดลองกับข้อมูลที่หลากหลายมากขึ้นและพบว่าบางสถานการณ์ตรงกันและบางอย่างไม่:

--Example 1: results matched
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn + b.rn, '1 Jan 2013')
FROM cte a
    CROSS JOIN cte b
WHERE a.rn % 3 = 0
 AND b.rn % 5 = 0
ORDER BY 1, 2, 3
GO


-- Original query
SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY D
            )
INTO #tmp1
FROM    dbo.T t1 
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;
GO

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
INTO #tmp2
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;
GO


-- Checks ...
SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1


Example 2: results did not match
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn, '1 Jan 2013')
FROM cte a

-- Add some more data
INSERT dbo.T (A, B, C, D)
SELECT A, B, C, D 
FROM dbo.T
WHERE DAY(D) In ( 3, 7, 9 )


INSERT dbo.T (A, B, C, D)
SELECT A, B, C, DATEADD( day, 1, D )
FROM dbo.T
WHERE DAY(D) In ( 12, 13, 17 )


SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1

SELECT * FROM #tmp2
INTERSECT
SELECT * FROM #tmp1


select * from #tmp1
where comp = 'A2-B2-C2'

select * from #tmp2
where comp = 'A2-B2-C2'

1
มันใช้ดัชนี แต่ขึ้นอยู่กับจุดเท่านั้น หากcompไม่ใช่คอลัมน์จากการคำนวณคุณจะไม่เห็นการจัดเรียง
Martin Smith

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

-1

เมื่อฉันพยายามที่จะดำเนินการเดียวกันได้รับผลลัพธ์อื่น ประการแรกแผนการดำเนินการของฉันสำหรับตารางที่ไม่มีดัชนีมีลักษณะดังนี้:ป้อนคำอธิบายรูปภาพที่นี่

ดังที่เราเห็นได้จากการสแกนดัชนีแบบคลัสเตอร์ (t2) ระบบจะใช้เพรดิเคตเพื่อกำหนดแถวที่ต้องการส่งคืน (เนื่องจากเงื่อนไข):

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

เมื่อเพิ่มดัชนีแล้วไม่ว่าจะถูกกำหนดโดยตัวดำเนินการ WITH หรือไม่ก็ตามแผนปฏิบัติการจะกลายเป็นดังต่อไปนี้:

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

อย่างที่เราเห็นการสแกนดัชนีแบบกลุ่มจะถูกแทนที่ด้วยการสแกนดัชนี ดังที่เราเห็นด้านบน SQL Server ใช้คอลัมน์แหล่งที่มาของคอลัมน์ที่คำนวณเพื่อทำการจับคู่ของแบบสอบถามที่ซ้อนกัน ในระหว่างการสแกนดัชนีแบบคลัสเตอร์ค่าทั้งหมดนี้สามารถรับได้ในเวลาเดียวกัน (ไม่จำเป็นต้องดำเนินการเพิ่มเติม) เมื่อเพิ่มดัชนีการกรองแถวที่จำเป็นจากตาราง (ในตัวเลือกหลัก) กำลังดำเนินการตามดัชนี แต่ค่าของคอลัมน์แหล่งที่มาสำหรับคอลัมน์ที่คำนวณcompยังคงต้องได้รับ (การดำเนินการล่าสุดลูปที่ซ้อนกัน) .

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

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

PS ดูเหมือนว่าบั๊กใน SQL Server

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