کدام برنامه را برای مانیتور کردن SQL پیشنهاد میکنید؟
نویسنده: هادی مزارعی
تاریخ: ۱۴۰۵/۰۶/۲۹ ۱۱:۰۵
آدرس: www.dntips.ir
sys.dm_exec_procedure_stats میتوانید مجموع، میانگین و آخرین زمان اجرا و منابع مصرفی را ببینید:SELECT
DB_NAME(ps.database_id) AS DatabaseName,
OBJECT_SCHEMA_NAME(ps.object_id, ps.database_id) AS SchemaName,
OBJECT_NAME(ps.object_id, ps.database_id) AS ProcedureName,
ps.execution_count AS TotalExecutions,
-- زمان کل و میانگین زمان اجرا (به میلیثانیه)
(ps.total_elapsed_time / 1000.0) AS TotalDuration_ms,
(ps.total_elapsed_time / ps.execution_count / 1000.0) AS AvgDuration_ms,
(ps.last_elapsed_time / 1000.0) AS LastDuration_ms,
-- زمان پردازنده (CPU Time به میلیثانیه)
(ps.total_worker_time / ps.execution_count / 1000.0) AS AvgCPUTime_ms,
-- مصرف حافظه و دیسک (Logical Reads بر حسب Pageهای 8 کیلوبایتی)
-- این شاخص دقیقترین معیار فشار روی حافظه Cache است:
(ps.total_logical_reads / ps.execution_count) AS AvgLogicalReads_Pages,
(ps.total_logical_reads / ps.execution_count * 8 / 1024.0) AS AvgMemoryRead_MB,
(ps.last_logical_reads * 8 / 1024.0) AS LastMemoryRead_MB,
-- آخرین باری که اجرا شده
ps.last_execution_time
FROM
sys.dm_exec_procedure_stats ps
WHERE
ps.database_id = DB_ID('YourDatabaseName') -- نام دیتابیس خود را اینجا وارد کنید
ORDER BY
AvgDuration_ms DESC;نکته در مورد حافظه مصرفی: در دیتابیسهای رابطهای، مصرف حافظه در طول اجرای پروسیجر معمولاً با شاخص Logical Reads (تعداد صفحات ۸ کیلوبایتی خواندهشده از Buffer Cache) سنجیده میشود. همچنین برای پروسیجرهایی که نیاز به مرتبسازی سنگین دارند، ستون total_grant_kb (میزان حافظه رزرو شده یا Memory Grant) در نسخههای جدید SQL Server در همین DMV در دسترس است.محدودیت DMV: اگر سرویس SQL Server ریاستارت شود یا پروسیجر تغییر کند، آمار کش پاک میشود.
ALTER DATABASE [YourDatabaseName] SET QUERY_STORE = ON;
module_end (هنگام اتمام اجرای هر SP تریگر میشود)duration، cpu_time، logical_reads و memory_grantsqlserver.module_end استفاده میکنیم. این رویداد دقیقاً در لحظه پایان اجرای SP شلیک میشود و مواردی چون زمان صرفشده (Duration)، زمان پردازنده (CPU) و دسترسی به حافظه (Logical Reads) را ثبت میکند.YourDatabaseName قرار دهید تا رویدادهای مربوط به دیتابیسهای دیگر فیلتر شوند:CREATE EVENT SESSION [Track_SP_Execution] ON SERVER
ADD EVENT sqlserver.module_end(
ACTION(
sqlserver.database_name,
sqlserver.client_app_name,
sqlserver.username,
sqlserver.sql_text
)
WHERE (
[package0].[equal_uint64]([sqlserver].[database_id], DB_ID(N'YourDatabaseName')) -- فیلتر روی دیتابیس مشخص
AND [object_type] = 'P ' -- فیلتر فقط برای Stored Procedureها
)
)
ADD TARGET package0.event_file(
SET filename = N'Track_SP_Execution.xel', -- مسیر ذخیره فایل گزارش
max_file_size = (50), -- حداکثر حجم هر فایل: ۵۰ مگابایت
max_rollover_files = (5) -- نگهداری تا حداکثر ۵ فایل چرخشی
)
WITH (
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 5 SECONDS,
TRACK_CAUSALITY = OFF
);
GOALTER EVENT SESSION [Track_SP_Execution] ON SERVER STATE = START; GO
SELECT
event_data.value('(/event/@timestamp)[1]', 'datetime2') AS [EndTime_UTC],
event_data.value('(/event/data[@name="object_name"]/value)[1]', 'sysname') AS [ProcedureName],
-- زمان کل اجرا (میکروثانیه به میلیثانیه تبدیل شده)
event_data.value('(/event/data[@name="duration"]/value)[1]', 'bigint') / 1000.0 AS [Duration_ms],
-- زمان مصرف CPU (میلیثانیه)
event_data.value('(/event/data[@name="cpu_time"]/value)[1]', 'bigint') / 1000.0 AS [CpuTime_ms],
-- مصرف حافظه کش (تعداد صفحات ۸ کیلوبایتی و تبدیل به مگابایت)
event_data.value('(/event/data[@name="logical_reads"]/value)[1]', 'bigint') AS [LogicalReads_Pages],
(event_data.value('(/event/data[@name="logical_reads"]/value)[1]', 'bigint') * 8) / 1024.0 AS [MemoryRead_MB],
-- تعداد نوشتن روی دیسک
event_data.value('(/event/data[@name="writes"]/value)[1]', 'bigint') AS [Writes],
event_data.value('(/event/action[@name="username"]/value)[1]', 'sysname') AS [UserName],
event_data.value('(/event/action[@name="client_app_name"]/value)[1]', 'nvarchar(max)') AS [ApplicationName]
FROM (
SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file(N'Track_SP_Execution*.xel', NULL, NULL, NULL)
) AS xed
ORDER BY [EndTime_UTC] DESC;module_end را پیدا و اضافه کنید.event_file را انتخاب کنید تا لاگها روی دیسک بنویسند.-- توقف موقت ALTER EVENT SESSION [Track_SP_Execution] ON SERVER STATE = STOP; -- یا حذف کامل سشن DROP EVENT SESSION [Track_SP_Execution] ON SERVER;
.xel در یک جدول دائمی، روال استاندارد ساخت یک جدول لاگ، یک پروسیجر انتقال داده (ETL) با قابلیت جلوگیری از ثبت رکوردهای تکراری، و زمانبندی آن توسط SQL Server Agent است.USE [YourDatabaseName];
GO
CREATE TABLE dbo.SP_ExecutionHistory (
HistoryID BIGINT IDENTITY(1,1) PRIMARY KEY,
EventTimestampUTC DATETIME2(3) NOT NULL,
ProcedureName SYSNAME NOT NULL,
DurationMs DECIMAL(18, 2) NOT NULL,
CpuTimeMs DECIMAL(18, 2) NOT NULL,
LogicalReads BIGINT NOT NULL,
MemoryReadMB DECIMAL(18, 2) NOT NULL,
PhysicalWrites BIGINT NOT NULL,
UserName SYSNAME NULL,
ApplicationName NVARCHAR(500) NULL,
InsertDate DATETIME2(3) DEFAULT SYSUTCDATETIME()
);
-- ایجاد ایندکس روی ستون زمان برای جلوگیری از تکرار و سرعت در جستجوها
CREATE NONCLUSTERED INDEX IX_SP_ExecutionHistory_Timestamp
ON dbo.SP_ExecutionHistory(EventTimestampUTC);
GOUSE [YourDatabaseName];
GO
CREATE OR ALTER PROCEDURE dbo.usp_Archive_SP_Executions
AS
BEGIN
SET NOCOUNT ON;
-- پیدا کردن زمان آخرین رکورد درجشده
DECLARE @LastRecordedTime DATETIME2(3);
SELECT @LastRecordedTime = ISNULL(MAX(EventTimestampUTC), '1900-01-01')
FROM dbo.SP_ExecutionHistory;
-- خواندن دادهها از فایلهای XEL و درج در جدول
INSERT INTO dbo.SP_ExecutionHistory (
EventTimestampUTC,
ProcedureName,
DurationMs,
CpuTimeMs,
LogicalReads,
MemoryReadMB,
PhysicalWrites,
UserName,
ApplicationName
)
SELECT
parsed.EventTimestampUTC,
parsed.ProcedureName,
parsed.DurationMs,
parsed.CpuTimeMs,
parsed.LogicalReads,
(parsed.LogicalReads * 8.0) / 1024.0 AS MemoryReadMB,
parsed.PhysicalWrites,
parsed.UserName,
parsed.ApplicationName
FROM (
SELECT
event_data.value('(/event/@timestamp)[1]', 'datetime2(3)') AS EventTimestampUTC,
event_data.value('(/event/data[@name="object_name"]/value)[1]', 'sysname') AS ProcedureName,
event_data.value('(/event/data[@name="duration"]/value)[1]', 'bigint') / 1000.0 AS DurationMs,
event_data.value('(/event/data[@name="cpu_time"]/value)[1]', 'bigint') / 1000.0 AS CpuTimeMs,
event_data.value('(/event/data[@name="logical_reads"]/value)[1]', 'bigint') AS LogicalReads,
event_data.value('(/event/data[@name="writes"]/value)[1]', 'bigint') AS PhysicalWrites,
event_data.value('(/event/action[@name="username"]/value)[1]', 'sysname') AS UserName,
event_data.value('(/event/action[@name="client_app_name"]/value)[1]', 'nvarchar(500)') AS ApplicationName
FROM (
SELECT CAST(event_data AS XML) AS event_data
-- در صورت لزوم مسیر کامل فایل را مشخص کنید: N'C:\LogPath\Track_SP_Execution*.xel'
FROM sys.fn_xe_file_target_read_file(N'Track_SP_Execution*.xel', NULL, NULL, NULL)
) AS raw_xed
) AS parsed
WHERE parsed.EventTimestampUTC > @LastRecordedTime
AND parsed.ProcedureName IS NOT NULL
ORDER BY parsed.EventTimestampUTC ASC;
END;
GODBA - Archive SP Execution Logs).Run usp_Archive_SP_ExecutionsEXEC dbo.usp_Archive_SP_Executions; را درج کنید.DELETE FROM dbo.SP_ExecutionHistory WHERE EventTimestampUTC < DATEADD(DAY, -60, SYSUTCDATETIME());
dbo.SP_ExecutionHistory، سناریوهای گزارشگیری را میتوان بر اساس نوع بار کاری (مدت زمان، مصرف RAM، پردازنده و نوسان کارایی) دستهبندی کرد.SELECT TOP 10
ProcedureName,
COUNT(*) AS ExecutionCount,
-- زمان بر حسب ثانیه
CAST(AVG(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS AvgDuration_Sec,
CAST(MIN(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS MinDuration_Sec,
CAST(MAX(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS MaxDuration_Sec,
CAST(AVG(CpuTimeMs) / 1000.0 AS DECIMAL(10, 2)) AS AvgCpu_Sec,
CAST(AVG(MemoryReadMB) AS DECIMAL(10, 2)) AS AvgMemory_MB
FROM dbo.SP_ExecutionHistory
WHERE EventTimestampUTC >= DATEADD(DAY, -7, SYSUTCDATETIME()) -- بازه ۷ روز اخیر
GROUP BY ProcedureName
HAVING COUNT(*) >= 5
ORDER BY AvgDuration_Sec DESC;SELECT TOP 10
ProcedureName,
COUNT(*) AS ExecutionCount,
-- مجموع حافظه خوانده شده از دیسک/کش در کل دوره
CAST(SUM(MemoryReadMB) / 1024.0 AS DECIMAL(10, 2)) AS TotalMemoryRead_GB,
CAST(AVG(MemoryReadMB) AS DECIMAL(10, 2)) AS AvgMemoryRead_MB,
CAST(MAX(MemoryReadMB) AS DECIMAL(10, 2)) AS MaxMemoryRead_MB,
SUM(PhysicalWrites) AS TotalWrites
FROM dbo.SP_ExecutionHistory
WHERE EventTimestampUTC >= DATEADD(DAY, -7, SYSUTCDATETIME())
GROUP BY ProcedureName
ORDER BY TotalMemoryRead_GB DESC;SELECT TOP 10
ProcedureName,
COUNT(*) AS ExecutionCount,
-- مجموع کل زمانی که CPU درگیر این پروسیجر بوده
CAST(SUM(CpuTimeMs) / 1000.0 / 60.0 AS DECIMAL(10, 2)) AS TotalCpu_Minutes,
CAST(SUM(DurationMs) / 1000.0 / 60.0 AS DECIMAL(10, 2)) AS TotalDuration_Minutes,
CAST(SUM(MemoryReadMB) / 1024.0 AS DECIMAL(10, 2)) AS TotalRead_GB,
-- درصد سهم از کل زمان CPU در کل دیتابیس
CAST(100.0 * SUM(CpuTimeMs) / SUM(SUM(CpuTimeMs)) OVER() AS DECIMAL(5, 2)) AS PctOfTotalCpu
FROM dbo.SP_ExecutionHistory
WHERE EventTimestampUTC >= DATEADD(DAY, -1, SYSUTCDATETIME()) -- ۲۴ ساعت گذشته
GROUP BY ProcedureName
ORDER BY TotalCpu_Minutes DESC;SELECT TOP 10
ProcedureName,
COUNT(*) AS ExecutionCount,
CAST(MIN(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS MinDuration_Sec,
CAST(MAX(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS MaxDuration_Sec,
CAST(AVG(DurationMs) / 1000.0 AS DECIMAL(10, 2)) AS AvgDuration_Sec,
-- نسبت بدترین حالت به بهترین حالت
CASE
WHEN MIN(DurationMs) > 0 THEN CAST(MAX(DurationMs) / MIN(DurationMs) AS DECIMAL(10, 1))
ELSE NULL
END AS PerformanceVarianceRatio
FROM dbo.SP_ExecutionHistory
WHERE EventTimestampUTC >= DATEADD(DAY, -7, SYSUTCDATETIME())
GROUP BY ProcedureName
HAVING COUNT(*) >= 10 AND MIN(DurationMs) > 100 -- حذف اجراهای خطای آنی
ORDER BY PerformanceVarianceRatio DESC;ODBC SQL Server Driver - SQL Server Could not get the memory grant of 4643232 KB because it exceeds the maximum configuation limit in workload group 'default' (2) and resource pool 'default' (2). contact the server administratior to increase the memory usage limit (#8657)
UPDATE STATISTICS YourTableName WITH FULLSCAN;
ORDER BY، DISTINCT و GROUP BY را بررسی کنید. مرتبسازیهای سنگین در حافظه (Sort Spills / Sort Grants) متهم ردیف اول درخواستهای حافظه بالا هستند.WHERE) و اتصالات (JOIN) دارای ایندکس مناسب هستند تا از اسکن کامل جدول (Table Scan) و Hash Joinهای سنگین جلوگیری شود.max_grant_percent محدود کنید تا کوئری اجازه اجرا پیدا کند:SELECT ... FROM ... OPTION (min_grant_percent = 5, max_grant_percent = 25);
نکته: با این کار کوئری به جای درخواست بیش از حد حافظه، در محدوده مشخصی اجرا میشود (حتی اگر نیاز باشد دادهها در tempdb اسپیلیت شوند)، و ارور ۸۶۵۷ رخ نمیدهد.REQUEST_MAX_MEMORY_GRANT_PERCENT) را افزایش دهید تا سقف گروه پیشفرض (Default) شکسته نشود و سایر پروسههای سرور دچار کمبود منابع نشوند.REQUEST_MAX_MEMORY_GRANT_PERCENT سقف مجاز حافظه برای یک کوئری را افزایش میدهید (بهطور پیشفرض ۲۵٪ است که در اینجا مثلاً به ۵۰٪ تغییر میدهیم):USE master;
GO
-- ۱. ساخت Resource Pool با سقف حافظه کلی مجاز
CREATE RESOURCE POOL HeavyBatchPool
WITH (
MIN_MEMORY_PERCENT = 0,
MAX_MEMORY_PERCENT = 80 -- حداکثر ۸۰ درصد از رم کل سرور در اختیار این Pool باشد
);
GO
-- ۲. ساخت Workload Group و تنظیم سقف درخواست حافظه برای یک کوئری
CREATE WORKLOAD GROUP HeavyBatchGroup
WITH (
REQUEST_MAX_MEMORY_GRANT_PERCENT = 50 -- مجاز به دریافت تا ۵۰٪ حافظه Pool
)
USING HeavyBatchPool;
GOsys.dm_resource_governor_workload_groups ستون request_max_memory_grant_percent باید مقدار ۵۰ را برای گروه جدید نمایش دهد.master بنویسید که تعیین کند ارتباط ورودی باید به کدام Workload Group متصل شود. میتوانید بر اساس نام کاربر (SUSER_NAME()) یا نام کلاینت (APP_NAME()) فیلتر کنید:USE master;
GO
CREATE OR ALTER FUNCTION dbo.rgClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @GroupName sysname;
-- اگر کاربر خاصی بود (مثلاً کاربری که این SP سنگین را اجرا میکند)
IF SUSER_NAME() = 'ReportRunnerUser'
SET @GroupName = 'HeavyBatchGroup';
-- یا بر اساس Application Name در کانکشن استرینگ
ELSE IF APP_NAME() = 'MonthlyHeavyBatchApp'
SET @GroupName = 'HeavyBatchGroup';
ELSE
SET @GroupName = 'default';
RETURN @GroupName;
END;
GOSELECT dbo.rgClassifier(); به صورت تستی با اجرای EXECUTE AS USER = 'ReportRunnerUser' نام گروه HeavyBatchGroup را برگرداند.USE master; GO -- اتصال تابع طبقهبندی ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = dbo.rgClassifier); GO -- اعمال تغییرات در حافظه سرور ALTER RESOURCE GOVERNOR RECONFIGURE; GO
SELECT is_enabled FROM sys.resource_governor_configuration; باید مقدار 1 را بازگرداند.SELECT
s.session_id,
s.login_name,
s.program_name,
wg.name AS WorkloadGroupName
FROM sys.dm_exec_sessions s
JOIN sys.dm_resource_governor_workload_groups wg
ON s.group_id = wg.group_id
WHERE s.session_id = @@SPID;WorkloadGroupName برابر با HeavyBatchGroup باشد، سقف حافظه مجاز برای کوئریهای این نشست طبق تنظیمات جدید عمل خواهد کرد و خطای ۸۶۵۷ برطرف میشود.RESOURCE_SEMAPHORE)RESOURCE_SEMAPHORE میشود.GROUP_MAX_REQUESTS را محدود کنید تا بیش از ۱ یا ۲ نمونه از این پردازش سنگین بهصورت همزمان شروع نشود.