@@ -62,6 +62,8 @@ ALTER PROCEDURE
6262 @minimum_execution_count bigint = 2 , /* noise floor for single-exec queries*/
6363 @ignore_system_databases bit = 1 , /* exclude master, model, msdb, tempdb*/
6464 @impact_threshold decimal (3 , 2 ) = 0 .50 , /* minimum impact_score to surface (0.00-1.00)*/
65+ @find_single_use_plans bit = 0 , /* show single-use plans consuming the most memory*/
66+ @find_duplicate_plans bit = 0 , /* show query hashes with multiple cached plans*/
6567 @debug bit = 0 , /* print diagnostics*/
6668 @help bit = 0 , /* display parameter help*/
6769 @version varchar (30 ) = NULL OUTPUT , /* OUTPUT; for support*/
@@ -122,6 +124,10 @@ BEGIN
122124 THEN N ' exclude system databases (master, model, msdb, tempdb)'
123125 WHEN N ' @impact_threshold'
124126 THEN N ' minimum impact_score to surface in results'
127+ WHEN N ' @find_single_use_plans'
128+ THEN N ' show single-use plans consuming the most memory'
129+ WHEN N ' @find_duplicate_plans'
130+ THEN N ' show query hashes with multiple cached plans'
125131 WHEN N ' @debug'
126132 THEN N ' print diagnostic information'
127133 WHEN N ' @help'
@@ -150,6 +156,10 @@ BEGIN
150156 THEN N ' 0 or 1'
151157 WHEN N ' @impact_threshold'
152158 THEN N ' 0.00 to 1.00'
159+ WHEN N ' @find_single_use_plans'
160+ THEN N ' 0 or 1'
161+ WHEN N ' @find_duplicate_plans'
162+ THEN N ' 0 or 1'
153163 WHEN N ' @debug'
154164 THEN N ' 0 or 1'
155165 WHEN N ' @help'
@@ -178,6 +188,10 @@ BEGIN
178188 THEN N ' 1'
179189 WHEN N ' @impact_threshold'
180190 THEN N ' 0.50'
191+ WHEN N ' @find_single_use_plans'
192+ THEN N ' 0'
193+ WHEN N ' @find_duplicate_plans'
194+ THEN N ' 0'
181195 WHEN N ' @debug'
182196 THEN N ' 0'
183197 WHEN N ' @help'
@@ -326,6 +340,134 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
326340 RETURN ;
327341 END ;
328342
343+ /*
344+ ╔══════════════════════════════════════════════════╗
345+ ║ Single-use plans mode ║
346+ ╚══════════════════════════════════════════════════╝
347+
348+ Shows the largest single-use plans by cached size,
349+ sorted by memory consumption descending.
350+ */
351+ IF @find_single_use_plans = 1
352+ BEGIN
353+ SELECT TOP (@top)
354+ database_name =
355+ DB_NAME (CONVERT (integer , pa .value )),
356+ cached_plan_size_kb =
357+ cp .size_in_bytes / 1024 ,
358+ query_text =
359+ st .text ,
360+ query_plan =
361+ CASE
362+ WHEN TRY_CAST (qp .query_plan AS xml ) IS NOT NULL
363+ THEN TRY_CAST (qp .query_plan AS xml )
364+ WHEN TRY_CAST (qp .query_plan AS xml ) IS NULL
365+ THEN
366+ (
367+ SELECT
368+ [processing-instruction(query_plan)] =
369+ N ' -- ' + NCHAR (13 ) + NCHAR (10 ) +
370+ N ' -- This is a huge query plan.' + NCHAR (13 ) + NCHAR (10 ) +
371+ N ' -- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR (13 ) + NCHAR (10 ) +
372+ NCHAR (13 ) + NCHAR (10 ) +
373+ REPLACE (qp .query_plan , N ' <RelOp' , NCHAR (13 ) + NCHAR (10 ) + N ' <RelOp' ) +
374+ NCHAR (13 ) + NCHAR (10 ) COLLATE Latin1_General_Bin2
375+ FOR
376+ XML
377+ PATH (N ' ' ),
378+ TYPE
379+ )
380+ END ,
381+ qs .query_hash ,
382+ qs .query_plan_hash ,
383+ qs .creation_time ,
384+ qs .last_execution_time ,
385+ qs .sql_handle ,
386+ qs .plan_handle
387+ FROM sys .dm_exec_query_stats AS qs
388+ JOIN sys .dm_exec_cached_plans AS cp
389+ ON cp .plan_handle = qs .plan_handle
390+ CROSS APPLY
391+ (
392+ SELECT TOP (1 )
393+ value = pa .value
394+ FROM sys .dm_exec_plan_attributes (qs .plan_handle ) AS pa
395+ WHERE pa .attribute = N ' dbid'
396+ ) AS pa
397+ CROSS APPLY sys .dm_exec_sql_text (qs .sql_handle ) AS st
398+ OUTER APPLY sys .dm_exec_text_query_plan
399+ (
400+ qs .plan_handle ,
401+ qs .statement_start_offset ,
402+ qs .statement_end_offset
403+ ) AS qp
404+ WHERE qs .execution_count = 1
405+ AND (@ignore_system_databases = 0 OR ISNULL (CONVERT (integer , pa .value ), 0 ) NOT IN (1 , 2 , 3 , 4 ))
406+ AND ISNULL (CONVERT (integer , pa .value ), 0 ) < 32761
407+ AND (@database_id IS NULL OR CONVERT (integer , pa .value ) = @database_id)
408+ ORDER BY
409+ cp .size_in_bytes DESC
410+ OPTION (RECOMPILE , MAXDOP 1 );
411+
412+ RETURN ;
413+ END ;
414+
415+ /*
416+ ╔══════════════════════════════════════════════════╗
417+ ║ Duplicate plans mode ║
418+ ╚══════════════════════════════════════════════════╝
419+
420+ Shows query hashes that have been compiled into
421+ multiple cached plans, sorted by plan count descending.
422+ */
423+ IF @find_duplicate_plans = 1
424+ BEGIN
425+ SELECT TOP (@top)
426+ database_name =
427+ DB_NAME (CONVERT (integer , MAX (pa .value ))),
428+ qs .query_hash ,
429+ plan_count =
430+ COUNT_BIG (DISTINCT qs .plan_handle ),
431+ total_executions =
432+ SUM (qs .execution_count ),
433+ total_cpu_ms =
434+ SUM (qs .total_worker_time ) / 1000 .0 ,
435+ total_logical_reads =
436+ SUM (qs .total_logical_reads ),
437+ total_cached_size_kb =
438+ SUM (cp .size_in_bytes ) / 1024 ,
439+ oldest_plan =
440+ MIN (qs .creation_time ),
441+ newest_plan =
442+ MAX (qs .creation_time ),
443+ sample_query_text =
444+ MAX (st .text )
445+ FROM sys .dm_exec_query_stats AS qs
446+ JOIN sys .dm_exec_cached_plans AS cp
447+ ON cp .plan_handle = qs .plan_handle
448+ CROSS APPLY
449+ (
450+ SELECT TOP (1 )
451+ value = pa .value
452+ FROM sys .dm_exec_plan_attributes (qs .plan_handle ) AS pa
453+ WHERE pa .attribute = N ' dbid'
454+ ) AS pa
455+ CROSS APPLY sys .dm_exec_sql_text (qs .sql_handle ) AS st
456+ WHERE qs .query_hash <> 0x0000000000000000
457+ AND (@ignore_system_databases = 0 OR ISNULL (CONVERT (integer , pa .value ), 0 ) NOT IN (1 , 2 , 3 , 4 ))
458+ AND ISNULL (CONVERT (integer , pa .value ), 0 ) < 32761
459+ AND (@database_id IS NULL OR CONVERT (integer , pa .value ) = @database_id)
460+ GROUP BY
461+ qs .query_hash
462+ HAVING
463+ COUNT_BIG (DISTINCT qs .plan_handle ) > 1
464+ ORDER BY
465+ COUNT_BIG (DISTINCT qs .plan_handle ) DESC
466+ OPTION (RECOMPILE , MAXDOP 1 );
467+
468+ RETURN ;
469+ END ;
470+
329471 /*
330472 ╔══════════════════════════════════════════════════╗
331473 ║ Plan cache health analysis ║
@@ -537,7 +679,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
537679 WHERE pa .attribute = N ' dbid'
538680 ) AS pa
539681 WHERE pa .value IS NOT NULL
540- AND CONVERT (integer , pa .value ) NOT IN (1 , 2 , 3 , 4 )
682+ AND (@ignore_system_databases = 0 OR CONVERT (integer , pa .value ) NOT IN (1 , 2 , 3 , 4 ) )
541683 AND CONVERT (integer , pa .value ) < 32761
542684 AND (@database_id IS NULL OR CONVERT (integer , pa .value ) = @database_id)
543685 GROUP BY
@@ -646,7 +788,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
646788 WHERE pa .attribute = N ' dbid'
647789 ) AS pa
648790 WHERE pa .value IS NOT NULL
649- AND CONVERT (integer , pa .value ) NOT IN (1 , 2 , 3 , 4 )
791+ AND (@ignore_system_databases = 0 OR CONVERT (integer , pa .value ) NOT IN (1 , 2 , 3 , 4 ) )
650792 AND CONVERT (integer , pa .value ) < 32761
651793 AND (@database_id IS NULL OR CONVERT (integer , pa .value ) = @database_id)
652794 GROUP BY
0 commit comments