แคช SQL Server คำนวณค่าในแบบสอบถามหรือไม่


10

ทุกครั้งที่ฉันเรียกใช้แบบสอบถามชนิดนี้ฉันมักจะสงสัยว่า SQL Server ทำงานอย่างไร ถ้าฉันเรียกใช้แบบสอบถามชนิดใดก็ได้ที่ต้องการการคำนวณแล้วใช้ค่านั้นในหลาย ๆ ที่ตัวอย่างเช่นในselectและorder bySQL Server จะคำนวณมันสองครั้งสำหรับทุกแถวหรือจะถูกแคชหรือไม่ นอกจากนี้วิธีการทำงานกับฟังก์ชั่นที่ผู้ใช้กำหนด?

ตัวอย่าง:

SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc

SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table

SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)

มีวิธีทำให้มีประสิทธิภาพมากขึ้นหรือ SQL Server ฉลาดพอที่จะจัดการให้ฉันได้หรือไม่?


"มันขึ้นอยู่กับ" นี่คืองานนิทรรศการหนึ่งรายการrextester.com/DXOB90032
Martin Smith

ซึ่งคุณสามารถเปรียบเทียบกับrextester.com/ARSO25902
Martin Smith

@MartinSmith คุณไม่ได้ใช้ฟังก์ชันที่ไม่ได้กำหนดไว้ใช่หรือไม่ ถ้าเป็นเช่นนั้นฉันคาดว่า SQL จะรันสองครั้ง
Jonas Stawski

มีข้อยกเว้นเสมอ! คุณสามารถลองSELECT RAND() FROM Sales order by RAND()- นี่เป็นเพียงการประเมินผลเพียงครั้งเดียวเนื่องจากเป็นทั้งค่าที่ไม่ได้กำหนดค่าและค่าคงที่เวลาทำงาน
Martin Smith

คำตอบ:


11

เครื่องมือเพิ่มประสิทธิภาพการสืบค้น 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 ครั้ง:

แผนแบบสอบถาม 1

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 ครั้ง:

แผนแบบสอบถาม 2

ผลของการ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;

เครื่องมือเพิ่มประสิทธิภาพการสืบค้นไม่ฉลาดพอที่จะรวมเข้าด้วยกันดังนั้นการทำงานเพิ่มเติมจึงจะเสร็จสิ้น:

แฮชคู่ที่ 2

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