SQL Server 2014: คำอธิบายใด ๆ สำหรับการประเมิน cardinality ที่ไม่สอดคล้องกันของตนเอง?


27

พิจารณาแผนแบบสอบถามต่อไปนี้ใน SQL Server 2014:

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

ในแผนแบบสอบถามการเข้าร่วมด้วยตนเองar.fId = ar.fIdจะให้ผลประมาณ 1 แถว อย่างไรก็ตามนี่เป็นค่าประมาณที่ไม่สอดคล้องกันเชิงตรรกะ: arมี20,608แถวและมีค่าที่แตกต่างกันเพียงหนึ่งค่าfId(สะท้อนให้เห็นอย่างถูกต้องในสถิติ) ดังนั้นการเข้าร่วมนี้จะสร้างผลิตภัณฑ์ไขว้เต็มของแถว ( ~424MMแถว) ทำให้คิวรีทำงานเป็นเวลาหลายชั่วโมง

ฉันมีเวลายากที่จะเข้าใจว่าทำไม SQL Server ถึงได้มีการประมาณการที่สามารถพิสูจน์ได้อย่างง่ายดายว่าไม่สอดคล้องกับสถิติ ความคิดใด ๆ

การตรวจสอบเบื้องต้นและรายละเอียดเพิ่มเติม

จากคำตอบของ Paul ที่นี่ดูเหมือนว่าทั้งฮิวริสติก SQL 2012 และ SQL 2014 สำหรับการประเมิน cardinality ที่เข้าร่วมควรจัดการสถานการณ์ที่ฮิสโทแกรมที่เหมือนกันทั้งสองต้องเปรียบเทียบได้ง่าย

ฉันเริ่มต้นด้วยผลลัพธ์จากการตั้งค่าสถานะการสืบค้นกลับ 2363 แต่ไม่สามารถเข้าใจได้อย่างง่ายดาย ตัวอย่างต่อไปนี้หมายความว่า SQL Server เปรียบเทียบฮิสโตแกรมสำหรับfIdและbIdเพื่อประเมินการเลือกของการเข้าร่วมที่ใช้เท่านั้นfIdหรือไม่ ถ้าเป็นเช่นนั้นแน่นอนว่าจะไม่ถูกต้อง หรือฉันกำลังอ่านเอาต์พุตแฟล็กการติดตามผิด?

Plan for computation:
  CSelCalcExpressionComparedToExpression( QCOL: [ar].fId x_cmpEq QCOL: [ar].fId )
Loaded histogram for column QCOL: [ar].bId from stats with id 3
Loaded histogram for column QCOL: [ar].fId from stats with id 1
Selectivity: 0

โปรดทราบว่าฉันมีวิธีแก้ไขปัญหาหลายประการซึ่งรวมอยู่ในสคริปต์ repro เต็มรูปแบบและทำให้แบบสอบถามนี้ลดลงเป็นมิลลิวินาที คำถามนี้มุ่งเน้นที่การทำความเข้าใจเกี่ยวกับพฤติกรรมวิธีหลีกเลี่ยงในการสืบค้นในอนาคตและพิจารณาว่าเป็นข้อบกพร่องที่ควรยื่นกับ Microsoft หรือไม่

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

WITH cte AS (
    SELECT ar.fId, 
        ar.bId,
        MIN(CONVERT(INT, ar.isT)) AS isT,
        MAX(CONVERT(INT, tcr.isS)) AS isS
    FROM  #SQL2014MinMaxAggregateCardinalityBug_ar ar 
    LEFT OUTER JOIN #SQL2014MinMaxAggregateCardinalityBug_tcr tcr
        ON tcr.rId = 508
        AND tcr.fId = ar.fId
        AND tcr.bId = ar.bId
    GROUP BY ar.fId, ar.bId
)
SELECT s.fId, s.bId, s.isS, t.isS
FROM cte s 
JOIN cte t 
    ON t.fId = s.fId 
    AND t.isT = 1

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_ar (
    fId INT NOT NULL,
    bId INT NOT NULL,
    isT BIT NOT NULL
    PRIMARY KEY (fId, bId)
)

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_tcr (
    rId INT NOT NULL,
    fId INT NOT NULL,
    bId INT NOT NULL,
    isS BIT NOT NULL
    PRIMARY KEY (rId, fId, bId, isS)
)

คำตอบ:


23

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

ความมั่นคง

ไม่มีการรับประกันความสอดคล้องโดยทั่วไป การประมาณการอาจถูกคำนวณในทรีย่อย (แต่เทียบเท่าเชิงตรรกะ) ในเวลาที่ต่างกันโดยใช้วิธีการทางสถิติที่แตกต่างกัน

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

การประมาณค่าเริ่มต้น

ในกรณีเฉพาะของคุณการประมาณค่าเชิงการนับเบื้องต้นสำหรับการเข้าร่วมนั้นไม่ได้ดำเนินการกับ subtrees ที่เหมือนกันสองค่า รูปร่างของต้นไม้ในเวลานั้นคือ:

  LogOp_Join
     LogOp_GbAgg
        LogOp_LeftOuterJoin
           LogOp_ รับ TBL: ar
           LogOp_Select
              LogOp_Get TBL: tcr
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [tcr] .rId
                 ScaOp_Const Value = 508
           ScaOp_Logical x_lopAnd
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .fId
                 ScaOp_Identifier [tcr] .fId
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .bId
                 ScaOp_Identifier [tcr] .bId
        AncOp_PrjList 
           AncOp_PrjEl Expr1003 
              ScaOp_AggFunc stopMax
                 ScaOp_Convert int
                    ScaOp_Identifier [tcr] .isS
     LogOp_Select
        LogOp_GbAgg
           LogOp_LeftOuterJoin
              LogOp_ รับ TBL: ar
              LogOp_Select
                 LogOp_Get TBL: tcr
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [tcr] .rId
                    ScaOp_Const Value = 508
              ScaOp_Logical x_lopAnd
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .fId
                    ScaOp_Identifier [tcr] .fId
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .bId
                    ScaOp_Identifier [tcr] .bId
           AncOp_PrjList 
              AncOp_PrjEl Expr1006 
                 ScaOp_AggFunc stopMin
                    ScaOp_Convert int
                       ScaOp_Identifier [ar] .isT
              AncOp_PrjEl Expr1007 
                 ScaOp_AggFunc stopMax
                    ScaOp_Convert int
                       ScaOp_Identifier [tcr] .isS
        ScaOp_Comp x_cmpEq
           ScaOp_Identifier Expr1006 
           ScaOp_Const Value = 1
     ScaOp_Comp x_cmpEq
        ScaOp_Identifier QCOL: [ar] .fId
        ScaOp_Identifier QCOL: [ar] .fId

ครั้งแรกที่เข้าร่วมการป้อนข้อมูลที่ได้มีการรวม unprojected ง่ายไปและที่สองเข้าร่วมการป้อนข้อมูลมีกริยาt.isT = 1ผลักดันด้านล่างที่เป็นt.isT MIN(CONVERT(INT, ar.isT))อย่างไรก็ตามเรื่องนี้การคำนวณการเลือกเกิดของภาคisTแสดงสามารถใช้CSelCalcColumnInIntervalกับฮิสโตแกรมได้:

  CSelCalcColumnInInterval
      คอลัมน์: COL: Expr1006 

ฮิสโตแกรมที่โหลดสำหรับคอลัมน์ QCOL: [ar] .isT จากสถิติที่มี id 3

หัวกะทิ: 4.85248e-005

สร้างสถิติการสะสมแล้ว: 
  CStCollFilter (ID = 11, CARD = 1)
      CStCollGroupBy (ID = 10, CARD = 20608)
          CStCollOuterJoin (ID = 9, CARD = 20608 x_jtLeftOuter)
              CStCollBaseTable (ID = 3, CARD = 20608 TBL: ar)
              CStCollFilter (ID = 8, CARD = 1)
                  CStCollBaseTable (ID = 4, CARD = 28 TBL: tcr)

ความคาดหวัง (ถูกต้อง) สำหรับ 20,608 แถวที่จะลดลงเป็น 1 แถวโดยคำกริยานี้

เข้าร่วมการประเมิน

ตอนนี้คำถามกลายเป็นวิธีที่แถว 20,608 จากอินพุตการเข้าร่วมอื่นจะจับคู่กับแถวนี้:

  LogOp_Join
      CStCollGroupBy (ID = 7, CARD = 20608)
          CStCollOuterJoin (ID = 6, CARD = 20608 x_jtLeftOuter)
              ...

      CStCollFilter (ID = 11, CARD = 1)
          CStCollGroupBy (ID = 10, CARD = 20608)
              ...

      ScaOp_Comp x_cmpEq
          ScaOp_Identifier QCOL: [ar] .fId
          ScaOp_Identifier QCOL: [ar] .fId

มีหลายวิธีในการประเมินการเข้าร่วมโดยทั่วไป ตัวอย่างเช่นเราทำได้:

  • รับฮิสโตแกรมใหม่ที่ผู้ดำเนินการแผนในแต่ละแผนผังย่อยจัดเรียงไว้ที่การเข้าร่วม (การแทรกค่าขั้นตอนตามความจำเป็น) และดูวิธีการจับคู่ หรือ
  • ทำการจัดตำแหน่งฮิสโทแกรมแบบ 'หยาบ' ให้ง่ายขึ้น (ใช้ค่าต่ำสุดและสูงสุดไม่ใช่ทีละขั้นตอน); หรือ
  • คำนวณการเลือกแยกต่างหากสำหรับคอลัมน์เข้าร่วมเพียงอย่างเดียว (จากตารางฐานและไม่มีการกรองใด ๆ ) จากนั้นเพิ่มเอฟเฟกต์การเลือกของกริยาที่ไม่เข้าร่วม
  • ...

ขึ้นอยู่กับตัวประมาณการใช้งานของ cardinality และฮิวริสติกบางอย่างสามารถใช้ (หรือรูปแบบใดก็ได้) ก็ได้ ดูเอกสารทางเทคนิคของ Microsoft การปรับแผนแบบสอบถามของคุณให้เหมาะสมด้วย SQL Server 2014 Cardinality Estimatorสำหรับข้อมูลเพิ่มเติม

ข้อผิดพลาด?

ตอนนี้ดังที่ระบุไว้ในคำถามในกรณีนี้การรวมคอลัมน์แบบง่าย 'แบบง่าย' (บนfId) ใช้CSelCalcExpressionComparedToExpressionเครื่องคิดเลข:

วางแผนการคำนวณ:

  CSelCalcExpression เทียบกับ Expression [ar] .fId x_cmpEq [ar] .fId

ฮิสโตแกรมที่โหลดสำหรับคอลัมน์ QCOL: [ar] .bId จากสถิติที่มี id 2
ฮิสโตแกรมที่โหลดสำหรับคอลัมน์ QCOL: [ar] .fId จากสถิติที่มี id 1

หัวกะทิ: 0

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

รายละเอียดค่อนข้างยุ่งยาก แต่ความคาดหวังของการประมาณค่าจะขึ้นอยู่กับfIdฮิสโตแกรมที่ไม่มีการกรองแก้ไขโดยการเลือกของตัวกรองทำให้20608 * 20608 * 4.85248e-005 = 20608แถวมีความสมเหตุสมผล

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

แผน 9479

หมายเหตุการเข้าร่วมครั้งสุดท้ายก่อให้เกิด 20,608 แถวจากสองอินพุตแถวเดียว แต่ก็ไม่น่าแปลกใจ มันเป็นแผนเดียวกันที่ผลิตโดย CE ดั้งเดิมภายใต้ TF 9481

ฉันพูดถึงรายละเอียดที่ยุ่งยาก (และใช้เวลานานในการตรวจสอบ) แต่เท่าที่ฉันสามารถบอกได้สาเหตุของปัญหาเกี่ยวข้องกับภาคแสดงrId = 508โดยมีการเลือกแบบศูนย์ การประมาณค่าศูนย์นี้ถูกยกให้เป็นหนึ่งแถวในวิธีปกติซึ่งดูเหมือนจะมีส่วนร่วมในการประเมินการเลือกแบบศูนย์ที่คำถามที่เข้าร่วมเมื่อบัญชีสำหรับภาคแสดงต่ำลงในแผนภูมิอินพุต (ดังนั้นการโหลดสถิติสำหรับbId)

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

แผน 9473

พฤติกรรมของการประมาณเชิงการเข้าร่วมด้วยCSelCalcExpressionComparedToExpressionสามารถแก้ไขได้โดยไม่คำนึงถึง `` ตัวเลข 'ด้วยรูปแบบการเปลี่ยนแปลงอื่นที่ไม่มีเอกสาร (9494) ฉันพูดถึงสิ่งเหล่านี้เพราะฉันรู้ว่าคุณมีความสนใจในสิ่งต่าง ๆ ; ไม่ใช่เพราะพวกเขาเสนอทางออก จนกว่าคุณจะรายงานปัญหาไปยัง Microsoft และพวกเขาจัดการปัญหา (หรือไม่) การแสดงข้อความค้นหาต่างกันน่าจะเป็นวิธีที่ดีที่สุดในการส่งต่อ ไม่ว่าพฤติกรรมนั้นจะตั้งใจหรือไม่ก็ตามพวกเขาควรสนใจฟังเกี่ยวกับการถดถอย

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

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