ในท้ายที่สุดมันเป็นไปไม่ได้ที่จะบังคับให้ SQL Server ประเมินค่าสเกลาร์ UDF เพียงครั้งเดียวในแบบสอบถาม อย่างไรก็ตามมีบางขั้นตอนที่สามารถดำเนินการเพื่อสนับสนุนมัน จากการทดสอบเราเชื่อว่าคุณจะได้รับสิ่งที่ทำงานกับ SQL Server รุ่นปัจจุบัน แต่เป็นไปได้ว่าการเปลี่ยนแปลงในอนาคตจะทำให้คุณต้องทบทวนรหัสของคุณอีกครั้ง
หากเป็นไปได้ที่จะแก้ไขโค้ดสิ่งแรกที่ควรลองคือการทำให้ฟังก์ชั่นกำหนดค่าได้ถ้าเป็นไปได้ Paul White ชี้ให้เห็นที่นี่ว่าฟังก์ชั่นจะต้องสร้างขึ้นด้วยSCHEMABINDING
ตัวเลือกและรหัสฟังก์ชั่นจะต้องกำหนดขึ้น
หลังจากทำการเปลี่ยนแปลงต่อไปนี้:
CREATE OR ALTER FUNCTION dbo.EXPENSIVE_UDF () RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
DECLARE @tbl TABLE (VAL VARCHAR(5));
-- make the function expensive to call
INSERT INTO @tbl
SELECT [VALUE]
FROM STRING_SPLIT(REPLICATE(CAST('Z ' AS VARCHAR(MAX)), 20000), ' ');
RETURN 1;
END;
แบบสอบถามจากคำถามจะถูกดำเนินการใน 64 ms:
SELECT x1.ID
FROM dbo.X_100_INTEGERS x1
WHERE x1.ID >= dbo.EXPENSIVE_UDF();
แผนแบบสอบถามไม่มีตัวดำเนินการตัวกรองอีกต่อไป:
เพื่อให้แน่ใจว่าดำเนินการเพียงครั้งเดียวเราสามารถใช้sys.dm_exec_function_stats DMV ใหม่ที่วางจำหน่ายใน SQL Server 2016:
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('EXPENSIVE_UDF', 'FN');
การออกALTER
ฟังก์ชันจะเป็นการรีเซ็ตexecution_count
สำหรับวัตถุนั้น แบบสอบถามด้านบนส่งคืน 1 ซึ่งหมายถึงฟังก์ชั่นถูกดำเนินการเพียงครั้งเดียว
โปรดทราบว่าเพียงเพราะฟังก์ชั่นที่กำหนดไว้ไม่ได้หมายความว่ามันจะถูกประเมินเพียงครั้งเดียวสำหรับแบบสอบถามใด ๆ ในความเป็นจริงการเพิ่มข้อความค้นหาบางอย่างSCHEMABINDING
อาจทำให้ประสิทธิภาพลดลง พิจารณาคำถามต่อไปนี้:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
เพิ่มความไม่จำเป็นDISTINCT
เพื่อกำจัดตัวดำเนินการตัวกรอง แผนดูมีแนวโน้ม:
จากนั้นจะคาดว่า UDF จะได้รับการประเมินหนึ่งครั้งและจะใช้เป็นตารางด้านนอกในการเข้าร่วมลูปที่ซ้อนกัน อย่างไรก็ตามแบบสอบถามใช้เวลา 6446 ms ในการทำงานบนเครื่องของฉัน ตามsys.dm_exec_function_stats
ฟังก์ชั่นถูกดำเนินการ 100 ครั้ง เป็นไปได้อย่างไร? ใน " Compute Scalars, Expression and Execution Plan Performance ", Paul White ชี้ให้เห็นว่าตัวดำเนินการคำนวณ Scalar สามารถเลื่อนออกได้:
บ่อยครั้งที่การคำนวณสเกลาร์คำนวณการแสดงออกเพียงอย่างเดียว การคำนวณจริงจะถูกเลื่อนออกไปจนกว่าจะมีบางสิ่งในภายหลังในแผนการดำเนินการที่ต้องการผลลัพธ์
สำหรับเคียวรี่นี้ดูเหมือนว่าการโทร UDF จะถูกเลื่อนออกไปจนกว่าจะมีความจำเป็น ณ จุดนี้มันถูกประเมิน 100 ครั้ง
น่าสนใจตัวอย่าง CTE รันใน 71 ms บนเครื่องของฉันเมื่อไม่ได้กำหนด UDF ด้วยSCHEMABINDING
เช่นเดียวกับคำถามเดิม ฟังก์ชั่นจะดำเนินการเพียงครั้งเดียวเมื่อมีการเรียกใช้แบบสอบถาม นี่คือแผนแบบสอบถามสำหรับที่:
ยังไม่ชัดเจนว่าทำไม Compute Scalar จึงไม่ถูกเลื่อนออกไป อาจเป็นเพราะ nondeterminism ของฟังก์ชัน จำกัด การจัดเรียงตัวดำเนินการใหม่ที่ตัวเพิ่มประสิทธิภาพการสืบค้นสามารถทำได้
อีกวิธีหนึ่งคือการเพิ่มตารางเล็ก ๆ ลงใน CTE และเพื่อค้นหาแถวเดียวในตารางนั้น โต๊ะเล็ก ๆ จะทำอะไร แต่ลองใช้สิ่งต่อไปนี้:
CREATE TABLE dbo.X_ONE_ROW_TABLE (ID INT NOT NULL);
INSERT INTO dbo.X_ONE_ROW_TABLE VALUES (1);
แบบสอบถามจะกลายเป็น:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
FROM dbo.X_ONE_ROW_TABLE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
การdbo.X_ONE_ROW_TABLE
เพิ่มความไม่แน่นอนเพิ่มขึ้นสำหรับเครื่องมือเพิ่มประสิทธิภาพ ถ้าตารางมีศูนย์แถว CTE จะส่งคืน 0 แถว ไม่ว่าในกรณีใดเครื่องมือเพิ่มประสิทธิภาพไม่สามารถรับประกันได้ว่า CTE จะส่งคืนหนึ่งแถวหาก UDF ไม่ได้กำหนดไว้ดังนั้นจึงดูเหมือนว่า UDF จะได้รับการประเมินก่อนเข้าร่วม ฉันคาดว่าเครื่องมือเพิ่มประสิทธิภาพในการสแกนdbo.X_ONE_ROW_TABLE
ใช้สตรีมมวลรวมเพื่อรับค่าสูงสุดของแถวที่ส่งคืนหนึ่งแถว (ซึ่งต้องใช้ฟังก์ชันที่ต้องประเมินผล) และใช้เป็นตารางด้านนอกสำหรับการวนซ้ำซ้อนรวมdbo.X_100_INTEGERS
ในแบบสอบถามหลัก . สิ่งนี้ดูเหมือนจะเป็นสิ่งที่เกิดขึ้น :
รันแบบสอบถามในประมาณ 110 มิลลิวินาทีในเครื่องของฉันและ UDF sys.dm_exec_function_stats
จะถูกประเมินเพียงครั้งเดียวตาม มันจะไม่ถูกต้องที่จะบอกว่าเครื่องมือเพิ่มประสิทธิภาพแบบสอบถามถูกบังคับให้ประเมิน UDF เพียงครั้งเดียว อย่างไรก็ตามมันเป็นเรื่องยากที่จะจินตนาการว่าเครื่องมือเพิ่มประสิทธิภาพการเขียนใหม่ที่จะนำไปสู่การสอบถามราคาที่ต่ำกว่าแม้จะมีข้อ จำกัด เกี่ยวกับ UDF และการคำนวณต้นทุนสเกลาร์
โดยสรุปสำหรับฟังก์ชั่นที่กำหนดขึ้นเอง (ซึ่งต้องมีSCHEMABINDING
ตัวเลือก) ให้ลองเขียนคำถามด้วยวิธีที่ง่ายที่สุดเท่าที่จะทำได้ หากใน SQL Server 2016 หรือรุ่นที่ใหม่กว่าให้ยืนยันว่าฟังก์ชั่นนั้นใช้งานได้เพียงครั้งเดียวsys.dm_exec_function_stats
เท่านั้น แผนการดำเนินการอาจทำให้เข้าใจผิดในเรื่องนั้น
สำหรับฟังก์ชั่นที่ไม่ได้รับการพิจารณาโดย SQL Server ให้กำหนดค่าได้รวมถึงสิ่งใดก็ตามที่ไม่มีSCHEMABINDING
ตัวเลือกแนวทางหนึ่งคือใส่ UDF ใน CTE หรือตารางที่ได้รับการออกแบบมาอย่างระมัดระวัง สิ่งนี้ต้องการการดูแลเพียงเล็กน้อย แต่ CTE เดียวกันสามารถทำงานได้ทั้งฟังก์ชั่นที่กำหนดไว้ล่วงหน้าและไม่ระบุชื่อ