使用sys.dm_exec_procedure_stats识别未使用的过程

时间:2018-08-11 15:23:55

标签: sql-server-2012

我有一个表,该表每年用sys.dm_exec_procedure_stats中的数据每小时更新一次。

如果我未在此表中看到该过程,我是否可以确保99%以上不再使用该过程?我的意思是,我知道在某些情况下可能会出现疯狂的情况,有人设计了一个进程来运行proc,然后立即将其从缓存中删除,因此我的进程永远不会记录它的使用情况。我显然愿意忽略那些愚蠢的边缘案例。

1 个答案:

答案 0 :(得分:1)

sys.dm_exec_procedure_stats DMV反映过程高速缓存的当前状态。删除相应的缓存条目后,SQL Server将从此DMV中删除条目,因此您可能会通过对此DMV进行定期快照而错过存储过程的执行。另外,不会捕获带有RECOMPILE的存储过程。

一种更可靠的方法来标识所有存储的proc执行,方法是使用写入文件目标的服务器端跟踪。然后可以将跟踪数据汇总并保存到表中。

下面是用于module_end事件的XE跟踪,支持SQL对象和PowerShell脚本的示例DDL。 PowerShell脚本从跟踪文件中汇总存储的proc执行,并将摘要数据保存到永久表中以进行分析。可以定期计划PS脚本以处理滚动跟踪文件。

USE YourDatabase;

CREATE EVENT SESSION [StoredProcedureExecutions] ON SERVER
ADD EVENT sqlserver.module_end(
    WHERE ([package0].[not_equal_uint64]([source_database_id],(32767)) AND [sqlserver].[equal_i_sql_ansi_string]([object_type],'P')))
ADD TARGET package0.event_file(SET filename=N'D:\SqlTraceFiles\StoredProcedureExecutions',max_file_size=(100),max_rollover_files=(5))
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON);

ALTER EVENT SESSION [StoredProcedureExecutions] ON SERVER 
     STATE=START;

CREATE TABLE dbo.ModuleEndSummaryStaging(
      source_database_id smallint NOT NULL
    , object_id int NOT NULL
    , object_name sysname NOT NULL
    , execution_count int NOT NULL
    , min_timestamp datetimeoffset NOT NULL
    , max_timestamp datetimeoffset NOT NULL
    CONSTRAINT PK_ModuleEndSummaryStaging PRIMARY KEY(
          source_database_id
        , object_id
        , object_name
    )
);
GO

CREATE TABLE dbo.StoredProcedureExecutionHistory(
      DatabaseName sysname NOT NULL
    , SchemaName sysname NOT NULL
    , ObjectName sysname NOT NULL
    , source_database_id smallint NOT NULL
    , object_id int NOT NULL
    , object_name sysname NOT NULL
    , ExecutionCount bigint
    , FirstExecutionTimestamp datetimeoffset NOT NULL
    , LastExecutionTimestamp datetimeoffset NOT NULL
    , CONSTRAINT PK_StoredProcedureExecutionHistory PRIMARY KEY (
          source_database_id
        , object_id
        , object_name
        , DatabaseName
        , SchemaName
        , ObjectName)
);
GO

CREATE OR ALTER PROCEDURE dbo.MergeStoredProcedureExecutionHistory
AS
SET NOCOUNT ON;
MERGE dbo.StoredProcedureExecutionHistory AS target
USING  (
    SELECT 
          source_database_id
        , object_id
        , object_name
        , execution_count
        , min_timestamp
        , max_timestamp
        , COALESCE(DB_NAME(source_database_id), N'') AS DatabaseName
        , COALESCE(OBJECT_SCHEMA_NAME(object_id, source_database_id), N'') AS SchemaName
        , COALESCE(OBJECT_NAME(object_id, source_database_id), N'') AS ObjectName
    FROM dbo.ModuleEndSummaryStaging
    ) AS source ON
        source.source_database_id = target.source_database_id
        AND source.object_id = target.object_id
        AND source.object_name = target.object_name
        AND source.DatabaseName = target.DatabaseName
        AND source.SchemaName = target.SchemaName
        AND source.ObjectName = target.ObjectName
WHEN MATCHED THEN
    UPDATE SET
          ExecutionCount += source.execution_count
        , FirstExecutionTimestamp = CASE WHEN source.min_timestamp < target.FirstExecutionTimestamp THEN source.min_timestamp ELSE target.FirstExecutionTimestamp END
        , LastExecutionTimestamp = CASE WHEN source.max_timestamp > target.LastExecutionTimestamp THEN source.max_timestamp ELSE target.LastExecutionTimestamp END
WHEN NOT MATCHED BY TARGET THEN
    INSERT (
          DatabaseName
        , SchemaName
        , ObjectName
        , source_database_id
        , object_id
        , object_name
        , ExecutionCount
        , FirstExecutionTimestamp
        , LastExecutionTimestamp
        )
    VALUES (
          source.DatabaseName
        , source.SchemaName
        , source.ObjectName
        , source.source_database_id
        , source.object_id
        , source.object_name
        , source.execution_count
        , source.min_timestamp
        , source.max_timestamp
        );
GO

下面是PS脚本示例。您需要修改系统的引用程序集路径(我使用的是安装有最新SSMS版本的系统)。

Add-Type -Path "C:\Program Files\Microsoft SQL Server\140\Shared\Microsoft.SqlServer.XE.Core.dll"
Add-Type -Path "C:\Program Files\Microsoft SQL Server\140\Shared\Microsoft.SqlServer.XEvent.Linq.dll"

# utility class to summarize proc calls by source_database_id, object_id, and object_name
Add-Type -TypeDefinition `
@"
using System;
using System.Collections.Generic;
using System.Text;

namespace ExtendedEventsUtility
{
    public static class ExtendedEventsAggegator
    {

        public static Dictionary<string, ModuleEndSummary> AggregatedEvents = new Dictionary<string, ModuleEndSummary>();

        public static void AggregateTraceFiles(string xeFilePathPattern)
        {
            AggregatedEvents.Clear();
            using (var events = new Microsoft.SqlServer.XEvent.Linq.QueryableXEventData(xeFilePathPattern))
            {
                foreach (var xe in events)
                {
                    ExtendedEventsAggegator.aggregateEvent(xe);
                }
            }
        }

        private static void aggregateEvent(Microsoft.SqlServer.XEvent.Linq.PublishedEvent eventData)
        {
            ModuleEndSummary aggregatedEvent;
            var key = new StringBuilder();
            key.Append(eventData.Fields["source_database_id"].Value.ToString());
            key.Append("|");
            key.Append(eventData.Fields["object_id"].Value.ToString());
            key.Append("|");
            key.Append(eventData.Fields["object_name"].Value.ToString());
            var keyValue = key.ToString();
            if (AggregatedEvents.ContainsKey(keyValue))
            {
                aggregatedEvent = AggregatedEvents[keyValue];
            }
            else
            {
                aggregatedEvent = new ModuleEndSummary()
                {
                    source_database_id = (UInt32)eventData.Fields["source_database_id"].Value,
                    object_id = (Int32)eventData.Fields["object_id"].Value,
                    object_name = (string)eventData.Fields["object_name"].Value
                };
                AggregatedEvents.Add(keyValue, aggregatedEvent);
            }
            aggregatedEvent.executionCount += 1;
            if((DateTimeOffset)eventData.Timestamp < aggregatedEvent.minTimestamp)
            {
                aggregatedEvent.minTimestamp = (DateTimeOffset)eventData.Timestamp;
            }
            if ((DateTimeOffset)eventData.Timestamp > aggregatedEvent.maxTimestamp)
            {
                aggregatedEvent.maxTimestamp = (DateTimeOffset)eventData.Timestamp;
            }

        }

    }

    public class ModuleEndSummary
    {
        public UInt32 source_database_id;
        public Int32 object_id;
        public string object_name;
        public Int32 executionCount = 0;
        public DateTimeOffset minTimestamp = DateTimeOffset.MaxValue;
        public DateTimeOffset maxTimestamp = DateTimeOffset.MinValue;
    }
}
"@ -ReferencedAssemblies ("C:\Program Files\Microsoft SQL Server\140\Shared\Microsoft.SqlServer.XE.Core.dll", "C:\Program Files\Microsoft SQL Server\140\Shared\Microsoft.SqlServer.XEvent.Linq.dll")

try {

    # move trace files that are not currently in use to import staging subfolder
    $sourceTraceFolderPath = "D:\SqlTraceFiles\"
    $targetTraceSubFolderPath = "D:\SqlTraceFiles\ImportStaging\"
    $traceFilePattern = "StoredProcedureExecutions*.xel"
    if(!(Test-Path $targetTraceSubFolderPath)) {
        [void](New-Item -Path $targetTraceSubFolderPath -ItemType Directory)
    }
    Get-Item "$sourceTraceFolderPath\$traceFilePattern" | Move-Item -Destination $targetTraceSubFolderPath -ErrorAction Ignore

    # aggegate usage by source_database_id, object_id, and object_name
    [ExtendedEventsUtility.ExtendedEventsAggegator]::AggregateTraceFiles("$targetTraceSubFolderPath\$traceFilePattern")

    # create data table for SqlBulkCopy
    $dt = New-Object System.Data.DataTable
    [void]$dt.Columns.Add("source_database_id", [System.Type]::GetType("System.Int16"))
    [void]$dt.Columns.Add("object_id", [System.Type]::GetType("System.Int32"))
    [void]$dt.Columns.Add("object_name", [System.Type]::GetType("System.String"))
    [void]$dt.Columns.Add("execution_count", [System.Type]::GetType("System.Int32"))
    [void]$dt.Columns.Add("min_timestamp", [System.Type]::GetType("System.DateTimeOffset"))
    [void]$dt.Columns.Add("max_timestamp", [System.Type]::GetType("System.DateTimeOffset"))

    # load proc execution summary into data table
    foreach ($proc in [ExtendedEventsUtility.ExtendedEventsAggegator]::AggregatedEvents.Values) {
        $row = $dt.NewRow()
        $dt.Rows.Add($row)
        $row["source_database_id"] = $proc.source_database_id
        $row["object_id"] = $proc.object_id
        $row["object_name"] = $proc.object_name
        $row["execution_count"] = $proc.executioncount
        $row["min_timestamp"] = $proc.mintimestamp
        $row["max_timestamp"] = $proc.maxtimestamp
    }

    # bulk insert execution summary into staging table
    $connectionString = "Data Source=.;Integrated Security=SSPI;Initial Catalog=YourDatabase"
    $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
    $command = New-Object System.Data.SqlClient.SqlCommand("TRUNCATE TABLE dbo.ModuleEndSummaryStaging;", $connection)
    $connection.Open()
    [void]$command.ExecuteNonQuery()
    $connection.Close()
    $bcp = New-Object System.Data.SqlClient.SqlBulkCopy($connectionString)
    $bcp.DestinationTableName = "dbo.ModuleEndSummaryStaging"
    $bcp.WriteToServer($dt);
    $bcp.Dispose()

    # merge proc execution summary into history table
    $connection.Open()
    $command.CommandText="dbo.MergeStoredProcedureExecutionHistory"
    $command.CommandType = [System.Data.CommandType]::StoredProcedure
    [void]$command.ExecuteNonQuery()
    [void]$connection.Close()

    #delete files after import
    Get-ChildItem "$targetTraceSubFolderPath\$traceFilePattern" | Remove-Item

} catch {
    throw
}