ฉันจะโพสต์คำตอบเพื่อเริ่มต้น ความคิดแรกของฉันคือควรใช้ประโยชน์จากลักษณะการวนรอบที่ซ้อนกันของการรวมกลุ่มพร้อมกับตารางตัวช่วยสองสามตัวที่มีหนึ่งแถวสำหรับแต่ละตัวอักษร ส่วนที่ซับซ้อนนั้นจะวนไปวนมาในลักษณะที่ผลลัพธ์เรียงตามความยาวรวมทั้งหลีกเลี่ยงการซ้ำซ้อน ตัวอย่างเช่นเมื่อข้ามการเข้าร่วม CTE ที่มีตัวอักษรใหญ่ทั้งหมด 26 ตัวพร้อมกับ '' คุณสามารถสร้างได้'A' + '' + 'A'
และ'' + 'A' + 'A'
เป็นสตริงตัวเดียวกัน
การตัดสินใจครั้งแรกคือสถานที่จัดเก็บข้อมูลผู้ช่วย ฉันลองใช้ตาราง temp แต่สิ่งนี้มีผลกระทบด้านลบอย่างน่าประหลาดใจต่อประสิทธิภาพการทำงานแม้ว่าข้อมูลจะพอดีกับหน้าเว็บเดียว ตารางอุณหภูมิมีข้อมูลด้านล่าง:
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
เมื่อเปรียบเทียบกับการใช้ CTE แบบสอบถามจะใช้เวลานานกว่า 3X ด้วยตารางคลัสเตอร์และ 4X อีกต่อไปเมื่อใช้ฮีป ฉันไม่เชื่อว่าปัญหาคือข้อมูลอยู่ในดิสก์ มันควรจะอ่านในหน่วยความจำเป็นหน้าเดียวและประมวลผลในหน่วยความจำสำหรับแผนทั้งหมด บางที SQL Server สามารถทำงานกับข้อมูลจากตัวดำเนินการสแกนอย่างมีประสิทธิภาพมากกว่าที่ทำได้กับข้อมูลที่เก็บไว้ในหน้าร้านแถวปกติ
ที่น่าสนใจ SQL Server เลือกที่จะนำผลลัพธ์ที่ได้รับคำสั่งจากหน้า tempdb หน้าเดียวที่มีข้อมูลที่สั่งไว้ในสปูลของตาราง:
SQL Server มักจะใส่ผลลัพธ์สำหรับตารางด้านในของ cross เข้าร่วมในสปูลตารางแม้ว่ามันจะดูไร้สาระก็ตาม ฉันคิดว่าเครื่องมือเพิ่มประสิทธิภาพต้องใช้งานเล็กน้อยในพื้นที่นี้ ฉันเรียกใช้แบบสอบถามด้วยNO_PERFORMANCE_SPOOL
เพื่อหลีกเลี่ยงการเข้าชมที่มีประสิทธิภาพ
ปัญหาอย่างหนึ่งของการใช้ CTE ในการจัดเก็บข้อมูลตัวช่วยก็คือข้อมูลไม่รับประกันว่าจะถูกสั่งซื้อ ฉันไม่สามารถคิดได้ว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจะเลือกที่จะไม่สั่งซื้อและในการทดสอบทั้งหมดของฉันข้อมูลจะถูกประมวลผลตามลำดับที่ฉันเขียน CTE:
อย่างไรก็ตามไม่ควรใช้โอกาสใด ๆ โดยเฉพาะอย่างยิ่งหากมีวิธีการทำโดยไม่มีค่าใช้จ่ายสูง เป็นไปได้ที่จะสั่งซื้อข้อมูลในตารางที่ได้รับโดยการเพิ่มตัวTOP
ดำเนินการที่ไม่จำเป็น ตัวอย่างเช่น:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
นอกเหนือจากแบบสอบถามควรรับประกันว่าผลลัพธ์จะถูกส่งกลับในลำดับที่ถูกต้อง ฉันคาดว่าทุกประเภทจะมีผลกระทบด้านลบอย่างมาก เครื่องมือเพิ่มประสิทธิภาพข้อความค้นหาคาดหวังสิ่งนี้เช่นกันตามค่าใช้จ่ายโดยประมาณ:
น่าแปลกใจมากที่ฉันไม่สามารถสังเกตเห็นความแตกต่างอย่างมีนัยสำคัญทางสถิติในเวลา cpu หรือรันไทม์ที่มีหรือไม่มีการสั่งซื้อที่ชัดเจน หากมีสิ่งใดแบบสอบถามดูเหมือนว่าจะทำงานได้เร็วขึ้นด้วยORDER BY
! ฉันไม่มีคำอธิบายสำหรับพฤติกรรมนี้
ส่วนที่ยุ่งยากของปัญหาคือการหาวิธีแทรกตัวอักษรที่ว่างเปล่าในสถานที่ที่เหมาะสม ตามที่กล่าวไว้ก่อนง่าย ๆCROSS JOIN
จะส่งผลให้ข้อมูลที่ซ้ำกัน เรารู้ว่าสตริงที่ 100000000 จะมีความยาวหกตัวอักษรเพราะ:
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
แต่
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
ดังนั้นเราจะต้องเข้าร่วมกับตัวอักษร CTE หกครั้งเท่านั้น สมมติว่าเราเข้าร่วมกับ CTE หกครั้งคว้าจดหมายฉบับหนึ่งจากแต่ละ CTE และเชื่อมต่อพวกเขาทั้งหมดเข้าด้วยกัน สมมติว่าตัวอักษรซ้ายสุดไม่ว่างเปล่า หากตัวอักษรใด ๆ ที่ตามมาว่างเปล่านั่นหมายความว่าสตริงนั้นมีความยาวน้อยกว่าหกตัวอักษรดังนั้นจึงเป็นตัวอักษรที่ซ้ำกัน ดังนั้นเราสามารถป้องกันการซ้ำซ้อนโดยค้นหาอักขระที่ไม่ว่างตัวแรกและต้องการให้ทุกตัวอักษรหลังจากนั้นจะต้องไม่ว่างเปล่า ฉันเลือกที่จะติดตามสิ่งนี้โดยกำหนดFLAG
คอลัมน์ให้กับหนึ่งใน CTEs และเพิ่มการตรวจสอบให้กับWHERE
ข้อ ควรชัดเจนมากขึ้นหลังจากดูแบบสอบถาม แบบสอบถามสุดท้ายมีดังนี้:
WITH FIRST_CHAR (CHR) AS
(
SELECT 'A'
UNION ALL SELECT 'B'
UNION ALL SELECT 'C'
UNION ALL SELECT 'D'
UNION ALL SELECT 'E'
UNION ALL SELECT 'F'
UNION ALL SELECT 'G'
UNION ALL SELECT 'H'
UNION ALL SELECT 'I'
UNION ALL SELECT 'J'
UNION ALL SELECT 'K'
UNION ALL SELECT 'L'
UNION ALL SELECT 'M'
UNION ALL SELECT 'N'
UNION ALL SELECT 'O'
UNION ALL SELECT 'P'
UNION ALL SELECT 'Q'
UNION ALL SELECT 'R'
UNION ALL SELECT 'S'
UNION ALL SELECT 'T'
UNION ALL SELECT 'U'
UNION ALL SELECT 'V'
UNION ALL SELECT 'W'
UNION ALL SELECT 'X'
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
SELECT '', 0 CHR
UNION ALL SELECT 'A', 1
UNION ALL SELECT 'B', 1
UNION ALL SELECT 'C', 1
UNION ALL SELECT 'D', 1
UNION ALL SELECT 'E', 1
UNION ALL SELECT 'F', 1
UNION ALL SELECT 'G', 1
UNION ALL SELECT 'H', 1
UNION ALL SELECT 'I', 1
UNION ALL SELECT 'J', 1
UNION ALL SELECT 'K', 1
UNION ALL SELECT 'L', 1
UNION ALL SELECT 'M', 1
UNION ALL SELECT 'N', 1
UNION ALL SELECT 'O', 1
UNION ALL SELECT 'P', 1
UNION ALL SELECT 'Q', 1
UNION ALL SELECT 'R', 1
UNION ALL SELECT 'S', 1
UNION ALL SELECT 'T', 1
UNION ALL SELECT 'U', 1
UNION ALL SELECT 'V', 1
UNION ALL SELECT 'W', 1
UNION ALL SELECT 'X', 1
UNION ALL SELECT 'Y', 1
UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
CASE
WHEN d6.FLAG = 1 THEN 5
WHEN d5.FLAG = 1 THEN 4
WHEN d4.FLAG = 1 THEN 3
WHEN d3.FLAG = 1 THEN 2
WHEN d2.FLAG = 1 THEN 1
ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);
CTEs เป็นไปตามที่อธิบายไว้ข้างต้น ALL_CHAR
ถูกรวมเข้ากับห้าครั้งเนื่องจากมีแถวสำหรับอักขระว่าง ตัวละครสุดท้ายในสตริงไม่ควรจะว่างเปล่าดังนั้น CTE FIRST_CHAR
แยกต่างหากที่กำหนดไว้สำหรับมัน คอลัมน์ค่าสถานะพิเศษในALL_CHAR
ใช้เพื่อป้องกันการซ้ำซ้อนตามที่อธิบายไว้ข้างต้น อาจมีวิธีที่มีประสิทธิภาพมากกว่าในการตรวจสอบนี้ แต่มีวิธีที่ไม่มีประสิทธิภาพมากกว่าในการตรวจสอบ ความพยายามครั้งหนึ่งโดยฉันด้วยLEN()
และPOWER()
ทำให้คิวรีทำงานช้ากว่ารุ่นปัจจุบันถึงหกเท่า
MAXDOP 1
และFORCE ORDER
คำแนะนำที่เป็นสิ่งจำเป็นเพื่อให้แน่ใจว่าการสั่งซื้อจะถูกรักษาไว้ในแบบสอบถาม แผนประมาณหมายเหตุประกอบอาจมีประโยชน์เพื่อดูว่าทำไมการรวมอยู่ในลำดับปัจจุบัน:
แผนแบบสอบถามมักอ่านจากขวาไปซ้าย แต่คำขอแถวเกิดขึ้นจากซ้ายไปขวา ตามหลักการแล้ว SQL Server จะขอแถว 100 ล้านแถวจากd1
ผู้ให้บริการการสแกนคงที่ ในขณะที่คุณย้ายจากซ้ายไปขวาฉันคาดว่าจะขอแถวน้อยลงจากผู้ให้บริการแต่ละราย เราสามารถมองเห็นได้ในแผนการดำเนินการที่เกิดขึ้นจริง นอกจากนี้ด้านล่างเป็นภาพหน้าจอจาก SQL Sentry Plan Explorer:
เราได้ 100 ล้านแถวจาก d1 ซึ่งเป็นสิ่งที่ดี โปรดทราบว่าอัตราส่วนของแถวระหว่าง d2 และ d3 เกือบจะเท่ากับ 27: 1 (165336 * 27 = 4464072) ซึ่งสมเหตุสมผลถ้าคุณคิดว่า cross cross จะทำงานได้อย่างไร อัตราส่วนของแถวระหว่าง d1 และ d2 คือ 22.4 ซึ่งแสดงถึงงานที่สูญเปล่า ฉันเชื่อว่าแถวพิเศษนั้นมาจากการซ้ำซ้อน (เนื่องจากอักขระว่างอยู่ตรงกลางของสตริง) ซึ่งไม่ได้ผ่านโอเปอเรเตอร์การรวมลูปที่ซ้อนกันซึ่งทำหน้าที่กรอง
LOOP JOIN
คำใบ้ไม่จำเป็นเพราะในทางเทคนิคCROSS JOIN
เท่านั้นที่สามารถนำมาใช้เป็นห่วงเข้าร่วมใน SQL Server NO_PERFORMANCE_SPOOL
คือการป้องกันไม่ให้มีการจัดตารางที่ไม่จำเป็น การข้ามคำใบ้สปูลทำให้ข้อความค้นหาใช้เวลานานขึ้น 3X บนเครื่องของฉัน
ข้อความค้นหาสุดท้ายมีเวลา cpu ประมาณ 17 วินาทีและเวลาที่ผ่านไปทั้งหมด 18 วินาที นั่นคือเมื่อเรียกใช้แบบสอบถามผ่าน SSMS และยกเลิกชุดผลลัพธ์ ฉันสนใจที่จะเห็นวิธีการอื่นในการสร้างข้อมูล