Předmluva
Dříve nebo později by správce DB chtěl mít indikátor výkonu pro dotazy SQL Server. Jak všichni víme, spuštění Profileru po dobu 24 hodin povede ke značnému zatížení systému, a proto jej nelze považovat za optimální řešení pro databáze používané v režimu 24/7.
Jak tedy můžeme zjistit stav dotazů SQL Server? Jak můžeme spustit trasování pro zjištěné problémy související s dotazem bez lidského zásahu?
V tomto článku poskytnu implementaci indikátoru výkonu SQL Serveru pro dotazy, uložené procedury a spouštěče a také jeho použití pro běh trasování.
Řešení
Nejprve se podívejme na obecný přístup k implementaci indikátoru výkonu pro dotazy, uložené procedury a spouštěče:
- Vytvoření požadovaných tabulek pro sběr a analýzu informací.
- Vytvoření zobrazení pro shromažďování informací.
- Vytvoření uložených procedur pro sběr informací.
- Vytvoření zobrazení pro výstup informací.
A nyní se podívejme na implementaci:
1. Vytvoření požadovaných tabulek pro sběr a analýzu informací.
1.1. Pro dotazy:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOSET ANSI_PADDING ONGOCREATE TABLE [srv].[SQL_StatementExecStat]( [ID] [bigint] IDENTITY(1,1) [NEPLATNÉ NULL, [datumash]Datum] NULL binární](8) NULL, [ExecutionCount] [bigint] NULL, [TotalWorkerTime] [bigint] NULL, [StatementText] [nvarchar](max) NULL, [TotalElapsedTime] [bigint] NULL, CONSTRAINT [PK_SQL_StatementExec CLUMA] PRIMA [ID] ASC)WITH (PAD_INDEX =VYPNUTO, STATISTICS_NORECOMPUTE =VYPNUTO, IGNORE_DUP_KEY =VYPNUTO, ALLOW_ROW_LOCKS =ZAPNUTO, ALLOW_PAGE_LOCKS =ZAPNUTO) ZAPNUTO [PRIMÁRNÍ]) ZAPNUTO [PRIMARY] TEXTIMAGE_ONGODD_pre>PRIMARY1.2. Pro uložené procedury:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE TABLE [srv].[SQL_ProcedureExecStat]( [ID] [bigint] IDENTITY(1,1) NOT NULL, [InsertDate] [datetime] NULL, [database] NULL] NULL, [id_objektu] [int] NULL, [Počet provádění] [bigint] NULL, [TotalWorkerTime] [bigint] NULL, [TotalElapsedTime] [bigint] NULL, [TotalPhysicalReads] [bigint] NULL, [TotalLogicalReads] [bigint] [TotalLogicalWrites] [bigint] NULL, OMEZENÍ [PK_SQL_ProcedureExecStat] PRIMÁRNÍ KLÍČ SE SLUSTROVANÝ ( [ID] ASC) S (PAD_INDEX =VYPNUTO, STATISTICS_NORECOMPUTE =VYPNUTO, IGNORE_DUP_KEY =VYPNUTO, ALLOWRISMA ZAPNUTO)_)_ PRIMARY]PŘEJÍT1.3. Pro spouštěče:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE TABLE [srv].[SQL_TriggerExecStat]( [ID] [bigint] IDENTITY(1,1) NOT NULL, [InsertDate] [datetime] NULL, [int] NULL, [id_objektu] [int] NULL, [Počet provedení] [bigint] NULL, [TotalWorkerTime] [bigint] NULL, [TotalElapsedTime] [bigint] NULL) NA [PRIMARY]GO2. Vytvoření pohledu pro sběr informací (zde můžeme vložit filtry pro zbavení se irelevantních informací (např. dotazy a procedury se spouštěči replikace atd.).
2.1. Pro dotazy:POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE zobrazení [srv].[vStatementExecInfo] jako s informacemi jako (SELECT query_stats.query_hash AS QueryHash, SUM(query_stats.total_worker_time, AScury_stat_time ) / SPU 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_statsworker_time) AS Min. 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(total_country_stats.exe).exe 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, ASlapE_logical_reads ) / ASlapE Total_statstoadsto (celkem_počet_dotazů)UMtalquegRecution Av. 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 Av gElapsedTime, MIN(query_stats.creation_time ) AS MinCreationTime, MAX(query_stats.last_execution_time ) AS LastExecuteTimeFROM (SELECT QS.query_hash ,QS.total_worker_time,QS.execution_time,QS.min_worker_worker_time,QS.min_worker_worker. celkem ,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_start_offset) 2) + 1) JAKO text příkazu OD sys.dm_exec_query_stats JAKO KŘÍŽOVÉ POUŽITÍ QS sys.dm_exec_sql_text(QS.sql_handle) jako ST) jako query_statsWHERE vykonání_počet> 1and last_execution_time>=dateadget. )vyberte QueryHash, AvgCPU_Time, ExecutionCount, TotalWorkerTime, StatementText, MinWorkerTime, MaxWorkerTime, TotalPhysicalReads, MinPhysicalReads, MaxPhysicalReads, AvgPhysicalReads, TotalLogicalWrites, MingLogicalWrites, MaxLogicalReads,LogicalReads,LogicalReads,LogicalReads,LogicalReads,LogicalReads AvgLogicalReads, TotalElapsedTime, MinElapsedTime, MaxElapsedTime, AvgElapsedTime, MinCreationTime, LastExecuteTimefrom infoGOZde se používají následující systémové dotazy:sys.dm_exec_query_stats a sys.dm_exec_sql_text.
2.2. Pro uložené procedury:POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE zobrazení [srv].[vProcedureExecInfo] jako s informacemi jako (SELECT procedure_stats.database_id AS databaze_id, procedura_stats.object_id AS id_objektu(typ_procedury, typ_statu_procedury, MIN(typ)procedura_stats .total_worker_time ) / SUM(procedure_stats.execution_time) AS AvgCPU_Time, SUM(procedure_stats.execution_count ) AS ExecutionCount, SUM(procedure_stats.total_worker_time ) AS TotalWorkerTime, MIN(Procedure_Procedure_Textworkstats). (procedure_stats.max_worker_time ) AS MaxWorkerTime, SUM(procedure_stats.total_physical_reads) AS TotalPhysicalReads, MIN(procedure_stats.min_ph ysical_reads) jako minphysicalreads, max (procedure_stats.max_physical_reads) jako maxPhysicalReads, součet (procedure_stats.total_physical_reads) / součet (procedure_stats.execution_count) procedure_stats.max_logical_writes ) AS MaxLogicalWrites, SUM(stats_procedury.total_logic_writes) / SUM(stats_procedury.počet_exekucí) AS AvgLogicalWrites, SUM(procedure_stats.total_logical_reads,total_logical_reads ) AS TotalLogical_adsminlogical_statsLogReread_max. SUM(procedure_stats.total_logical_reads ) / SUM(procedure_stats.execution_count) AS AvgLogicalReads, SUM(procedure_stats.total_elap sed_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 ) / AScastats procedure_stats.last_execution_time ) AS LastExecuteTimeFROM (SELECT QS.database_id ,QS.object_id ,QS.type ,QS.total_worker_time ,QS.execution_count ,QS.min_worker_time ,QS.max_worker_time,Q_physical_read_time,Q_physital_read_time. 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.total_elapsed_time ,QSmcued_time ,Qexe_text_exestla. QS CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) jako ST) jako procedura_statsWHERE počet_provedení> 1 a čas_posledního_exekuce>=dateadd(hodina,-3,getdate())GROUP BY_id_databaze,id_objektu)vyber_id_databaze,AvuntgCPU,typ TotalWorkerTime, ProcedureText, MinWorkerTime, MaxWorkerTime, TotalPhysicalReads, MinPhysicalReads, MaxPhysicalRead s, AvgPhysicalReads, TotalLogicalWrites, MinLogicalWrites, MaxLogicalWrites, AvgLogicalWrites, TotalLogicalReads, MinLogicalReads, MaxLogicalReads, AvgLogicalReads, TotalElapsedTime, MinElapsedTime, MaxElapsedTime, MinCGOElapsedTime, MinCGOElapsedTimefromZde se používají následující systémové dotazy:sys.dm_exec_Procedure_stats a sys.dm_exec_sql_text.
2.3. Pro spouštěče:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE zobrazení [srv].[vTriggerExecInfo] jako s informacemi jako (SELECT procedure_stats.database_id AS database_id, procedure_stats.object_id AS objectUM_id, MIN(type)procedure_stats type, MIN(type)procedure_stats .total_worker_time ) / SUM(procedure_stats.execution_time) AS AvgCPU_Time, SUM(procedure_stats.execution_count ) AS ExecutionCount, SUM(procedure_stats.total_worker_time ) AS TotalWorkerTime, MIN(Procedure_Procedure_Textworkstats). (procedure_stats.max_worker_time ) AS MaxWorkerTime, SUM(procedure_stats.total_physical_reads) AS TotalPhysicalReads, MIN(procedure_stats.min_phys ical_reads ) AS MinPhysicalReads, MAX(procedure_stats.max_physical_reads ) AS MaxPhysicalReads, SUM(procedure_stats.total_physical_reads) / SUM(procedure_stats.execution_count) ASWgswriteReads,SUM_Logical_procedure_stats,SUMsLogicaltotal_processrites) procedure_stats.max_logical_writes ) AS MaxLogicalWrites, SUM(stats_procedury.total_logic_writes) / SUM(stats_procedury.počet_exekucí) AS AvgLogicalWrites, SUM(procedure_stats.total_logical_reads,total_logical_reads ) AS TotalLogical_adsminlogical_statsLogReread_max. SUM(procedure_stats.total_logical_reads ) / SUM(procedure_stats.execution_count) AS AvgLogicalReads, SUM(procedure_stats.total_elapse d_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 ) / AScastats procedure_stats.last_execution_time ) AS LastExecuteTimeFROM (SELECT QS.database_id ,QS.object_id ,QS.type ,QS.total_worker_time ,QS.execution_count ,QS.min_worker_time ,QS.max_worker_time,Q_physical_read_time,Q_physital_read_time. total_logical_writes, QS S CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) jako ST) jako procedure_statsWHERE vykonání_počet> 1 a last_execution_time>=dateadd(hodina,-3,getdate())GROUP BY database_id,object_id)select database_id, objectCPUerWorkTime, Type, Exunt , ProcedureText, MinWorkerTime, MaxWorkerTime, TotalPhysicalReads, MinPhysicalReads, MaxPhysicalReads, AvgPhysicalReads, TotalLogicalWrites, MinLogicalWrites, MaxLogicalWrites, AvgLogicalWrites, TotalLogicalReads, MinLogicalReads, MaxLogicalReads, AvgLogicalReads, TotalElapsedTime, MinElapsedTime, MaxElapsedTime, AvgCachGOElapedTime, MinCachGOElapsedTime, MinCachGOElapsedTime, MinCachGOElapsedTimeZde se používají následující systémové dotazy:sys.dm_exec_trigger_stats a sys.dm_exec_sql_text.
3. Vytváření uložených procedur pro sběr informací.
3.1. Pro dotazy:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER POSTUP PŘI VYTVOŘENÍ [srv].[InsertForSQL_StatementExecStat] @koef decimal(12,2)=0,0 – koeficient sběru – je vybrán experimentálním způsobem pro přesnější sběr případech můžeme dát 0,0, -- pokud frekvence běhu sběru nepřesáhne 5 minut. --Přesnost výpočtu závisí na frekvenci sběru a koeficientu sběru. --Čím častěji probíhá sběr, tím menší vliv má koeficient sběru.ASBEGIN SET NOCOUNT ON; deklare @avgcpu_time bigint,@maxavgcpu_time bigint,@avgtotalworkertrime bigint,@maxtotalwortime bigint,@avgavgeLapseTime bigint,@maxAvgeLapseseTime bigtint@avgc -time@avgc -time@avgc -time@avgCTu (AvgCPu =Avg) AvgTotalWorkerTime =AVG(TotalWorkerTime), @MaxTotalWorkerTime =max(TotalWorkerTime), @AvgAvgElapsedTime =AVG(AvgElapsedTime), @MaxAvgElapsedTime =max(AvgElapsedTime), @AvgTotalElapsedTime =AVG(TotalExlapsedTime) from TotalExTotalElapsedTime, from TotalExTotalElapsedTime; vložit do srv.SQL_StatementExecStat ( [InsertDate] ,[QueryHash] ,[ExecutionCount] ,[TotalWorkerTime] ,[StatementText] ,[TotalElapsedTime]) select getdate() ,[QueryHash] ,[ExecutionCount] ,[TotalWorkTime] ,[TotalElapsedTime] ze srv.vStatementExecInfo where(AvgCPU_Time> @AvgCPU_Time + @koef * (@MaxAvgCPU_Time - @AvgCPU_Time)) nebo (TotalWorkerTime> @AvgTotalWorkerTime + @koef *) nebo lapTotalTimeer>AvgTotalWorkTime * (@lapTotalTimeer + @koef *) orsed>AvgTotalWorkTime + @koef * (@MaxAvgElapsedTime - @AvgAvgElapsedTime)) nebo (TotalElapsedTime> @AvgTotalElapsedTime + @koef * (@MaxTotalElapsedTime - @AvgTotalElapsedTime));ENDGO3.2. Pro uložené procedury:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE PROCEDURE [srv].[InsertForProcedureExecStat] @koef decimal(12,2)=0.0 --kolekční koeficient --je vybrán experimentálním způsobem pro přesnější sběr, --in ve většině případů můžeme dát 0,0, -- pokud frekvence běhu sběru nepřesáhne 5 minut. --Přesnost výpočtu závisí na frekvenci sběru a koeficientu sběru. --Čím častěji probíhá sběr, tím menší vliv má koeficient sběru.ASBEGIN SET NOCOUNT ON; deklarovat @AvgCPU_Time bigint , @MaxAvgCPU_Time bigint , @AvgTotalWorkerTime bigint , @MaxTotalWorkerTime bigint , @AvgAvgElapsedTime bigint , @MaxAvgElapsedTime bigint ,@AvgTotalElapsedxTotalElapsed bigint ,@Ma vyberte @AvgCPU_Time =AVG (AvgCPU_Time), @MaxAvgCPU_Time =max (AvgCPU_Time), @AvgTotalWorkerTime =AVG (celkový čas pracovníka), @MaxTotalWorkerTime =max (celkový čas pracovníka), @AvgAvgElapsedTime =AVG (Avg) uplynulý čas =vgElapsedTime max.AvgE AvgTotalElapsedTime =AVG(TotalElapsedTime), @MaxTotalElapsedTime =max(TotalElapsedTime) ze srv.vProcedureExecInfo; vložit do srv.SQL_ProcedureExecStat ( [InsertDate] ,database_id ,object_id ,[ExecutionCount] ,[TotalWorkerTime] ,[TotalElapsedTime] ,[TotalPhysicalReads] ,[TotalLogicalReads] ,[TotalLogicalReads],[TotalLogicalReads],[TotalLogicalReads],[TotalLogicalReads],[TotalLogicalWrites]id_datu_výběrového_zápisu ,[TotalWorkerTime] ,[TotalElapsedTime] ,[TotalPhysicalReads] ,[TotalLogicalReads] ,[TotalLogicalWrites] z srv.vProcedureExecInfo where(AvgCPU_Time> @AvgCPU_Time @AvgCPU_Time + @koef * +Time>Time>Ctal -CPU_Time koef * (@MaxTotalWorkerTime - @AvgTotalWorkerTime)) nebo (AvgElapsedTime> @AvgAvgElapsedTime + @koef * (@MaxAvgElapsedTime - @AvgAvgElapsedTime)) nebo (TotalElapsedTime> @AvgTotalElapsedTime@EtalxMa @ko apsedTime - @AvgTotalElapsedTime));ENDGO3.3. Pro spouštěče:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER POSTUP PŘI VYTVOŘENÍ [srv].[InsertForTriggerExecStat] @koef decimal(12,2)=0,0 --koeficient shromažďování --je vybrán experimentálním způsobem pro přesnější sběr, --in ve většině případů můžeme dát 0,0, -- pokud frekvence běhu sběru nepřesáhne 5 minut. --Přesnost výpočtu závisí na frekvenci sběru a koeficientu sběru. --Čím častěji probíhá sběr, tím menší vliv má koeficient sběru.ASBEGIN SET NOCOUNT ON; deklare @avgcpu_time bigint,@maxavgcpu_time bigint,@avgtotalworkertrime bigint,@maxtotalwortime bigint,@avgavgeLapseTime bigint,@maxAvgeLapseseTime bigtint@avgc -time@avgc -time@avgc -time@avgCTu (AvgCPu =Avg) AvgTotalWorkerTime =AVG(TotalWorkerTime), @MaxTotalWorkerTime =max(TotalWorkerTime), @AvgAvgElapsedTime =AVG(AvgElapsedTime), @MaxAvgElapsedTime =max(AvgElapsedTime), @AvgTotalElapsedTime =AVG(TotalExlapsedTime) zMaxTotalElapsedTime =AVG(TotalExlapsedTime) odMaxTotalElapsedTime; vložit do srv.SQL_TriggerExecStat ( [InsertDate] ,database_id ,object_id ,[ExecutionCount] ,[TotalWorkerTime] ,[TotalElapsedTime]) vybrat getdate() ,database_id ,object_id ,[ExecutionCount] ,[TotalrvElapggerTime] ,[TotalrvElapggerTime] ,[TotalrvElapggerTime] kde(AvgCPU_Time> @AvgCPU_Time + @koef * (@MaxAvgCPU_Time - @AvgCPU_Time)) nebo (TotalWorkerTime> @AvgTotalWorkerTime + @koef * (@MaxTotalWorkerTime - @AvgTotalWorkerTime)@AvgTotalWorkerTime)@+AvgAvgElapsedTime -AvgkovgElapsedTime -AvgkovgElapsedTime @AvgAvgElapsedTime)) nebo (TotalElapsedTime> @AvgTotalElapsedTime + @koef * (@MaxTotalElapsedTime - @AvgTotalElapsedTime));ENDGO4. Vytvoření pohledu pro výstup informací.
4.1. Pro dotazy:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE VIEW [srv].[vStatementExecTotalInfo]asselect ExecutionCount as Num ,TotalWorkerTime as TotalWorkerTime ,TotalElapsedTime as TotalElapsedTime/0WorkerTime,Acon00 convert(decimal(8,2),AvgElapsedTime/1000000.) as AvgElapsedSec ,... ,QueryHash ,StatementText z [SRV].[srv].[vStatementExecInfo];GO4.2. Pro uložené procedury:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE VIEW [srv].[vProcedureExecTotalInfo]jako vyberte ExecutionCount jako Num ,TotalWorkerTime jako TotalWorkerTime ,TotalElapsedTime jako TotalElapsedTime/Av)con0 ,convert(decimal(8,2),AvgElapsedTime/1000000.) jako AvgElapsedSec ,... ,id_databaze ,id_objektu ,jméno_db(id_databáze) jako název_DB ,NÁZEV_OBJECT_SCHEMA_NAME(id_objektu, id_objektu_databáze,_id_objektu) jako název_objektu_Schéma,_id_objektu_Procedura z [SRV].[srv].[vProcedureExecInfo];GO4.3. Pohledy pro spouštěče se vytvářejí podobným způsobem (pokud je to požadováno). Pokud jde o mě, nepotřebuji trasovací spouštěče, protože pokud se vyskytnou nějaké problémy se spouštěči, zobrazí je provádění uložených procedur a dotazů.
Následující dva parametry mají zásadní význam pro implementované pohledy:
- AvgWorkerSec – doba provedení dotazu v sekundách.
- AvgElapsedSec — doba čekání nebo doba čekání+AvgWorkerSec.
Pokud jde o výsledky názorů, je důležitá následující rovnost:
AvgWorkerSec=AvgElapsedSec
- AvgWorkerSec>AvgElapsedSec – zde něco silně zatěžuje procesor v okamžiku provádění dotazu (jak se ukázalo, probíhala kontrola antivirového softwaru; může to být také chyba paralelního plánu).
- AvgWorkerSec
Pokud je dodrženo AvgWorkerSec=AvgElapsedSec, dlouhá doba provádění souvisí se samotným dotazem a dobou jeho provádění.
Jaké je kritérium dlouhého provádění dotazu?
Na tuto otázku neexistuje absolutní odpověď. Záleží na tom, co dotaz dělá, kde a jak se používá atd.
Mám následující hodnocení pro dotazy ad hoc a uložené procedury:
- Až 0,5 – dobré pro uložené procedury, žádné problémy (žádné čekání na provedení).
- Až 0,1 – dobré pro dotazy, žádné problémy (žádné čekání na provedení).
- 0,5 — 1,0 – špatné pro uložené procedury, dochází k problémům (neexistují žádné čekání na spuštění, které by uživatel viděl, ale stále existují a vyžadují řešení).
- 0,1 — 0,5 — špatné pro dotazy, existují problémy (neexistují žádné čekání na spuštění, které by uživatel viděl, ale stále existují a vyžadují řešení).
- Více než 1.0 – špatné pro uložené procedury, dochází k problémům (je vysoká pravděpodobnost, že existují čekání, která jsou pro uživatele viditelná, problém vyžaduje okamžité řešení).
- Více než 0,5 – špatné pro dotazy, dochází k problémům (je vysoká pravděpodobnost, že existují čekání, která jsou viditelná pro uživatele, problém vyžaduje okamžité řešení).
U nead hoc dotazů (nahrání dat, načtení dat) je výše uvedené vyhodnocení vybíráno individuálně. Obvykle výrazně překračuje hodnocení pro ad hoc dotazy a uložené procedury.
Pokud veškerý software pracuje prostřednictvím uložených procedur, můžete sledovat pouze uložené procedury, protože práce s dotazy vždy ovlivňuje práci uložených procedur. Proto se spokojme s analýzou provádění uložených procedur.
Vytvořme systém pro shromažďování informací o nejtěžších uložených procedurách pro následnou analýzu a spuštění autotrace podle následujícího algoritmu:
1. Vytvoření tabulky pro ukládání informací:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE TABLE [srv].[SQL_TopProcedureExecStat]( [Row_GUID] [jednoznačný identifikátor] NOT NULL, [SERVER] [nvarchar](255)LLintB NOT NULL , [OBJECT_ID] [int] NOT NULL, [ExecutionCount] [bigint] NOT NULL, [TotalWorkerTime] [bigint] NULL, [TotalElapsedTime] [bigint] NULL, [Func] [decimal](8, 2) NULL, [AvgWorkerSec ] [desítkové](8, 2) NULL, [AvgElapsedSec] [desítkové](8, 2) NULL, [DB_NAME] [nvarchar](255) NULL, [SCHEMA_NAME] [nvarchar](255) NULL, [NÁZEV_OBJEKTU] [ nvarchar](255) NULL, [InsertUTCDate] [datetime] NOT NULL, [TotalPhysicalReads] [bigint] NULL, [TotalLogicalReads] [bigint] NULL, [TotalLogicalWrites] [bigint] NULL, [AvgLogPhysical]Reads] ] [bigint] NULL, [AvgLogicalWrites] [bigint] NULL, [CategoryName] [nvarchar](255) NULL, CONSTRAINT [PK_ SQL_TopProcedureExecStat] PRIMÁRNÍ KLÍČ V CLUSTERU ( [Row_GUID] ASC)WITH (PAD_INDEX =VYPNUTO, STATISTICS_NORECOMPUTE =VYPNUTO, IGNORE_DUP_KEY =VYPNUTO, ALLOW_ROW_LOCKS =ZAPNUTO, ALLOW_PAGE_LOCKRyPRIMA ONBLEec)sPRIMA ZAPNUTO)[t]Prot ] PŘIDAT OMEZENÍ [DF_SQL_TopProcedureExecStat_Row_GUID] VÝCHOZÍ (newid()) PRO [Row_GUID]TABULKA BRANKŮ [srv].[SQL_TopProcedureExecStat] PŘIDAT OMEZENÍ [DF_SQL_TopProcedureSERExecStat_SERVER] ADD_SQL] DEFAULT [Procedurarv.TERSERVER] ADDFA OMEZENÍ [DF_SQL_TopProcedureExecStat_InsertUTCDate] VÝCHOZÍ (getutcdate()) PRO [InsertUTCDate]PŘEJÍT2. Vytvoření uložené procedury pro sběr informací:
POUŽÍVEJTE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE PROCEDURE [srv].[InsertTopProcedureExecStat] @top tinyint=24 – počet dní pro ukládání záznamů ,@CategoryName nvarchar(255) aSET SET pro kategorii 'A NA; INSERT INTO [srv].[SQL_TopProcedureExecStat] ([DB_ID] ,[OBJECT_ID] ,[ExecutionCount] ,[TotalWorkerTime] ,[TotalElapsedTime] ,[AvgWorkerSec] ,[AvgElapsedSec] ,[DB_NAME] ,[DB_NAME] ,[DB_NAME] Vložit UTCDate ,CategoryName ,TotalPhysicalReads ,TotalLogicalReads ,TotalLogicalWrites ,AvgPhysicalReads ,AvgLogicalReads ,AvgLogicalWrites) select top(@top) [database_id,sed,lap]TotalTime] ,[object,lap]TtalTime,Eec [DB_NAME] ,[SCHEMA_NAME] ,[PROCEDURE_NAME] ,InsertUTCDate ,C ategoryName ,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 ,AvgL ogicalWrites 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 [email protected]); declare @diff [email protected]@top;;with tbl_del as( select Row_GUID from [srv].[SQL_TopProcedureExecStat] where InsertUTCDate0) begin;with tbl_del as( select top(@diff) Row_GUID from [srv].[SQL_TopProcedureExecStat] where [email protected] order by case @CategoryName when 'TotalWorkerTime' then TotalWorkerTime when 'TotalElapsedTime' then TotalElapsedTime when 'AvgWorkerSec' then AvgWorkerSec when 'AvgElapsedSec' then AvgElapsedSec when 'TotalPhysicalReads' then TotalP hysicalReads 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 [email protected] 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 [email protected] 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 Tot alLogicalReads 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 [email protected] 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 [email protected] 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 [email protected] 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 [email protected] and AvgElapsedSec =@AvgElapsedSec order by InsertUTCDate desc ) delete from [srv].[SQL_TopProcedureExecStat] where Row_GUID in (select Row_GUID from tbl_del); end endENDGO 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 queriesexec [srv].[InsertForTriggerExecStat]; --collecting information about executed triggersexec [srv].[InsertForProcedureExecStat]; --collecting information about executed stored procedures--collecting information about the most heavy executed stored procedures, according to the criteriaexec [srv].[InsertTopProcedureExecStat] @[email protected], @CategoryName='AvgWorkerSec';exec [srv].[InsertTopProcedureExecStat] @[email protected], @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 indicatordeclare @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])>[email protected]_red or avg([AvgWorkerSec])>[email protected]_red)) begin --running autorace exec .[srv].[AutoTrace]; endThe auto-trace stored procedure is implemented on an individual basis. Například:
USE [DATABASE_NAME]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE 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 messagesASBEGIN 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)[email protected]@[email protected]_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=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)[email protected]_delay_hour_str+':'[email protected]_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 @[email protected]+'.trc'; --here, we need to insert code to delete the trace file declare @str_title nvarchar(max)='There was auto trace on the server'[email protected]@servername, @str_pred_mess nvarchar(max)='На '[email protected]@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, ''); endENDGO For more information on setting trace, refer to How to:Create a Trace (Transact-SQL).
Závěr
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
References
- 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)