เครื่องมือเพิ่มประสิทธิภาพการสืบค้น SQL Server สามารถรวมค่าจากการคำนวณซ้ำ ๆ เข้ากับตัวดำเนินการ Scalar ของ Compute เดียว การทำเช่นนี้จะขึ้นอยู่กับการวางแผนการคิดต้นทุนและคุณสมบัติของมูลค่าที่คำนวณได้หรือไม่ เป็นที่คาดหวังว่ามันจะไม่ทำเช่นนี้สำหรับค่าที่คำนวณซึ่งเป็น nondeterministic RAND()ซึ่งข้อยกเว้นบางประการเช่น มันจะไม่ทำสิ่งนี้สำหรับฟังก์ชั่นที่ผู้ใช้กำหนด
ฉันจะเริ่มต้นด้วยตัวอย่างฟังก์ชันที่ผู้ใช้กำหนด นี่คือตัวอย่างที่ยอดเยี่ยมของฟังก์ชั่นที่ผู้ใช้กำหนด:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
ฉันต้องการสร้างตารางและใส่ 100 แถวในนั้น:
CREATE TABLE X_100 (N BIGINT NOT NULL);
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;
dbo.NULL_FUNCTIONฟังก์ชั่น determistic จะมีการดำเนินการกี่ครั้งสำหรับคำค้นหาต่อไปนี้
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
ขึ้นอยู่กับแผนแบบสอบถามนี้จะถูกดำเนินการหนึ่งครั้งสำหรับแต่ละแถวหรือ 100 ครั้ง:

SQL Server 2016 แนะนำsys.dm_exec_function_stats DMV เราสามารถถ่ายภาพสแนปชอตของ DMV นั้นเพื่อดูจำนวนครั้งที่ UDF ถูกเรียกใช้งานโดยเคียวรี
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
ผลลัพธ์ที่ได้คือ 100 ดังนั้นฟังก์ชันจึงถูกดำเนินการ 100 ครั้ง
ลองทำแบบสอบถามง่ายๆอีก:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
แผนแบบสอบถามแสดงให้เห็นว่าฟังก์ชั่นจะถูกดำเนินการ 200 ครั้ง:

ผลของการsys.dm_exec_function_statsแนะนำให้ฟังก์ชั่นถูกดำเนินการ 200 ครั้ง
โปรดทราบว่าคุณไม่สามารถใช้แผนคิวรีเพื่อคำนวณจำนวนสเกลาร์คำนวณได้เสมอ ข้อความต่อไปนี้มาจาก " คำนวณสเกลาร์นิพจน์และประสิทธิภาพของแผนปฏิบัติการ ":
สิ่งนี้ทำให้ผู้คนคิดว่า Compute Scalar ทำตัวเหมือนตัวดำเนินการส่วนใหญ่: เมื่อแถวไหลผ่านมันผลลัพธ์ของการคำนวณอะไรก็ตามที่ Compute Scalar บรรจุอยู่จะถูกเพิ่มลงในสตรีม สิ่งนี้ไม่เป็นความจริง แม้จะมีชื่อ Compute Scalar ไม่คำนวณอะไรเสมอไปและไม่ได้มีค่าสเกลาร์เดียวเสมอไป (อาจเป็นเวกเตอร์นามแฝงหรือแม้แต่คำกริยาบูลีนเป็นต้น) บ่อยครั้งที่การคำนวณสเกลาร์คำนวณการแสดงออกเพียงอย่างเดียว การคำนวณจริงจะถูกเลื่อนออกไปจนกว่าจะมีบางสิ่งในภายหลังในแผนการดำเนินการที่ต้องการผลลัพธ์
ลองอีกตัวอย่างหนึ่ง สำหรับเคียวรี่ต่อไปนี้ฉันหวังว่า UDF จะถูกคำนวณหนึ่งครั้ง:
WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;
แผนแบบสอบถามแสดงให้เห็นว่าจะมีการคำนวณหนึ่งครั้ง:

อย่างไรก็ตาม DMV เปิดเผยความจริง สเกลาร์คำนวณถูกเลื่อนออกไปจนกว่าจะมีความจำเป็นซึ่งอยู่ในตัวดำเนินการเข้าร่วม มันถูกประเมิน 100 ครั้ง
คุณถามว่าคุณสามารถทำอะไรได้บ้างเพื่อกระตุ้นให้เครื่องมือเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงการคำนวณนิพจน์เดิมซ้ำหลายครั้ง สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือหลีกเลี่ยงการใช้สเกลาร์ UDF ในรหัสของคุณ สิ่งเหล่านี้มีปัญหาด้านประสิทธิภาพจำนวนหนึ่งนอกเหนือจากคำถามนี้รวมถึงการขยายหน่วยความจำที่ได้รับมอบหมายบังคับให้เคียวรีทั้งหมดรันด้วยการMAXDOP 1ประมาณค่า cardinality ที่ไม่ดีและนำไปสู่การใช้งาน CPU เพิ่มเติม หากคุณจำเป็นต้องใช้ UDF และค่าของ UDF นั้นเป็นค่าคงที่คุณสามารถคำนวณนอกแบบสอบถามและเพื่อวางไว้ในตัวแปรท้องถิ่น
สำหรับข้อความค้นหาที่ไม่มี UDF คุณสามารถพยายามหลีกเลี่ยงการเขียนนิพจน์ที่ส่งคืนผลลัพธ์เดียวกัน แต่ไม่ได้พิมพ์ด้วยวิธีเดียวกัน สำหรับตัวอย่างต่อไปนี้ฉันใช้ฐานข้อมูล AdventureworksDW2016CTP3 ที่เปิดเผยต่อสาธารณชน แต่จริงๆแล้วฐานข้อมูลใด ๆ จะทำ จะCOUNT(*)มีการคำนวณคำค้นหานี้กี่ครั้ง
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
สำหรับคำค้นหานี้เราสามารถหาคำตอบได้โดยดูที่โอเปอเรเตอร์แฮช (รวม)

คำนวณครั้งเดียวสำหรับแต่ละค่าที่เป็นเอกลักษณ์ของCOUNT(*) OrderDateKeyการรวมส่วนORDER BYคำสั่งไม่ทำให้เกิดการคำนวณสองครั้ง คุณสามารถดูแผนปฏิบัติการที่นี่
ตอนนี้ให้พิจารณาคำถามที่จะส่งคืนผลลัพธ์ที่แน่นอนเหมือนกัน แต่ถูกเขียนด้วยวิธีที่แตกต่าง:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
เครื่องมือเพิ่มประสิทธิภาพการสืบค้นไม่ฉลาดพอที่จะรวมเข้าด้วยกันดังนั้นการทำงานเพิ่มเติมจึงจะเสร็จสิ้น:
