สมมติว่า "ต้นทุน" เป็นแง่ของเวลา (แม้ว่าไม่แน่ใจว่ามันจะเป็นอะไรในแง่ของ ;-) จากนั้นอย่างน้อยที่สุดคุณควรจะเข้าใจมันด้วยการทำสิ่งต่อไปนี้:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
รายการแรกที่รายงานในแท็บ "ข้อความ" ควรเป็น:
SQL Server แยกวิเคราะห์และรวบรวมเวลา:
ฉันจะเรียกใช้อย่างน้อย 10 ครั้งและโดยเฉลี่ยทั้ง "CPU" และ "Elapsed" มิลลิวินาที
เป็นการดีที่คุณจะเรียกใช้ในการผลิตเพื่อให้คุณได้รับการประเมินเวลาจริง แต่ไม่ค่อยมีคนได้รับอนุญาตให้ล้างแคชแผนในการผลิต โชคดีที่การเริ่มต้นใน SQL Server 2008 เป็นไปได้ที่จะล้างแผนเฉพาะจากแคช ในกรณีนี้คุณสามารถทำสิ่งต่อไปนี้:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
อย่างไรก็ตามขึ้นอยู่กับความแปรปรวนของค่าที่ส่งผ่านสำหรับพารามิเตอร์ที่ก่อให้เกิดแผนการแคช "ไม่ดี" มีวิธีการอื่นที่จะพิจารณาว่าเป็นพื้นกลางระหว่างOPTION(RECOMPILE)
และOPTION(OPTIMIZE FOR UNKNOWN)
: SQL แบบไดนามิก ใช่ฉันพูดแล้ว และฉันยังหมายถึง Dynamic SQL แบบไม่มีพารามิเตอร์ นี่คือเหตุผล
คุณมีข้อมูลที่มีการแจกแจงที่ไม่สม่ำเสมออย่างชัดเจนอย่างน้อยก็ในแง่ของค่าพารามิเตอร์อินพุตอย่างน้อยหนึ่งค่า ข้อเสียของตัวเลือกที่กล่าวถึงคือ:
OPTION(RECOMPILE)
จะสร้างแผนสำหรับการดำเนินการทุกครั้งและคุณจะไม่สามารถได้รับประโยชน์จากการใช้ซ้ำแผนใด ๆแม้ว่าค่าพารามิเตอร์ที่ส่งผ่านอีกครั้งจะเหมือนกันกับการเรียกใช้ก่อนหน้านี้ สำหรับ procs ที่ถูกเรียกบ่อย ๆ - ทุกๆสองสามวินาทีหรือบ่อยครั้งขึ้นไป - สิ่งนี้จะช่วยคุณให้รอดพ้นจากสถานการณ์ที่น่ากลัวเป็นครั้งคราว แต่ก็ยังทำให้คุณอยู่ในสถานการณ์ที่ไม่ยิ่งใหญ่
OPTION(OPTIMIZE FOR (@Param = value))
จะสร้างแผนตามค่าเฉพาะนั้นซึ่งอาจช่วยได้หลายกรณี แต่ยังคงทำให้คุณเปิดรับปัญหาปัจจุบัน
OPTION(OPTIMIZE FOR UNKNOWN)
จะสร้างแผนตามจำนวนเงินที่จ่ายให้กับการแจกแจงเฉลี่ยซึ่งจะช่วยให้แบบสอบถามบางอย่าง แต่ทำอันตรายผู้อื่น นี่ควรเป็นตัวเลือกเดียวกับการใช้ตัวแปรโลคอล
อย่างไรก็ตาม Dynamic SQL เมื่อทำอย่างถูกต้องจะช่วยให้ค่าต่างๆที่ส่งผ่านไปมีแผนแบบสอบถามแยกต่างหากของพวกเขาที่เหมาะ (ดีเท่าที่พวกเขาจะ) ค่าใช้จ่ายหลักในที่นี้คือเมื่อความหลากหลายของค่าที่เพิ่มขึ้นจำนวนแผนดำเนินการในแคชเพิ่มขึ้นและใช้หน่วยความจำมากขึ้น ค่าใช้จ่ายเล็กน้อยคือ:
จำเป็นต้องตรวจสอบความถูกต้องของพารามิเตอร์สตริงเพื่อป้องกัน SQL Injections
อาจจำเป็นต้องตั้งค่าผู้ใช้ที่ใช้ใบรับรองและใบรับรองเพื่อรักษาความปลอดภัยที่เป็นนามธรรมเนื่องจาก Dynamic SQL ต้องการการอนุญาตตารางโดยตรง
ดังนั้นนี่คือวิธีที่ฉันจัดการสถานการณ์นี้เมื่อฉันมี procs ที่ถูกเรียกมากกว่าหนึ่งครั้งต่อวินาทีและกดหลายตารางแต่ละแถวมีล้านแถว ฉันพยายามOPTION(RECOMPILE)
แต่สิ่งนี้พิสูจน์แล้วว่าเป็นอันตรายต่อกระบวนการใน 99% ของกรณีที่ไม่มีปัญหาในการดมกลิ่นพารามิเตอร์ / แผนการแคชที่ไม่ดี และโปรดทราบว่าหนึ่งใน procs เหล่านี้มีคำค้นหาประมาณ 15 คำและมีเพียง 3 - 5 คนเท่านั้นที่ถูกแปลงเป็น Dynamic SQL ดังที่อธิบายไว้ที่นี่ Dynamic SQL ไม่ได้ถูกใช้ยกเว้นว่าจำเป็นสำหรับการสืบค้นเฉพาะ
หากมีพารามิเตอร์อินพุตจำนวนมากสำหรับโพรซีเดอร์ที่เก็บไว้ให้พิจารณาว่าอันไหนที่ใช้กับคอลัมน์ที่มีการแจกแจงข้อมูลที่แตกต่างกันอย่างมาก (และทำให้เกิดปัญหานี้) และอันไหนที่ใช้กับคอลัมน์ที่มีการแจกแจงที่เท่าเทียมกันมากขึ้น ทำให้เกิดปัญหานี้)
สร้างสตริง Dynamic SQL โดยใช้พารามิเตอร์สำหรับพารามิเตอร์อินพุต proc ที่เชื่อมโยงกับคอลัมน์แบบกระจาย การปรับพารามิเตอร์นี้ช่วยลดการเพิ่มขึ้นของแผนการดำเนินการในแคชที่เกี่ยวข้องกับแบบสอบถามนี้
สำหรับพารามิเตอร์ที่เหลือที่เกี่ยวข้องกับการแจกแจงที่แตกต่างกันอย่างมากพารามิเตอร์เหล่านั้นควรถูกรวมเข้ากับ Dynamic SQL เป็นค่าตามตัวอักษร ตั้งแต่แบบสอบถามที่ไม่ซ้ำกันจะถูกกำหนดโดยการเปลี่ยนแปลงใด ๆ ข้อความแบบสอบถามที่มีเป็นแบบสอบถามที่แตกต่างกันและด้วยเหตุนี้แผนแบบสอบถามที่แตกต่างกันกว่ามีWHERE StatusID = 1
WHERE StatusID = 2
หากพารามิเตอร์อินพุต proc ใด ๆ ที่จะต่อกันเป็นข้อความของเคียวรีนั้นเป็นสตริงดังนั้นพวกเขาจำเป็นต้องตรวจสอบความถูกต้องเพื่อป้องกัน SQL Injection (แม้ว่าจะมีโอกาสน้อยกว่าที่จะเกิดขึ้นหากสตริงที่ส่งผ่านถูกสร้างขึ้นโดย แอปและไม่ใช่ผู้ใช้ แต่ยังคงมี) อย่างน้อยทำREPLACE(@Param, '''', '''''')
เพื่อให้มั่นใจว่าคำพูดเดี่ยวจะกลายเป็นคำพูดเดียวที่หลบหนี
หากจำเป็นให้สร้างใบรับรองที่จะใช้ในการสร้างผู้ใช้และลงนามในขั้นตอนการจัดเก็บเช่นการอนุญาตตารางโดยตรงจะได้รับอนุญาตเฉพาะกับผู้ใช้ที่ใช้ใบรับรองใหม่เท่านั้นและไม่ให้[public]
หรือผู้ใช้ที่ไม่ควรมีสิทธิ์ดังกล่าว .
ตัวอย่าง proc:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;