นี่ดูเหมือนจะเป็นพฤติกรรมที่ไม่ตั้งใจ มันเป็นความจริงที่การประเมินความเป็นหัวใจไม่จำเป็นต้องสอดคล้องกันในแต่ละขั้นตอนของแผน แต่นี่เป็นแผนแบบสอบถามที่ค่อนข้างง่ายและการประเมินความสำคัญเชิงหัวใจขั้นสุดท้ายนั้นไม่สอดคล้องกับสิ่งที่ทำ การประเมินความเป็นหัวใจต่ำเช่นนี้อาจส่งผลให้ตัวเลือกที่ไม่ดีสำหรับประเภทการเข้าร่วมและวิธีการเข้าถึงสำหรับตารางอื่น ๆ ที่อยู่ท้ายน้ำในแผนที่ซับซ้อนมากขึ้น
จากการทดลองและข้อผิดพลาดเราสามารถค้นหาคำถามที่คล้ายกันซึ่งปัญหาไม่ปรากฏขึ้น:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
นอกจากนี้เรายังสามารถค้นหาข้อความค้นหาเพิ่มเติมที่มีปัญหาปรากฏขึ้น:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
ดูเหมือนจะมีรูปแบบ: หากมีการแสดงออกภายในCASE
ที่ไม่คาดว่าจะได้รับการดำเนินการและการแสดงออกผลเป็นแบบสอบถามย่อยกับตารางจากนั้นประมาณการแถวที่อยู่ที่ 1 หลังจากการแสดงออก
ถ้าฉันเขียนแบบสอบถามกับตารางที่มีดัชนีคลัสเตอร์กฎจะเปลี่ยนไปบ้าง เราสามารถใช้ข้อมูลเดียวกัน:
CREATE TABLE dbo.X_CI (ID INT NOT NULL, PRIMARY KEY (ID))
INSERT INTO dbo.X_CI WITH (TABLOCK)
SELECT * FROM dbo.X_HEAP;
UPDATE STATISTICS X_CI WITH FULLSCAN;
แบบสอบถามนี้มีการประมาณการขั้นสุดท้าย 1,000 แถว:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI;
แต่แบบสอบถามนี้มีการประมาณการขั้นสุดท้าย 1 แถว:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI;
หากต้องการขุดเพิ่มเติมนี้เราสามารถใช้การตั้งค่าสถานะการสืบค้นกลับที่ไม่มีเอกสาร2363เพื่อรับข้อมูลเกี่ยวกับวิธีที่เครื่องมือเพิ่มประสิทธิภาพการสืบค้นทำการคำนวณการเลือก ผมพบว่ามันเป็นประโยชน์ที่จะจับคู่สถานะการติดตามว่ามีไม่มีเอกสารร่องรอยธง 8606 TF 2363 ดูเหมือนจะให้การคำนวณแบบเลือกสำหรับทั้งต้นไม้ที่ง่ายขึ้นและต้นไม้หลังจากการทำให้เป็นมาตรฐานของโครงการ การเปิดใช้งานค่าสถานะการสืบค้นกลับทั้งคู่ทำให้ชัดเจนว่าการคำนวณใดที่นำไปใช้กับต้นไม้
ลองทำกับแบบสอบถามต้นฉบับที่โพสต์ไว้ในคำถาม:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
นี่เป็นส่วนหนึ่งของผลลัพธ์ที่ฉันคิดว่าเกี่ยวข้องกับความคิดเห็น:
Plan for computation:
CSelCalcColumnInInterval -- this is the type of calculator used
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID -- this is the column used for the calculation
Pass-through selectivity: 0 -- all rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- the row estimate after the join will still be 1000
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1 -- no rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter) -- the row estimate after the join will still be 1
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- here is the row estimate after the previous join
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: X_OTHER_TABLE_2)
ทีนี้ลองหาคำค้นหาที่คล้ายกันที่ไม่มีปัญหา ฉันจะใช้อันนี้:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ดีบักเอาต์พุตที่ท้ายสุด:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollConstTable(ID=4, CARD=1) -- this is different than before because we select a constant instead of from a table
ลองทำเคียวรีอื่นที่มีการประมาณแถวที่ไม่ดีอยู่:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ในตอนท้ายสุดการประมาณค่า cardinality จะลดลงเหลือ 1 แถวอีกครั้งหลังจากการเลือกแบบพาส - ทรู = 1 การประมาณค่าแบบ cardinality นั้นจะยังคงอยู่หลังจากค่าการเลือกที่ 0.501 และ 0.499
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.501
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=12, CARD=1 x_jtLeftOuter) -- this is associated with the ELSE expression
CStCollOuterJoin(ID=11, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=10, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=4, CARD=1 TBL: X_OTHER_TABLE)
ลองสลับไปยังอีกแบบสอบถามที่คล้ายกันที่ไม่มีปัญหา ฉันจะใช้อันนี้:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ในผลลัพธ์การดีบักไม่เคยมีขั้นตอนใดที่มีการเลือกผ่านแบบ 1 การประมาณความน่าจะอยู่ที่ 1,000 แถว
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
End selectivity computation
สิ่งที่เกี่ยวกับแบบสอบถามเมื่อมันเกี่ยวข้องกับตารางที่มีดัชนีคลัสเตอร์? พิจารณาแบบสอบถามต่อไปนี้ด้วยปัญหาการประมาณแถว:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
จุดสิ้นสุดของเอาต์พุตดีบักคล้ายกับสิ่งที่เราได้เห็นแล้ว:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_CI].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
อย่างไรก็ตามเคียวรีกับ CI ที่ไม่มีปัญหามีเอาต์พุตต่างกัน ใช้แบบสอบถามนี้:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ผลลัพธ์ในการใช้เครื่องคิดเลขที่แตกต่างกัน CSelCalcColumnInInterval
ไม่ปรากฏอีกต่อไป:
Plan for computation:
CSelCalcFixedFilter (0.559)
Pass-through selectivity: 0.559
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
...
Plan for computation:
CSelCalcUniqueKeyFilter
Pass-through selectivity: 0.001
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE)
โดยสรุปเราดูเหมือนจะได้รับการประมาณการแถวที่ไม่ดีหลังจากแบบสอบถามย่อยภายใต้เงื่อนไขดังต่อไปนี้:
CSelCalcColumnInInterval
เครื่องคิดเลขหัวกะทิถูกนำมาใช้ ฉันไม่รู้ว่ามันถูกใช้ไปเมื่อไหร่ แต่ดูเหมือนว่าจะปรากฏบ่อยขึ้นเมื่อตารางฐานเป็นกอง
Pass-through selectivity = 1 กล่าวอีกนัยหนึ่งCASE
คาดว่าจะมีการแสดงออกอย่างใดอย่างหนึ่งเป็นเท็จสำหรับทุกแถว มันไม่สำคัญว่าCASE
นิพจน์แรกประเมินว่าเป็นจริงสำหรับทุกแถว
มีการเข้าร่วม outer CStCollBaseTable
ไป กล่าวอีกนัยหนึ่งการCASE
แสดงออกของผลลัพธ์เป็นแบบสอบถามย่อยเทียบกับตาราง ค่าคงที่จะไม่ทำงาน
ภายใต้เงื่อนไขเหล่านั้นเคียวรีเครื่องมือเพิ่มประสิทธิภาพอาจใช้การเลือกแบบพาส - ทรูกับการประมาณแถวของตารางด้านนอกแทนการทำงานที่ทำในส่วนด้านในของลูปซ้อน นั่นจะลดค่าประมาณแถวเป็น 1
ฉันสามารถหาวิธีแก้ไขสองวิธี ฉันไม่สามารถทำซ้ำปัญหาเมื่อใช้APPLY
แทนแบบสอบถามย่อย การส่งออกของร่องรอยธง 2363 APPLY
แตกต่างอย่างมากกับ ต่อไปนี้เป็นวิธีหนึ่งในการเขียนแบบสอบถามต้นฉบับในคำถาม:
SELECT
h.ID
, a.ID2
FROM X_HEAP h
OUTER APPLY
(
SELECT CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END
) a(ID2);
CE ดั้งเดิมปรากฏขึ้นเพื่อหลีกเลี่ยงปัญหานี้เช่นกัน
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
รายการการเชื่อมต่อถูกส่งสำหรับปัญหานี้ (กับบางส่วนของรายละเอียดที่พอลไวท์ที่ระบุไว้ในคำตอบของเขา)