Vorwort
Früher oder später möchte ein DB-Administrator einen Leistungsindikator für SQL-Server-Abfragen haben. Wie wir alle wissen, führt eine 24-stündige Ausführung des Profilers zu einer erheblichen Systembelastung und kann daher nicht als optimale Lösung für Datenbanken angesehen werden, die im 24/7-Modus verwendet werden.
Wie können wir also den Status von SQL Server-Abfragen erkennen? Wie können wir eine Ablaufverfolgung für erkannte abfragebezogene Probleme ohne menschliche Eingabe ausführen?
In diesem Artikel werde ich eine Implementierung des SQL Server-Leistungsindikators für Abfragen, gespeicherte Prozeduren und Trigger sowie seine Verwendung für den Trace-Lauf bereitstellen.
Lösung
Lassen Sie uns zunächst einen Blick auf den allgemeinen Ansatz zur Implementierung des Leistungsindikators für Abfragen, gespeicherte Prozeduren und Trigger werfen:
- Erstellung der erforderlichen Tabellen für die Informationssammlung und -analyse.
- Erstellung einer Ansicht zur Informationssammlung.
- Erstellung gespeicherter Prozeduren zur Informationssammlung.
- Erstellung einer Ansicht zur Informationsausgabe.
Betrachten wir nun die Implementierung:
1. Erstellung der erforderlichen Tabellen für die Informationssammlung und -analyse.
1.1. Für Anfragen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [srv].[SQL_StatementExecStat](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[InsertDate] [datetime] NULL,
[QueryHash] [binary](8) NULL,
[ExecutionCount] [bigint] NULL,
[TotalWorkerTime] [bigint] NULL,
[StatementText] [nvarchar](max) NULL,
[TotalElapsedTime] [bigint] NULL,
CONSTRAINT [PK_SQL_StatementExecStat] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO 1.2. Für gespeicherte Prozeduren:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [srv].[SQL_ProcedureExecStat](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[InsertDate] [datetime] NULL,
[database_id] [int] NULL,
[object_id] [int] NULL,
[ExecutionCount] [bigint] NULL,
[TotalWorkerTime] [bigint] NULL,
[TotalElapsedTime] [bigint] NULL,
[TotalPhysicalReads] [bigint] NULL,
[TotalLogicalReads] [bigint] NULL,
[TotalLogicalWrites] [bigint] NULL,
CONSTRAINT [PK_SQL_ProcedureExecStat] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO 1.3. Für Auslöser:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [srv].[SQL_TriggerExecStat](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[InsertDate] [datetime] NULL,
[database_id] [int] NULL,
[object_id] [int] NULL,
[ExecutionCount] [bigint] NULL,
[TotalWorkerTime] [bigint] NULL,
[TotalElapsedTime] [bigint] NULL
) ON [PRIMARY]
GO
2. Erstellen einer Ansicht zum Sammeln von Informationen (hier können wir Filter einfügen, um irrelevante Informationen zu entfernen (z. B. Abfragen und Prozeduren mit Replikationsauslösern usw.).
2.1. Für Abfragen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [srv].[vStatementExecInfo] as
with info as (
SELECT
query_stats.query_hash AS QueryHash,
SUM(query_stats.total_worker_time ) /
SUM(query_stats.execution_count) AS AvgCPU_Time,
SUM(query_stats.execution_count ) AS ExecutionCount,
SUM(query_stats.total_worker_time ) AS TotalWorkerTime,
MIN(query_stats.statement_text ) AS StatementText,
MIN(query_stats.min_worker_time ) AS MinWorkerTime,
MAX(query_stats.max_worker_time ) AS MaxWorkerTime,
SUM(query_stats.total_physical_reads) AS TotalPhysicalReads,
MIN(query_stats.min_physical_reads ) AS MinPhysicalReads,
MAX(query_stats.max_physical_reads ) AS MaxPhysicalReads,
SUM(query_stats.total_physical_reads) /
SUM(query_stats.execution_count) AS AvgPhysicalReads,
SUM(query_stats.total_logical_writes) AS TotalLogicalWrites,
MIN(query_stats.min_logical_writes ) AS MinLogicalWrites,
MAX(query_stats.max_logical_writes ) AS MaxLogicalWrites,
SUM(query_stats.total_logical_writes) /
SUM(query_stats.execution_count) AS AvgLogicalWrites,
SUM(query_stats.total_logical_reads ) AS TotalLogicalReads,
MIN(query_stats.min_logical_reads ) AS MinLogicalReads,
MAX(query_stats.max_logical_reads ) AS MaxLogicalReads,
SUM(query_stats.total_logical_reads ) /
SUM(query_stats.execution_count) AS AvgLogicalReads,
SUM(query_stats.total_elapsed_time ) AS TotalElapsedTime,
MIN(query_stats.min_elapsed_time ) AS MinElapsedTime,
MAX(query_stats.max_elapsed_time ) AS MaxElapsedTime,
SUM(query_stats.total_elapsed_time ) /
SUM(query_stats.execution_count) AS AvgElapsedTime,
MIN(query_stats.creation_time ) AS MinCreationTime,
MAX(query_stats.last_execution_time ) AS LastExecuteTime
FROM
(SELECT QS.query_hash
,QS.total_worker_time
,QS.execution_count
,QS.min_worker_time
,QS.max_worker_time
,QS.min_physical_reads
,QS.max_physical_reads
,QS.total_physical_reads
,QS.total_logical_writes
,QS.min_logical_writes
,QS.max_logical_writes
,QS.min_logical_reads
,QS.max_logical_reads
,QS.total_logical_reads
,QS.min_elapsed_time
,QS.max_elapsed_time
,QS.total_elapsed_time
,QS.creation_time
,QS.last_execution_time
,SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1,
((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(ST.text)
ELSE QS.statement_end_offset END
- QS.statement_start_offset)/2) + 1) AS statement_text
FROM sys.dm_exec_query_stats AS QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST) as query_stats
WHERE execution_count > 1
and last_execution_time >= dateadd(hour,-3,getdate())
GROUP BY query_stats.query_hash)
select
QueryHash,
AvgCPU_Time,
ExecutionCount,
TotalWorkerTime,
StatementText,
MinWorkerTime,
MaxWorkerTime,
TotalPhysicalReads,
MinPhysicalReads,
MaxPhysicalReads,
AvgPhysicalReads,
TotalLogicalWrites,
MinLogicalWrites,
MaxLogicalWrites,
AvgLogicalWrites,
TotalLogicalReads,
MinLogicalReads,
MaxLogicalReads,
AvgLogicalReads,
TotalElapsedTime,
MinElapsedTime,
MaxElapsedTime,
AvgElapsedTime,
MinCreationTime,
LastExecuteTime
from info
GO
Dabei kommen folgende Systemabfragen zum Einsatz:sys.dm_exec_query_stats und sys.dm_exec_sql_text.
2.2. Für gespeicherte Prozeduren:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [srv].[vProcedureExecInfo] as
with info as (
SELECT
procedure_stats.database_id AS database_id,
procedure_stats.object_id AS object_id,
MIN(procedure_stats.type) AS type,
SUM(procedure_stats.total_worker_time ) /
SUM(procedure_stats.execution_count) AS AvgCPU_Time,
SUM(procedure_stats.execution_count ) AS ExecutionCount,
SUM(procedure_stats.total_worker_time ) AS TotalWorkerTime,
MIN(procedure_stats.ProcedureText ) AS ProcedureText,
MIN(procedure_stats.min_worker_time ) AS MinWorkerTime,
MAX(procedure_stats.max_worker_time ) AS MaxWorkerTime,
SUM(procedure_stats.total_physical_reads) AS TotalPhysicalReads,
MIN(procedure_stats.min_physical_reads ) AS MinPhysicalReads,
MAX(procedure_stats.max_physical_reads ) AS MaxPhysicalReads,
SUM(procedure_stats.total_physical_reads) /
SUM(procedure_stats.execution_count) AS AvgPhysicalReads,
SUM(procedure_stats.total_logical_writes) AS TotalLogicalWrites,
MIN(procedure_stats.min_logical_writes ) AS MinLogicalWrites,
MAX(procedure_stats.max_logical_writes ) AS MaxLogicalWrites,
SUM(procedure_stats.total_logical_writes) /
SUM(procedure_stats.execution_count) AS AvgLogicalWrites,
SUM(procedure_stats.total_logical_reads ) AS TotalLogicalReads,
MIN(procedure_stats.min_logical_reads ) AS MinLogicalReads,
MAX(procedure_stats.max_logical_reads ) AS MaxLogicalReads,
SUM(procedure_stats.total_logical_reads ) /
SUM(procedure_stats.execution_count) AS AvgLogicalReads,
SUM(procedure_stats.total_elapsed_time ) AS TotalElapsedTime,
MIN(procedure_stats.min_elapsed_time ) AS MinElapsedTime,
MAX(procedure_stats.max_elapsed_time ) AS MaxElapsedTime,
SUM(procedure_stats.total_elapsed_time ) /
SUM(procedure_stats.execution_count) AS AvgElapsedTime,
MIN(procedure_stats.cached_time ) AS MinCachedTime,
MAX(procedure_stats.last_execution_time ) AS LastExecuteTime
FROM
(SELECT QS.database_id
,QS.object_id
,QS.type
,QS.total_worker_time
,QS.execution_count
,QS.min_worker_time
,QS.max_worker_time
,QS.min_physical_reads
,QS.max_physical_reads
,QS.total_physical_reads
,QS.total_logical_writes
,QS.min_logical_writes
,QS.max_logical_writes
,QS.min_logical_reads
,QS.max_logical_reads
,QS.total_logical_reads
,QS.min_elapsed_time
,QS.max_elapsed_time
,QS.total_elapsed_time
,QS.cached_time
,QS.last_execution_time
,ST.text as Proceduretext
FROM sys.dm_exec_Procedure_stats AS QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST) as procedure_stats
WHERE execution_count > 1
and last_execution_time >= dateadd(hour,-3,getdate())
GROUP BY database_id,object_id)
select
database_id,
object_id,
type,
AvgCPU_Time,
ExecutionCount,
TotalWorkerTime,
ProcedureText,
MinWorkerTime,
MaxWorkerTime,
TotalPhysicalReads,
MinPhysicalReads,
MaxPhysicalReads,
AvgPhysicalReads,
TotalLogicalWrites,
MinLogicalWrites,
MaxLogicalWrites,
AvgLogicalWrites,
TotalLogicalReads,
MinLogicalReads,
MaxLogicalReads,
AvgLogicalReads,
TotalElapsedTime,
MinElapsedTime,
MaxElapsedTime,
AvgElapsedTime,
MinCachedTime,
LastExecuteTime
from info
GO Hier werden die folgenden Systemabfragen verwendet:sys.dm_exec_Procedure_stats und sys.dm_exec_sql_text.
2.3. Für Auslöser:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [srv].[vTriggerExecInfo] as
with info as (
SELECT
procedure_stats.database_id AS database_id,
procedure_stats.object_id AS object_id,
MIN(procedure_stats.type) AS type,
SUM(procedure_stats.total_worker_time ) /
SUM(procedure_stats.execution_count) AS AvgCPU_Time,
SUM(procedure_stats.execution_count ) AS ExecutionCount,
SUM(procedure_stats.total_worker_time ) AS TotalWorkerTime,
MIN(procedure_stats.ProcedureText ) AS ProcedureText,
MIN(procedure_stats.min_worker_time ) AS MinWorkerTime,
MAX(procedure_stats.max_worker_time ) AS MaxWorkerTime,
SUM(procedure_stats.total_physical_reads) AS TotalPhysicalReads,
MIN(procedure_stats.min_physical_reads ) AS MinPhysicalReads,
MAX(procedure_stats.max_physical_reads ) AS MaxPhysicalReads,
SUM(procedure_stats.total_physical_reads) /
SUM(procedure_stats.execution_count) AS AvgPhysicalReads,
SUM(procedure_stats.total_logical_writes) AS TotalLogicalWrites,
MIN(procedure_stats.min_logical_writes ) AS MinLogicalWrites,
MAX(procedure_stats.max_logical_writes ) AS MaxLogicalWrites,
SUM(procedure_stats.total_logical_writes) /
SUM(procedure_stats.execution_count) AS AvgLogicalWrites,
SUM(procedure_stats.total_logical_reads ) AS TotalLogicalReads,
MIN(procedure_stats.min_logical_reads ) AS MinLogicalReads,
MAX(procedure_stats.max_logical_reads ) AS MaxLogicalReads,
SUM(procedure_stats.total_logical_reads ) /
SUM(procedure_stats.execution_count) AS AvgLogicalReads,
SUM(procedure_stats.total_elapsed_time ) AS TotalElapsedTime,
MIN(procedure_stats.min_elapsed_time ) AS MinElapsedTime,
MAX(procedure_stats.max_elapsed_time ) AS MaxElapsedTime,
SUM(procedure_stats.total_elapsed_time ) /
SUM(procedure_stats.execution_count) AS AvgElapsedTime,
MIN(procedure_stats.cached_time ) AS MinCachedTime,
MAX(procedure_stats.last_execution_time ) AS LastExecuteTime
FROM
(SELECT QS.database_id
,QS.object_id
,QS.type
,QS.total_worker_time
,QS.execution_count
,QS.min_worker_time
,QS.max_worker_time
,QS.min_physical_reads
,QS.max_physical_reads
,QS.total_physical_reads
,QS.total_logical_writes
,QS.min_logical_writes
,QS.max_logical_writes
,QS.min_logical_reads
,QS.max_logical_reads
,QS.total_logical_reads
,QS.min_elapsed_time
,QS.max_elapsed_time
,QS.total_elapsed_time
,QS.cached_time
,QS.last_execution_time
,ST.text as Proceduretext
FROM sys.dm_exec_trigger_stats AS QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST) as procedure_stats
WHERE execution_count > 1
and last_execution_time >= dateadd(hour,-3,getdate())
GROUP BY database_id,object_id)
select
database_id,
object_id,
type,
AvgCPU_Time,
ExecutionCount,
TotalWorkerTime,
ProcedureText,
MinWorkerTime,
MaxWorkerTime,
TotalPhysicalReads,
MinPhysicalReads,
MaxPhysicalReads,
AvgPhysicalReads,
TotalLogicalWrites,
MinLogicalWrites,
MaxLogicalWrites,
AvgLogicalWrites,
TotalLogicalReads,
MinLogicalReads,
MaxLogicalReads,
AvgLogicalReads,
TotalElapsedTime,
MinElapsedTime,
MaxElapsedTime,
AvgElapsedTime,
MinCachedTime,
LastExecuteTime
from info
GO Hier werden die folgenden Systemabfragen verwendet:sys.dm_exec_trigger_stats und sys.dm_exec_sql_text.
3. Erstellung gespeicherter Prozeduren für die Informationssammlung.
3.1. Für Anfragen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[InsertForSQL_StatementExecStat]
@koef decimal(12,2)=0.0 –collection coefficient
--is picked in an experimental way for more exact collection,
--in most cases, we can put 0.0,
--if frequency of the collection running does not exceed 5 minutes.
--Accuracy of calculation depends on the collection frequency and collection coefficient.
--The more frequent collection running is, the less influence collection coefficient has.
AS
BEGIN
SET NOCOUNT ON;
declare @AvgCPU_Time bigint
,@MaxAvgCPU_Time bigint
,@AvgTotalWorkerTime bigint
,@MaxTotalWorkerTime bigint
,@AvgAvgElapsedTime bigint
,@MaxAvgElapsedTime bigint
,@AvgTotalElapsedTime bigint
,@MaxTotalElapsedTime bigint
select
@AvgCPU_Time = AVG(AvgCPU_Time),
@MaxAvgCPU_Time = max(AvgCPU_Time),
@AvgTotalWorkerTime = AVG(TotalWorkerTime),
@MaxTotalWorkerTime = max(TotalWorkerTime),
@AvgAvgElapsedTime = AVG(AvgElapsedTime),
@MaxAvgElapsedTime = max(AvgElapsedTime),
@AvgTotalElapsedTime = AVG(TotalElapsedTime),
@MaxTotalElapsedTime = max(TotalElapsedTime)
from srv.vStatementExecInfo;
insert into srv.SQL_StatementExecStat
(
[InsertDate]
,[QueryHash]
,[ExecutionCount]
,[TotalWorkerTime]
,[StatementText]
,[TotalElapsedTime])
select
getdate()
,[QueryHash]
,[ExecutionCount]
,[TotalWorkerTime]
,[StatementText]
,[TotalElapsedTime]
from srv.vStatementExecInfo
where(AvgCPU_Time > @AvgCPU_Time + @koef * (@MaxAvgCPU_Time - @AvgCPU_Time))
or (TotalWorkerTime > @AvgTotalWorkerTime + @koef * (@MaxTotalWorkerTime - @AvgTotalWorkerTime))
or (AvgElapsedTime > @AvgAvgElapsedTime + @koef * (@MaxAvgElapsedTime - @AvgAvgElapsedTime))
or (TotalElapsedTime > @AvgTotalElapsedTime + @koef * (@MaxTotalElapsedTime - @AvgTotalElapsedTime));
END
GO 3.2. Für gespeicherte Prozeduren:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[InsertForProcedureExecStat]
@koef decimal(12,2)=0.0 --collection coefficient
--is picked in an experimental way for more exact collection,
--in most cases, we can put 0.0,
-- if frequency of the collection running does not exceed 5 minutes.
--Accuracy of calculation depends on the collection frequency and collection coefficient.
--The more frequent collection running is, the less influence collection coefficient has.
AS
BEGIN
SET NOCOUNT ON;
declare @AvgCPU_Time bigint
,@MaxAvgCPU_Time bigint
,@AvgTotalWorkerTime bigint
,@MaxTotalWorkerTime bigint
,@AvgAvgElapsedTime bigint
,@MaxAvgElapsedTime bigint
,@AvgTotalElapsedTime bigint
,@MaxTotalElapsedTime bigint;
select
@AvgCPU_Time = AVG(AvgCPU_Time),
@MaxAvgCPU_Time = max(AvgCPU_Time),
@AvgTotalWorkerTime = AVG(TotalWorkerTime),
@MaxTotalWorkerTime = max(TotalWorkerTime),
@AvgAvgElapsedTime = AVG(AvgElapsedTime),
@MaxAvgElapsedTime = max(AvgElapsedTime),
@AvgTotalElapsedTime = AVG(TotalElapsedTime),
@MaxTotalElapsedTime = max(TotalElapsedTime)
from srv.vProcedureExecInfo;
insert into srv.SQL_ProcedureExecStat
(
[InsertDate]
,database_id
,object_id
,[ExecutionCount]
,[TotalWorkerTime]
,[TotalElapsedTime]
,[TotalPhysicalReads]
,[TotalLogicalReads]
,[TotalLogicalWrites])
select
getdate()
,database_id
,object_id
,[ExecutionCount]
,[TotalWorkerTime]
,[TotalElapsedTime]
,[TotalPhysicalReads]
,[TotalLogicalReads]
,[TotalLogicalWrites]
from srv.vProcedureExecInfo
where(AvgCPU_Time > @AvgCPU_Time + @koef * (@MaxAvgCPU_Time - @AvgCPU_Time))
or (TotalWorkerTime > @AvgTotalWorkerTime + @koef * (@MaxTotalWorkerTime - @AvgTotalWorkerTime))
or (AvgElapsedTime > @AvgAvgElapsedTime + @koef * (@MaxAvgElapsedTime - @AvgAvgElapsedTime))
or (TotalElapsedTime > @AvgTotalElapsedTime + @koef * (@MaxTotalElapsedTime - @AvgTotalElapsedTime));
END
GO 3.3. Für Auslöser:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[InsertForTriggerExecStat]
@koef decimal(12,2)=0.0 --collection coefficient
--is picked in an experimental way for more exact collection,
--in most cases, we can put 0.0,
--if frequency of the collection running does not exceed 5 minutes.
--Accuracy of calculation depends on the collection frequency and collection coefficient.
--The more frequent collection running is, the less influence collection coefficient has.
AS
BEGIN
SET NOCOUNT ON;
declare @AvgCPU_Time bigint
,@MaxAvgCPU_Time bigint
,@AvgTotalWorkerTime bigint
,@MaxTotalWorkerTime bigint
,@AvgAvgElapsedTime bigint
,@MaxAvgElapsedTime bigint
,@AvgTotalElapsedTime bigint
,@MaxTotalElapsedTime bigint
select
@AvgCPU_Time = AVG(AvgCPU_Time),
@MaxAvgCPU_Time = max(AvgCPU_Time),
@AvgTotalWorkerTime = AVG(TotalWorkerTime),
@MaxTotalWorkerTime = max(TotalWorkerTime),
@AvgAvgElapsedTime = AVG(AvgElapsedTime),
@MaxAvgElapsedTime = max(AvgElapsedTime),
@AvgTotalElapsedTime = AVG(TotalElapsedTime),
@MaxTotalElapsedTime = max(TotalElapsedTime)
from srv.vProcedureExecInfo;
insert into srv.SQL_TriggerExecStat
(
[InsertDate]
,database_id
,object_id
,[ExecutionCount]
,[TotalWorkerTime]
,[TotalElapsedTime])
select
getdate()
,database_id
,object_id
,[ExecutionCount]
,[TotalWorkerTime]
,[TotalElapsedTime]
from srv.vTriggerExecInfo
where(AvgCPU_Time > @AvgCPU_Time + @koef * (@MaxAvgCPU_Time - @AvgCPU_Time))
or (TotalWorkerTime > @AvgTotalWorkerTime + @koef * (@MaxTotalWorkerTime - @AvgTotalWorkerTime))
or (AvgElapsedTime > @AvgAvgElapsedTime + @koef * (@MaxAvgElapsedTime - @AvgAvgElapsedTime))
or (TotalElapsedTime > @AvgTotalElapsedTime + @koef * (@MaxTotalElapsedTime - @AvgTotalElapsedTime));
END
GO 4. Erstellen einer Ansicht für die Informationsausgabe.
4.1. Für Anfragen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [srv].[vStatementExecTotalInfo]
as
select ExecutionCount as Num
,TotalWorkerTime as TotalWorkerTime
,TotalElapsedTime as TotalElapsedTime
,convert(decimal(8,2),AvgCPU_Time/1000000.) as AvgWorkerSec
,convert(decimal(8,2),AvgElapsedTime/1000000.) as AvgElapsedSec
,...
,QueryHash
,StatementText
from [SRV].[srv].[vStatementExecInfo];
GO 4.2. Für gespeicherte Prozeduren:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [srv].[vProcedureExecTotalInfo]
as
select ExecutionCount as Num
,TotalWorkerTime as TotalWorkerTime
,TotalElapsedTime as TotalElapsedTime
,convert(decimal(8,2),AvgCPU_Time/1000000.) as AvgWorkerSec
,convert(decimal(8,2),AvgElapsedTime/1000000.) as AvgElapsedSec
,...
,database_id
,object_id
,db_name(database_id) as DB_Name
,OBJECT_SCHEMA_NAME(object_id, database_id) as Schema_Name
,object_name(object_id, database_id) as Procedure_Name
from [SRV].[srv].[vProcedureExecInfo];
GO 4.3. Ansichten für Trigger werden auf ähnliche Weise erstellt (falls erforderlich). Was mich betrifft, ich brauche keine Tracing-Trigger, denn wenn es irgendwelche Probleme mit Triggern gibt, werden sie durch die Ausführung gespeicherter Prozeduren und Abfragen angezeigt.
Die folgenden beiden Parameter sind für die implementierten Ansichten von entscheidender Bedeutung:
- AvgWorkerSec – Abfrageausführungszeit in Sekunden.
- AvgElapsedSec – Wartezeit oder Wartezeit+AvgWorkerSec.
Was die Ergebnisse von Ansichten betrifft, so ist die folgende Gleichheit wichtig:
AvgWorkerSec=AvgElapsedSec
- AvgWorkerSec>AvgElapsedSec – hier belastet etwas den Prozessor im Moment der Abfrageausführung stark (wie sich herausstellte, lief der Scan der Antivirensoftware; es kann auch der Fehler des parallelen Plans sein).
- AvgWorkerSec
Wenn AvgWorkerSec=AvgElapsedSec eingehalten wird, hängt die lange Ausführungszeit mit der Abfrage selbst und ihrer Ausführungszeit zusammen.
Was ist ein Kriterium für die lange Abfrageausführung?
Auf diese Frage gibt es keine absolute Antwort. Es hängt davon ab, was eine Abfrage tut, wo und wie sie verwendet wird usw.
Ich habe die folgende Auswertung für die Ad-hoc-Abfragen und gespeicherten Prozeduren:
- Bis zu 0,5 – gut für gespeicherte Prozeduren, keine Probleme (keine Wartezeiten bei der Ausführung).
- Bis zu 0,1 – gut für Abfragen, keine Probleme (keine Wartezeiten bei der Ausführung).
- 0.5 – 1.0 – schlecht für gespeicherte Prozeduren, es gibt Probleme (es gibt keine Ausführungswartezeiten, die für einen Benutzer sichtbar sind, aber sie existieren immer noch und erfordern eine Lösung).
- 0,1 – 0,5 – schlecht für Abfragen, es gibt Probleme (es gibt keine Ausführungswartezeiten, die für einen Benutzer sichtbar sind, aber sie existieren immer noch und müssen gelöst werden).
- Mehr als 1.0 – schlecht für gespeicherte Prozeduren, es gibt Probleme (es besteht eine hohe Wahrscheinlichkeit, dass Wartezeiten auftreten, die für Benutzer sichtbar sind, das Problem erfordert eine sofortige Lösung).
- Mehr als 0,5 – schlecht für Abfragen, es gibt Probleme (es besteht eine hohe Wahrscheinlichkeit, dass Wartezeiten auftreten, die für Benutzer sichtbar sind, das Problem erfordert eine sofortige Lösung).
Bei den Non-Ad-hoc-Anfragen (Daten-Upload, Daten-Laden) wird die obige Bewertung individuell ausgewählt. Normalerweise übertrifft es Bewertungen für Ad-hoc-Abfragen und gespeicherte Prozeduren massiv.
Wenn die gesamte Software die gespeicherten Prozeduren durchläuft, können Sie nur gespeicherte Prozeduren verfolgen, da die Arbeit von Abfragen immer die Arbeit von gespeicherten Prozeduren beeinflusst. Belassen wir es daher bei der Analyse der Ausführung gespeicherter Prozeduren.
Lassen Sie uns ein System zum Sammeln von Informationen über die schwersten gespeicherten Prozeduren für die anschließende Analyse und Ausführung von Autotrace gemäß dem folgenden Algorithmus erstellen:
1. Erstellen einer Tabelle zum Speichern von Informationen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [srv].[SQL_TopProcedureExecStat](
[Row_GUID] [uniqueidentifier] NOT NULL,
[SERVER] [nvarchar](255) NOT NULL,
[DB_ID] [int] NOT NULL,
[OBJECT_ID] [int] NOT NULL,
[ExecutionCount] [bigint] NOT NULL,
[TotalWorkerTime] [bigint] NULL,
[TotalElapsedTime] [bigint] NULL,
[Func] [decimal](8, 2) NULL,
[AvgWorkerSec] [decimal](8, 2) NULL,
[AvgElapsedSec] [decimal](8, 2) NULL,
[DB_NAME] [nvarchar](255) NULL,
[SCHEMA_NAME] [nvarchar](255) NULL,
[OBJECT_NAME] [nvarchar](255) NULL,
[InsertUTCDate] [datetime] NOT NULL,
[TotalPhysicalReads] [bigint] NULL,
[TotalLogicalReads] [bigint] NULL,
[TotalLogicalWrites] [bigint] NULL,
[AvgPhysicalReads] [bigint] NULL,
[AvgLogicalReads] [bigint] NULL,
[AvgLogicalWrites] [bigint] NULL,
[CategoryName] [nvarchar](255) NULL,
CONSTRAINT [PK_SQL_TopProcedureExecStat] PRIMARY KEY CLUSTERED
(
[Row_GUID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [srv].[SQL_TopProcedureExecStat] ADD CONSTRAINT [DF_SQL_TopProcedureExecStat_Row_GUID] DEFAULT (newid()) FOR [Row_GUID]
GO
ALTER TABLE [srv].[SQL_TopProcedureExecStat] ADD CONSTRAINT [DF_SQL_TopProcedureExecStat_SERVER] DEFAULT (@@servername) FOR [SERVER]
GO
ALTER TABLE [srv].[SQL_TopProcedureExecStat] ADD CONSTRAINT [DF_SQL_TopProcedureExecStat_InsertUTCDate] DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO 2. Erstellung einer gespeicherten Prozedur zum Sammeln von Informationen:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[InsertTopProcedureExecStat]
@top tinyint=24 – number of days to store records
,@CategoryName nvarchar(255)='AvgWorkerSec' – a category for selection
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [srv].[SQL_TopProcedureExecStat]
([DB_ID]
,[OBJECT_ID]
,[ExecutionCount]
,[TotalWorkerTime]
,[TotalElapsedTime]
,[AvgWorkerSec]
,[AvgElapsedSec]
,[DB_NAME]
,[SCHEMA_NAME]
,[OBJECT_NAME]
,InsertUTCDate
,CategoryName
,TotalPhysicalReads
,TotalLogicalReads
,TotalLogicalWrites
,AvgPhysicalReads
,AvgLogicalReads
,AvgLogicalWrites)
select top(@top)
[database_id]
,[object_id]
,[Num]
,[TotalWorkerTime]
,[TotalElapsedTime]
,[AvgWorkerSec]
,[AvgElapsedSec]
,[DB_NAME]
,[SCHEMA_NAME]
,[PROCEDURE_NAME]
,InsertUTCDate
,CategoryName
,TotalPhysicalReads
,TotalLogicalReads
,TotalLogicalWrites
,AvgPhysicalReads
,AvgLogicalReads
,AvgLogicalWrites
from(
select [database_id]
,[object_id]
,[Num]
,[TotalWorkerTime]
,[TotalElapsedTime]
,[AvgWorkerSec]
,[AvgElapsedSec]
,[DB_NAME]
,[SCHEMA_NAME]
,[PROCEDURE_NAME]
,getUTCDate() as InsertUTCDate
,@CategoryName as CategoryName
,TotalPhysicalReads
,TotalLogicalReads
,TotalLogicalWrites
,AvgPhysicalReads
,AvgLogicalReads
,AvgLogicalWrites
FROM [srv].[vProcedureExecTotalInfoHour]
) as t
order by
case @CategoryName
when 'TotalWorkerTime' then TotalWorkerTime
when 'TotalElapsedTime' then TotalElapsedTime
when 'AvgWorkerSec' then AvgWorkerSec
when 'AvgElapsedSec' then AvgElapsedSec
when 'TotalPhysicalReads' then TotalPhysicalReads
when 'TotalLogicalReads' then TotalLogicalReads
when 'TotalLogicalWrites' then TotalLogicalWrites
when 'AvgPhysicalReads' then AvgPhysicalReads
when 'AvgLogicalReads' then AvgLogicalReads
when 'AvgLogicalWrites' then AvgLogicalWrites
end
desc;
declare @count int=(select count(*) from [srv].[SQL_TopProcedureExecStat] where example@sqldat.com);
declare @diff example@sqldat.com@top;
;with tbl_del as(
select
Row_GUID
from [srv].[SQL_TopProcedureExecStat]
where InsertUTCDate<DateAdd(hour,-24,getUTCDate()) and example@sqldat.com ) delete from [srv].[SQL_TopProcedureExecStat] where Row_GUID in (select Row_GUID from tbl_del); --if table contains more records that it is specified in @top, the most insignificant records are deleted set @count = (select count(*) from [srv].[SQL_TopProcedureExecStat] where example@sqldat.com) set @diff = @count - @Top - 3 if(@diff>0)
begin
;with tbl_del as(
select top(@diff)
Row_GUID
from [srv].[SQL_TopProcedureExecStat]
where example@sqldat.com
order by
case @CategoryName
when 'TotalWorkerTime' then TotalWorkerTime
when 'TotalElapsedTime' then TotalElapsedTime
when 'AvgWorkerSec' then AvgWorkerSec
when 'AvgElapsedSec' then AvgElapsedSec
when 'TotalPhysicalReads' then TotalPhysicalReads
when 'TotalLogicalReads' then TotalLogicalReads
when 'TotalLogicalWrites' then TotalLogicalWrites
when 'AvgPhysicalReads' then AvgPhysicalReads
when 'AvgLogicalReads' then AvgLogicalReads
when 'AvgLogicalWrites' then AvgLogicalWrites
end
)
delete from [srv].[SQL_TopProcedureExecStat]
where Row_GUID in (select Row_GUID from tbl_del);
end
declare @DB_ID int
declare @OBJECT_ID int
declare @top1 int = 3
declare @diff1 int
declare @count1 int
-- deletion of more than @top1 times repeats of the specific procedure
select top (1)
@count1 = tp.num
,@DB_ID = tp.DB_ID
,@OBJECT_ID = tp.OBJECT_ID
from
(select count(*) as num, DB_ID, OBJECT_ID
from [srv].[SQL_TopProcedureExecStat]
where example@sqldat.com
group by DB_ID, OBJECT_ID) as tp
order by tp.num desc;
set @diff1 = @count1 - @top1;
if(@diff1) > 0
begin
;with tbl_del as(
select top(@diff1)
Row_GUID
from [srv].[SQL_TopProcedureExecStat]
where DB_ID = @DB_ID and OBJECT_ID = @OBJECT_ID
and example@sqldat.com
order by
case @CategoryName
when 'TotalWorkerTime' then TotalWorkerTime
when 'TotalElapsedTime' then TotalElapsedTime
when 'AvgWorkerSec' then AvgWorkerSec
when 'AvgElapsedSec' then AvgElapsedSec
when 'TotalPhysicalReads' then TotalPhysicalReads
when 'TotalLogicalReads' then TotalLogicalReads
when 'TotalLogicalWrites' then TotalLogicalWrites
when 'AvgPhysicalReads' then AvgPhysicalReads
when 'AvgLogicalReads' then AvgLogicalReads
when 'AvgLogicalWrites' then AvgLogicalWrites
end
)
delete from [srv].[SQL_TopProcedureExecStat]
where Row_GUID in (select Row_GUID from tbl_del);
end
-- deletion of more than 1 repeats of the AvgWorkerSec parameter for the specific procedure
if @CategoryName = 'AvgWorkerSec'
begin
declare @AvgWorkerSec decimal(8,2)
select top (1)
@count1 = tp.num
,@DB_ID = tp.DB_ID
,@OBJECT_ID = tp.OBJECT_ID
,@AvgWorkerSec = tp.AvgWorkerSec
from
(select count(*) as num, DB_ID, OBJECT_ID, AvgWorkerSec
from [srv].[SQL_TopProcedureExecStat]
where example@sqldat.com
group by DB_ID, OBJECT_ID,AvgWorkerSec) as tp
order by tp.num desc;
set @diff1 = @count1 - 1;
if(@diff1) > 0
begin
;with tbl_del as(
select top(@diff1)
Row_GUID
from [srv].[SQL_TopProcedureExecStat]
where DB_ID = @DB_ID and OBJECT_ID = @OBJECT_ID
and example@sqldat.com and AvgWorkerSec = @AvgWorkerSec
order by InsertUTCDate desc
)
delete from [srv].[SQL_TopProcedureExecStat]
where Row_GUID in (select Row_GUID from tbl_del);
end
end
if @CategoryName = 'AvgElapsedSec'
begin
declare @AvgElapsedSec decimal(8,2)
select top (1)
@count1 = tp.num
,@DB_ID = tp.DB_ID
,@OBJECT_ID = tp.OBJECT_ID
,@AvgElapsedSec = tp.AvgElapsedSec
from
(select count(*) as num, DB_ID, OBJECT_ID, AvgElapsedSec
from [srv].[SQL_TopProcedureExecStat]
where example@sqldat.com
group by DB_ID, OBJECT_ID,AvgElapsedSec) as tp
order by tp.num desc;
set @diff1 = @count1 - 1;
if(@diff1) > 0
begin
;with tbl_del as(
select top(@diff1)
Row_GUID
from [srv].[SQL_TopProcedureExecStat]
where DB_ID = @DB_ID and OBJECT_ID = @OBJECT_ID
and example@sqldat.com and AvgElapsedSec = @AvgElapsedSec
order by InsertUTCDate desc
)
delete from [srv].[SQL_TopProcedureExecStat]
where Row_GUID in (select Row_GUID from tbl_del);
end
end
END
GO It is better to run this stored procedure immediately after collecting information about the stored procedures (we can set up a task in Agent for running it every 5-10 minutes for queries, stored procedures and triggers):
exec [srv].[InsertForSQL_StatementExecStat]; --collecting information about executed queries exec [srv].[InsertForTriggerExecStat]; --collecting information about executed triggers exec [srv].[InsertForProcedureExecStat]; --collecting information about executed stored procedures --collecting information about the most heavy executed stored procedures, according to the criteria exec [srv].[InsertTopProcedureExecStat] @example@sqldat.com, @CategoryName='AvgWorkerSec'; exec [srv].[InsertTopProcedureExecStat] @example@sqldat.com, @CategoryName='AvgElapsedSec'
3. Running trace (every 5-10 minutes with the help of the Agent tasks, preferably right after collecting information):
USE [DATABASE_NAME];
go
--coefficient of transition value of indicator
declare @koef_red numeric(8,3)=1.3;
--if there are records with the indicator greater than or equal to the
--preset indicator coefficient
if(exists(
SELECT top(1) 1
FROM [srv].[SQL_TopProcedureExecStat]
where CategoryName='AvgElapsedSec'
or CategoryName='AvgWorkerSec'
group by CategoryName
having avg([AvgElapsedSec])>example@sqldat.com_red
or avg([AvgWorkerSec])>example@sqldat.com_red))
begin
--running autorace
exec .[srv].[AutoTrace];
end The auto-trace stored procedure is implemented on an individual basis. Zum Beispiel:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[AutoTrace]
@maxfilesize bigint=200 --maximum file size in Mb
,@run_minutes int=60 --tracing length in minutes
,@file_patch nvarchar(255)=N'Path to directory' --directory for trace file
,@file_name nvarchar(255)=N'Profiler' --file name
,@res_msg nvarchar(255)=NULL output --result in the form of messages
AS
BEGIN
SET NOCOUNT ON;
declare @rc int;
declare @TraceID int;
if(@run_minutes>=1200) set @run_minutes=1200; --no longer than 20 hours!
declare @finish_dt datetime=DateAdd(minute,@run_minutes,GetDate()); --execution end time
--end of trace file
declare @finish_dt_inc nvarchar(255)=N'_'+cast(YEAR(@finish_dt) as nvarchar(255))+'_'+cast(MONTH(@finish_dt) as nvarchar(255))+'_'+cast(DAY(@finish_dt) as nvarchar(255));
declare @File nvarchar(255)example@sqldat.comexample@sqldat.comexample@sqldat.com_dt_inc; --full name of the trace file
DECLARE @result bit;
DECLARE @msgerrors nvarchar(255);
DECLARE @oldDT datetime;
--Getting the last date and time
if(object_id('DATABASE_NAME.dbo.TraceTable')<>0)
begin
select @oldDT=max(StartTime)
from DATABASE_NAME.dbo.TraceTable
where StartTime is not null;
end
--select @oldDT;
--If the last date and time is not specified or it is less than time of trace ending,trace is run. Otherwise, the trace was executed on this date.
if(@oldDT is null or @oldDT<DATETIMEFROMPARTS(YEAR(@finish_dt), MONTH(@finish_dt), DAY(@finish_dt), 0, 0, 0, 0))
begin
--creating trace
exec @rc = sp_trace_create
@example@sqldat.com output, --Trace ID
@Options=0, --tracing options (by default)
@example@sqldat.com, --trace file destination
@example@sqldat.com, --maximum size of the trace file (when reaching maximum size, trace stops)
@example@sqldat.com_dt--, --end data and time of trace
example@sqldat.com=2; --number of files, at which the previous files are deleted (that are not used in the given set of trace options)
--if the trace is created without errors (code 0), we can set up and tun it
if (@rc = 0)
begin
-- Setting event for trace
declare @on bit
set @on = 1
exec sp_trace_setevent @TraceID, 10, 1, @on
exec sp_trace_setevent @TraceID, 10, 9, @on
exec sp_trace_setevent @TraceID, 10, 2, @on
exec sp_trace_setevent @TraceID, 10, 66, @on
exec sp_trace_setevent @TraceID, 10, 10, @on
exec sp_trace_setevent @TraceID, 10, 3, @on
exec sp_trace_setevent @TraceID, 10, 4, @on
exec sp_trace_setevent @TraceID, 10, 6, @on
exec sp_trace_setevent @TraceID, 10, 7, @on
exec sp_trace_setevent @TraceID, 10, 8, @on
exec sp_trace_setevent @TraceID, 10, 11, @on
exec sp_trace_setevent @TraceID, 10, 12, @on
exec sp_trace_setevent @TraceID, 10, 13, @on
exec sp_trace_setevent @TraceID, 10, 14, @on
exec sp_trace_setevent @TraceID, 10, 15, @on
exec sp_trace_setevent @TraceID, 10, 16, @on
exec sp_trace_setevent @TraceID, 10, 17, @on
exec sp_trace_setevent @TraceID, 10, 18, @on
exec sp_trace_setevent @TraceID, 10, 25, @on
exec sp_trace_setevent @TraceID, 10, 26, @on
exec sp_trace_setevent @TraceID, 10, 31, @on
exec sp_trace_setevent @TraceID, 10, 34, @on
exec sp_trace_setevent @TraceID, 10, 35, @on
exec sp_trace_setevent @TraceID, 10, 41, @on
exec sp_trace_setevent @TraceID, 10, 48, @on
exec sp_trace_setevent @TraceID, 10, 49, @on
exec sp_trace_setevent @TraceID, 10, 50, @on
exec sp_trace_setevent @TraceID, 10, 51, @on
exec sp_trace_setevent @TraceID, 10, 60, @on
exec sp_trace_setevent @TraceID, 10, 64, @on
exec sp_trace_setevent @TraceID, 12, 1, @on
exec sp_trace_setevent @TraceID, 12, 9, @on
exec sp_trace_setevent @TraceID, 12, 3, @on
exec sp_trace_setevent @TraceID, 12, 11, @on
exec sp_trace_setevent @TraceID, 12, 4, @on
exec sp_trace_setevent @TraceID, 12, 6, @on
exec sp_trace_setevent @TraceID, 12, 7, @on
exec sp_trace_setevent @TraceID, 12, 8, @on
exec sp_trace_setevent @TraceID, 12, 10, @on
exec sp_trace_setevent @TraceID, 12, 12, @on
exec sp_trace_setevent @TraceID, 12, 13, @on
exec sp_trace_setevent @TraceID, 12, 14, @on
exec sp_trace_setevent @TraceID, 12, 15, @on
exec sp_trace_setevent @TraceID, 12, 16, @on
exec sp_trace_setevent @TraceID, 12, 17, @on
exec sp_trace_setevent @TraceID, 12, 18, @on
exec sp_trace_setevent @TraceID, 12, 26, @on
exec sp_trace_setevent @TraceID, 12, 31, @on
exec sp_trace_setevent @TraceID, 12, 35, @on
exec sp_trace_setevent @TraceID, 12, 41, @on
exec sp_trace_setevent @TraceID, 12, 48, @on
exec sp_trace_setevent @TraceID, 12, 49, @on
exec sp_trace_setevent @TraceID, 12, 50, @on
exec sp_trace_setevent @TraceID, 12, 51, @on
exec sp_trace_setevent @TraceID, 12, 60, @on
exec sp_trace_setevent @TraceID, 12, 64, @on
exec sp_trace_setevent @TraceID, 12, 66, @on
exec sp_trace_setevent @TraceID, 13, 1, @on
exec sp_trace_setevent @TraceID, 13, 9, @on
exec sp_trace_setevent @TraceID, 13, 3, @on
exec sp_trace_setevent @TraceID, 13, 11, @on
exec sp_trace_setevent @TraceID, 13, 4, @on
exec sp_trace_setevent @TraceID, 13, 6, @on
exec sp_trace_setevent @TraceID, 13, 7, @on
exec sp_trace_setevent @TraceID, 13, 8, @on
exec sp_trace_setevent @TraceID, 13, 10, @on
exec sp_trace_setevent @TraceID, 13, 12, @on
exec sp_trace_setevent @TraceID, 13, 14, @on
exec sp_trace_setevent @TraceID, 13, 26, @on
exec sp_trace_setevent @TraceID, 13, 35, @on
exec sp_trace_setevent @TraceID, 13, 41, @on
exec sp_trace_setevent @TraceID, 13, 49, @on
exec sp_trace_setevent @TraceID, 13, 50, @on
exec sp_trace_setevent @TraceID, 13, 51, @on
exec sp_trace_setevent @TraceID, 13, 60, @on
exec sp_trace_setevent @TraceID, 13, 64, @on
exec sp_trace_setevent @TraceID, 13, 66, @on
-- Setting filters
declare @intfilter int;
declare @bigintfilter bigint;
exec sp_trace_setfilter @TraceID, 10, 0, 7, N'Application SQL Server Profiler - fa35966e-e426-4d1a-8753-8f971cf89495';
exec sp_trace_setfilter @TraceID, 35, 0, 6, N'%DATABASE_NAME%';
exec sp_trace_setfilter @TraceID, 35, 1, 6, N'%DATABASE_NAME%';
--Running trace
exec sp_trace_setstatus @TraceID, 1;
--setting execution time wait
declare @run_delay example@sqldat.com_minutes+1; --increase of wait for 1 minute from the preset trace execution time
declare @run_delay_hour example@sqldat.com_delay/60; --calculation of wait hours
declare @run_delay_minute example@sqldat.com_delay-(@run_delay/60)*60; --calculation of the remaining minutes
declare @run_delay_hour_str nvarchar(2); --string representation of hours
declare @run_delay_minute_str nvarchar(2); --string representation of minutes
--adding missing nulls for string representation of hours
if(@run_delay_hour=0) set @run_delay_hour_str='00';
else if(@run_delay_hour<10) set @run_delay_hour_str='0'+cast(@run_delay_hour as nvarchar(255)); else if(@run_delay_hour>=10) set @run_delay_hour_str=cast(@run_delay_hour as nvarchar(255));
--select @run_delay_hour, @run_delay_hour_str;
--adding missing nulls for string representation of minutes
if(@run_delay_minute=0) set @run_delay_minute_str='00';
else if(@run_delay_minute<10) set @run_delay_minute_str='0'+cast(@run_delay_minute as nvarchar(255)); else if(@run_delay_minute>=10) set @run_delay_minute_str=cast(@run_delay_minute as nvarchar(255));
--select @run_delay_minute, @run_delay_minute_str;
--the hours:minutes string representation for the wait
declare @run_delay_str nvarchar(255)example@sqldat.com_delay_hour_str+':'example@sqldat.com_delay_minute_str;
--wait
WAITFOR DELAY @run_delay_str;
--select @run_delay_str;
--deletion of the trace table, if it exists
if(object_id('DATABASE_NAME.dbo.TraceTable')<>0)
begin
drop table DATABASE_NAME.dbo.TraceTable;
end
--creation and filling of the trace table from the trace file
SELECT
*
INTO DATABASE_NAME.dbo.TraceTable
FROM ::fn_trace_gettable(@File+'.trc', default);
--adding extension to the full file
set @example@sqldat.com+'.trc';
--here, we need to insert code to delete the trace file
declare @str_title nvarchar(max)='There was auto trace on the server'example@sqldat.com@servername,
@str_pred_mess nvarchar(max)='На 'example@sqldat.com@servername+'The auto trace has been run on the server. You can view the result in the Database_Name.dbo.TraceTable table;
--here, we can send the auto trace run notification to administrator
end
--returning the result
set @res_msg=N'ErrorCode='+cast(@rc as nvarchar(255))+'\r\n'+coalesce(@msgerrors, '');
end
END
GO For more information on setting trace, refer to How to:Create a Trace (Transact-SQL).
Schlussfolgerung
In this article, we considered an example of implementation of a system for collecting information about the state of a database, that does not load the system. In case of problem detection, this system runs the preset trace and saves results into a table. This approach can be extended to several servers. In this case, we need to collect information from all servers for subsequent sending of information to administrators.
It is also important to remember about deletion of old data from the used tables. It is quite sufficient to store data within a month or two weeks.
Also read:
Implementing a Common MS SQL Server Performance Indicator
Referenzen
- sys.dm_exec_trigger_stats
- sys.dm_exec_procedure_stats
- sys.dm_exec_query_stats
- sys.dm_exec_sql_text
- How to:Create a Trace (Transact-SQL)