เหตุใดฟังก์ชัน LEN () จึงประเมินค่าความสำคัญต่ำใน SQL Server 2014


26

ฉันมีตารางที่มีคอลัมน์สตริงและเพรดิเคตที่ตรวจสอบแถวที่มีความยาวแน่นอน ใน SQL Server 2014 ฉันเห็นการประมาณ 1 แถวโดยไม่คำนึงถึงความยาวที่ฉันกำลังตรวจสอบ นี่เป็นแผนที่แย่มากเพราะมีหลายพันหรือหลายล้านแถวและ SQL Server เลือกที่จะวางตารางนี้ไว้ที่ด้านนอกของลูปที่ซ้อนกัน

มีคำอธิบายสำหรับการประมาณค่า cardinality ที่ 1.0003 สำหรับ SQL Server 2014 หรือไม่ในขณะที่ SQL Server 2012 ประมาณ 31,622 แถว มีวิธีแก้ปัญหาที่ดีหรือไม่?

นี่คือการทำซ้ำสั้น ๆ ของปัญหา:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

นี่คือสคริปต์ที่สมบูรณ์ยิ่งขึ้นซึ่งแสดงการทดสอบเพิ่มเติม

ฉันได้อ่านwhitepaper ในเครื่องมือประมาณการ Cardinality ของ SQL Server 2014แล้ว แต่ไม่พบสิ่งใดที่ทำให้สถานการณ์ชัดเจนขึ้น

คำตอบ:


20

สำหรับ CE มรดกที่ฉันเห็นประมาณการสำหรับ 3.16228% ของแถว - และนั่นคือการแก้ปัญหา "จำนวนมายากล" ที่ใช้สำหรับคอลัมน์ = ภาคอักษร (มีการวิเคราะห์พฤติกรรมอื่น ๆ ที่อยู่บนพื้นฐานของการก่อสร้างกริยา - แต่LENห่อรอบคอลัมน์สำหรับ ผลลัพธ์ CE ดั้งเดิมตรงกับกรอบการเดานี้) คุณสามารถดูตัวอย่างของสิ่งนี้ได้จากโพสต์เกี่ยวกับSelectivity Guesses โดยที่ไม่มี Sacks ของ Joe และไม่มีการเปรียบเทียบค่าคงที่โดย Ian Jose

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

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

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

น่าเสียดายที่ตรรกะอาศัยการประมาณจำนวนค่าที่แตกต่างกันซึ่งไม่ได้ปรับสำหรับผลกระทบของLENฟังก์ชัน

วิธีแก้ไขที่เป็นไปได้

คุณสามารถรับการประมาณค่าแบบไตรภาคภายใต้แบบจำลอง CE ทั้งสองโดยเขียนLENเป็นLIKE:

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

แผนเหมือน


ข้อมูลเกี่ยวกับ Trace Flags ที่ใช้:

  • 2363: แสดงข้อมูลจำนวนมากรวมถึงสถิติที่ถูกโหลด
  • 3604: พิมพ์เอาต์พุตของคำสั่ง DBCC ไปยังแท็บข้อความ

13

มีคำอธิบายสำหรับการประมาณค่า cardinality ที่ 1.0003 สำหรับ SQL 2014 ในขณะที่ SQL 2012 ประมาณ 31,622 แถว

ฉันคิดว่าคำตอบของ @ Zane ครอบคลุมส่วนนี้ค่อนข้างดี

มีวิธีแก้ปัญหาที่ดีหรือไม่?

คุณสามารถลองสร้างคอลัมน์ที่คำนวณไม่ได้สำหรับLEN(cust_nbr)และ (เป็นทางเลือก) สร้างดัชนีที่ไม่เป็นคลัสเตอร์บนคอลัมน์ที่คำนวณ ที่ควรจะได้รับสถิติที่ถูกต้อง

ฉันทำการทดสอบและนี่คือสิ่งที่ฉันพบ:

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

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