การเข้าร่วมแฮชระหว่างตารางต้นแบบ / ตารางรายละเอียดจะสร้างการประมาณค่าเชิงหัวใจต่ำเกินไป


9

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

ตัวอย่างเช่นเมื่อเข้าร่วมแถวหลัก 10K ถึงแถวรายละเอียด 100K ฉันต้องการให้ SQL Server ประมาณค่าการเข้าร่วมที่แถว 100K - เหมือนกับจำนวนแถวรายละเอียดโดยประมาณ ฉันควรจัดโครงสร้างคิวรีและ / หรือตารางและ / หรือดัชนีของฉันอย่างไรเพื่อช่วยให้ตัวประมาณของ SQL Server ใช้ประโยชน์จากข้อเท็จจริงที่ว่าทุกแถวรายละเอียดมีแถวหลักที่สอดคล้องกันเสมอ (หมายความว่าการเข้าร่วมระหว่างพวกเขาไม่ควรลดค่าประมาณของ cardinality)

นี่คือรายละเอียดเพิ่มเติม ฐานข้อมูลของเรามีคู่ของตารางต้นแบบ / รายละเอียด: VisitTargetมีหนึ่งแถวสำหรับแต่ละธุรกรรมการขายและVisitSaleมีหนึ่งแถวสำหรับแต่ละผลิตภัณฑ์ในแต่ละธุรกรรม มันเป็นความสัมพันธ์แบบหนึ่งต่อหลายคน: หนึ่งแถว VisitTarget สำหรับแถว VisitSale เฉลี่ย 10 แถว

ตารางมีลักษณะดังนี้: (ฉันลดความซับซ้อนของคอลัมน์ที่เกี่ยวข้องสำหรับคำถามนี้เท่านั้น)

-- "master" table
CREATE TABLE VisitTarget
(
  VisitTargetId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  SaleDate date NOT NULL,
  StoreId int NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitTarget_SaleDate 
    ON VisitTarget (SaleDate) INCLUDE (StoreId /*, ...more columns */);

-- "detail" table
CREATE TABLE VisitSale
(
  VisitSaleId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  VisitTargetId int NOT NULL,
  SaleDate date NOT NULL, -- denormalized; copied from VisitTarget
  StoreId int NOT NULL, -- denormalized; copied from VisitTarget
  ItemId int NOT NULL,
  SaleQty int NOT NULL,
  SalePrice decimal(9,2) NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitSale_SaleDate 
  ON VisitSale (SaleDate)
  INCLUDE (VisitTargetId, StoreId, ItemId, SaleQty, TotalSalePrice decimal(9,2) /*, ...more columns */
);
ALTER TABLE VisitSale 
  WITH CHECK ADD CONSTRAINT FK_VisitSale_VisitTargetId 
  FOREIGN KEY (VisitTargetId)
  REFERENCES VisitTarget (VisitTargetId);
ALTER TABLE VisitSale
  CHECK CONSTRAINT FK_VisitSale_VisitTargetId;

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

เมื่อเราเข้าร่วมสองตารางเหล่านี้แบบสอบถามจะมีลักษณะดังนี้:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitTarget vt 
    JOIN VisitSale vs on vt.VisitTargetId = vs.VisitTargetId
WHERE
    vs.SaleDate BETWEEN '20170101' and '20171231'
    and vt.SaleDate BETWEEN '20170101' and '20171231'
    -- more filtering goes here, e.g. by store, by product, etc. 

ตัวกรองวันที่บนตารางรายละเอียด ( VisitSale) ซ้ำซ้อน อยู่ที่นั่นเพื่อเปิดใช้งาน I / O ตามลำดับ (ตัวดำเนินการค้นหาดัชนี) บนตารางรายละเอียดสำหรับข้อความค้นหาที่กรองตามช่วงวันที่

แผนสำหรับข้อความค้นหาประเภทนี้มีลักษณะดังนี้:

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

แผนการที่เกิดขึ้นจริงของแบบสอบถามที่มีปัญหาเดียวกันสามารถพบได้ที่นี่

อย่างที่คุณเห็นการประมาณค่าความสำคัญของการเข้าร่วม (คำแนะนำเครื่องมือในมุมซ้ายล่างของภาพ) สูงกว่า 4x ต่ำเกินไป: 2.1M จริงกับ 0.5M โดยประมาณ ซึ่งทำให้เกิดปัญหาประสิทธิภาพ (เช่น spilling to tempdb) โดยเฉพาะเมื่อแบบสอบถามนี้เป็นแบบสอบถามย่อยที่ใช้ในแบบสอบถามที่ซับซ้อนมากขึ้น

แต่การประมาณจำนวนแถวสำหรับแต่ละสาขาของการรวมจะใกล้เคียงกับจำนวนแถวจริง ครึ่งบนของการเข้าร่วมคือ 100K จริงกับ 164K โดยประมาณ ครึ่งล่างของการเข้าร่วมคือแถว 2.1M จริงกับ 3.7M โดยประมาณ การกระจายตัวของแฮชถังยังดูดี ข้อสังเกตเหล่านี้แนะนำให้ฉันทราบว่าสถิตินั้นใช้ได้สำหรับแต่ละตารางและปัญหาคือการประมาณความเข้าร่วมของ cardinality

ตอนแรกฉันคิดว่าปัญหาคือ SQL Server ที่คาดหวังว่าคอลัมน์ SaleDate ในแต่ละตารางมีความเป็นอิสระในขณะที่พวกเขาเหมือนกันจริงๆ ดังนั้นฉันจึงพยายามเพิ่มการเปรียบเทียบความเท่าเทียมกันสำหรับวันที่ลดราคาลงในเงื่อนไขการเข้าร่วมหรือข้อ WHERE เช่น

ON vt.VisitTargetId = vs.VisitTargetId and vt.SaleDate = vs.SaleDate

หรือ

WHERE vt.SaleDate = vs.SaleDate

สิ่งนี้ไม่ทำงาน มันยังทำให้การประเมินความสำคัญของหัวใจแย่ลง! ดังนั้น SQL Server ไม่ได้ใช้คำแนะนำความเท่าเทียมกันนั้นหรืออย่างอื่นเป็นสาเหตุของปัญหา

มีความคิดเห็นใด ๆ สำหรับวิธีการแก้ไขปัญหาและหวังว่าจะแก้ไขปัญหาการประมาณค่า cardinality นี้หรือไม่? เป้าหมายของฉันคือความสำคัญของการเข้าร่วมของมาสเตอร์ / รายละเอียดที่จะประมาณเช่นเดียวกับการประมาณสำหรับอินพุต ("ตารางรายละเอียด") ขนาดใหญ่ของการเข้าร่วม

หากเป็นเรื่องสำคัญเรากำลังเรียกใช้ SQL Server 2014 Enterprise SP2 CU8 บิลด์ 12.0.5557.0 บน Windows Server ไม่มีการเปิดใช้งานค่าสถานะการสืบค้นกลับ ระดับความเข้ากันได้ของฐานข้อมูลคือ SQL Server 2014 เราเห็นการทำงานแบบเดียวกันกับเซิร์ฟเวอร์ SQL ที่แตกต่างกันหลายตัวดังนั้นจึงดูเหมือนว่าจะไม่เกิดปัญหาเฉพาะเซิร์ฟเวอร์

มีการเพิ่มประสิทธิภาพในเครื่องมือประมาณการ Cardinality ของ SQL Server 2014ที่เป็นพฤติกรรมที่ฉันกำลังมองหา:

อย่างไรก็ตาม CE ใหม่ใช้อัลกอริทึมที่ง่ายกว่าซึ่งสันนิษฐานว่ามีการเชื่อมโยงการเข้าร่วมแบบหนึ่งต่อกลุ่มระหว่างตารางขนาดใหญ่และตารางขนาดเล็ก สมมติว่าแต่ละแถวในตารางขนาดใหญ่ตรงกับหนึ่งแถวในตารางขนาดเล็ก อัลกอริทึมนี้ส่งคืนขนาดโดยประมาณของอินพุตที่ใหญ่กว่าว่าเป็น cardinality ที่เข้าร่วม

เป็นการดีที่ฉันจะได้รับพฤติกรรมนี้โดยที่ cardinality ประมาณการสำหรับการเข้าร่วมจะเป็นเช่นเดียวกับการประมาณการสำหรับตารางที่มีขนาดใหญ่แม้ว่าตาราง "เล็ก" ของฉันจะยังคงส่งคืนมากกว่า 100K แถว!

คำตอบ:


6

สมมติว่าคุณไม่สามารถปรับปรุงอะไรได้ด้วยการทำสถิติหรือใช้ CE ดั้งเดิมวิธีที่ตรงไปตรงมาที่สุดในการแก้ไขปัญหาของคุณคือการเปลี่ยนINNER JOINเป็นLEFT OUTER JOIN:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitSale vs
    LEFT OUTER JOIN VisitTarget vt on vt.VisitTargetId = vs.VisitTargetId
            AND vt.SaleDate BETWEEN '20170101' and '20171231'
WHERE vs.SaleDate BETWEEN '20170101' and '20171231'

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

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

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