SQL Server sunucularında CPU kullanımını kontrol altında tutmak, sistem performansı ve kaynak yönetimi açısından kritik öneme sahiptir.
SQL Server sunucularında CPU kullanımını kontrol altında tutmak, sistem performansı ve kaynak yönetimi açısından kritik öneme sahiptir. Özellikle aynı sunucuda birden fazla uygulama çalışıyorsa veya belirli iş yüklerinin tüm sistem kaynaklarını tüketmesini engellemek istiyorsanız, CPU sınırlaması hayati bir gereklilik haline gelir.
Bu yazıda, SQL Server'ın farklı sürümlerinde (Express, Standard, Enterprise) CPU kullanımını nasıl sınırlayabileceğinizi, hangi yöntemlerin hangi durumlarda işe yaradığını ve karşılaşabileceğiniz hataların çözümlerini detaylı olarak inceleyeceğiz.
SQL Server Sürümleri ve CPU Sınırlamaları
SQL Server'ın farklı sürümlerinin CPU kullanımı konusunda farklı yetenekleri ve kısıtlamaları vardır:
Express Edition
- Maksimum CPU: 4 çekirdek veya 1 soket (hangisi daha az ise)
- Maksimum RAM: 1 GB
- Resource Governor: Desteklenmiyor ❌
- CPU Affinity: Desteklenmiyor ❌
- Lisans: Ücretsiz
Standard Edition
- Maksimum CPU: 4 soket veya 24 çekirdek (hangisi daha az ise)
- Maksimum RAM: 128 GB
- Resource Governor: Desteklenmiyor ❌
- CPU Affinity: Destekleniyor ✅
- Lisans: Ücretli
Enterprise Edition
- Maksimum CPU: Sınırsız
- Maksimum RAM: İşletim sistemi maksimumu
- Resource Governor: Tam destek ✅
- CPU Affinity: Tam destek ✅
- Lisans: Ücretli (en pahalı)
Developer Edition
- Özellikler: Enterprise ile tamamen aynı
- Kullanım: Sadece geliştirme/test ortamları için
- Lisans: Ücretsiz
Yöntem 1: Resource Governor (Sadece Enterprise/Developer)
Resource Governor, SQL Server Enterprise ve Developer Edition'larda bulunan, kaynak kullanımını dinamik olarak yönetmenizi sağlayan gelişmiş bir özelliktir.
Resource Governor Nedir?
Resource Governor, üç ana bileşenden oluşur:
- Resource Pool (Kaynak Havuzu): CPU ve bellek limitlerini tanımlar
- Workload Group (İş Yükü Grubu): Resource Pool'a bağlı grup
- Classifier Function (Sınıflandırıcı Fonksiyon): Hangi bağlantının hangi gruba gideceğini belirler
Temel Kullanım
-- 1. Kaynak Havuzu Oluşturma
CREATE RESOURCE POOL limited_cpu_pool
WITH (
MAX_CPU_PERCENT = 90, -- Maksimum %90 CPU kullanımı
MIN_CPU_PERCENT = 10 -- Garanti edilen minimum %10 CPU
);
-- 2. İş Yükü Grubu Oluşturma
CREATE WORKLOAD GROUP limited_workload_group
USING limited_cpu_pool;
-- 3. Yapılandırmayı Aktifleştirme
ALTER RESOURCE GOVERNOR RECONFIGURE;
Classifier Function ile Kullanıcı Bazlı Sınırlama
USE master;
GO
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @workload_group_name sysname;
-- Raporlama kullanıcıları için sınırlama
IF (SUSER_NAME() IN ('rapor_user', 'bi_user', 'ssrs_service'))
SET @workload_group_name = 'limited_workload_group';
-- SSRS (Reporting Services) uygulamaları için
ELSE IF (APP_NAME() LIKE '%ReportServer%')
SET @workload_group_name = 'limited_workload_group';
-- Belirli bir sunucudan gelen bağlantılar için
ELSE IF (HOST_NAME() = 'RAPOR_SUNUCU')
SET @workload_group_name = 'limited_workload_group';
-- Diğer tüm kullanıcılar varsayılan gruba
ELSE
SET @workload_group_name = 'default';
RETURN @workload_group_name;
END;
GO
-- Classifier Function'ı Kaydetme
ALTER RESOURCE GOVERNOR
WITH (CLASSIFIER_FUNCTION = dbo.ResourceGovernorClassifier);
GO
ALTER RESOURCE GOVERNOR RECONFIGURE;
GO
Mesai Saatine Göre Dinamik Sınırlama
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @hour INT = DATEPART(HOUR, GETDATE());
DECLARE @workload_group_name sysname;
-- Mesai saatleri dışında (18:00-08:00) raporlar sınırlansın
IF (@hour >= 18 OR @hour < 8)
SET @workload_group_name = 'limited_workload_group';
ELSE
SET @workload_group_name = 'default';
RETURN @workload_group_name;
END;
GO
Resource Governor İzleme
-- Resource Pool ve Workload Group durumunu görüntüleme
SELECT
rp.name AS pool_name,
rp.max_cpu_percent,
rp.min_cpu_percent,
rp.max_memory_percent,
wg.name AS workload_group_name,
wg.group_max_requests,
wg.importance
FROM sys.dm_resource_governor_resource_pools rp
LEFT JOIN sys.dm_resource_governor_workload_groups wg
ON rp.pool_id = wg.pool_id
WHERE rp.name <> 'internal';
-- Aktif oturumların hangi workload group'ta olduğunu görme
SELECT
s.session_id,
s.login_name,
s.host_name,
s.program_name,
wg.name AS workload_group_name,
rp.name AS resource_pool_name,
rp.max_cpu_percent
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_resource_governor_workload_groups wg
ON s.group_id = wg.group_id
LEFT JOIN sys.dm_resource_governor_resource_pools rp
ON wg.pool_id = rp.pool_id
WHERE s.is_user_process = 1;
-- CPU kullanım istatistikleri
SELECT
pool_id,
name,
total_cpu_usage_ms,
total_cpu_usage_preemptive_ms,
CAST(total_cpu_usage_ms AS DECIMAL(20,2)) / 1000 / 60 AS total_cpu_minutes
FROM sys.dm_resource_governor_resource_pools
WHERE name <> 'internal'
ORDER BY total_cpu_usage_ms DESC;
Resource Governor'ı Kaldırma
-- Önce classifier'ı kaldır
ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL);
ALTER RESOURCE GOVERNOR RECONFIGURE;
-- Workload Group'u sil
DROP WORKLOAD GROUP limited_workload_group;
-- Resource Pool'u sil
DROP RESOURCE POOL limited_cpu_pool;
-- Değişiklikleri uygula
ALTER RESOURCE GOVERNOR RECONFIGURE;
Karşılaşılabilecek Hatalar
Hata 1: Edition Desteklemiyor
Msg 10923, Level 16, State 1
'ALTER RESOURCE GOVERNOR RECONFIGURE' failed.
The resource governor is not available in this edition of SQL Server.
Çözüm: Bu hata Standard veya Express Edition kullandığınızı gösterir. Resource Governor sadece Enterprise/Developer Edition'da çalışır.
Alternatif Yöntemler:
- CPU Affinity (Standard Edition'da çalışır)
- MAXDOP ayarları
- Query hints
Yöntem 2: CPU Affinity (Processor Affinity)
CPU Affinity, SQL Server'ın hangi işlemci çekirdeklerini kullanabileceğini belirler. Bu yöntem Standard ve Enterprise Edition'larda çalışır.
T-SQL ile CPU Affinity Ayarlama
-- 20 CPU'luk bir sistemde 18 CPU kullan (%90)
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0 TO 17;
-- Belirli CPU'ları seçme
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0, 1, 2, 3, 4, 5, 6, 7;
-- NUMA node bazlı ayarlama
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY NUMANODE = 0, 1;
-- Affinity'yi kaldırma (tüm CPU'ları kullan)
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = AUTO;
GUI ile CPU Affinity Ayarlama
- SQL Server Management Studio (SSMS) açın
- Sunucuya sağ tıklayın → Properties
- Processors sayfasını seçin
- "Automatically set processor affinity mask for all processors" işaretini kaldırın
- Kullanılmasını istemediğiniz CPU'ların kutularını işaretsiz bırakın
- OK butonuna tıklayın
- SQL Server servisini restart edin
CPU Affinity Kontrol
-- Hangi CPU'lar SQL Server tarafından kullanılıyor?
SELECT
scheduler_id,
cpu_id,
status,
is_online,
current_tasks_count,
runnable_tasks_count
FROM sys.dm_os_schedulers
WHERE scheduler_id < 255
ORDER BY cpu_id;
-- SQL Server'ın toplam CPU sayısı
SELECT
cpu_count AS 'SQL Server Görünen CPU Sayısı',
hyperthread_ratio AS 'Hyperthread Oranı',
cpu_count / hyperthread_ratio AS 'Fiziksel CPU Sayısı',
scheduler_count AS 'Scheduler Sayısı'
FROM sys.dm_os_sys_info;
Affinity Mask Hesaplama
Affinity Mask, binary (ikili) sistemde çalışır. Her bit bir CPU'yu temsil eder.
Örnek: 20 CPU'dan 18'ini kullanmak
CPU 0-17 kullanılacak:
Binary: 0000 0000 0000 1111 1111 1111 1111 1111
Decimal: 262143
Hex: 0x3FFFF
-- sp_configure ile affinity mask ayarlama (eski yöntem)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'affinity mask', 262143;
RECONFIGURE;
Not: Modern SQL Server sürümlerinde ALTER SERVER CONFIGURATION kullanımı önerilir.
Karşılaşılabilecek Hatalar
Hata 2: CPU Sayısı Yetersiz
Msg 5833, Level 16, State 2
The affinity mask specified is greater than the number of CPUs
supported or licensed on this edition of SQL Server.
Nedenleri:
- Belirttiğiniz CPU numarası sistemdeki toplam CPU sayısından fazla
- SQL Server Edition'ınız daha az CPU destekliyor (örn: Express = 4 core)
- CPU lisansınız sınırlı
Çözüm:
-- Önce kaç CPU olduğunu kontrol edin
SELECT cpu_count FROM sys.dm_os_sys_info;
-- Sonra uygun değeri ayarlayın
-- Örnek: 8 CPU varsa, 7'sini kullan
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0 TO 6;
Yöntem 3: MAXDOP (Max Degree of Parallelism)
MAXDOP, bir sorgunun aynı anda kaç CPU çekirdeği kullanabileceğini belirler. Tüm SQL Server sürümlerinde çalışır.
Sunucu Seviyesinde MAXDOP
-- Tüm sunucu için MAXDOP ayarlama
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', 8;
RECONFIGURE;
-- Ayarı kontrol etme
EXEC sp_configure 'max degree of parallelism';
Veritabanı Seviyesinde MAXDOP
-- Belirli bir veritabanı için MAXDOP
ALTER DATABASE SCOPED CONFIGURATION
SET MAXDOP = 8;
-- Veritabanı ayarını kontrol etme
SELECT name, value
FROM sys.database_scoped_configurations
WHERE name = 'MAXDOP';
Sorgu Seviyesinde MAXDOP
-- Tek bir sorgu için MAXDOP
SELECT
CustomerID,
COUNT(*) AS OrderCount
FROM Orders
GROUP BY CustomerID
OPTION (MAXDOP 4);
-- Paralellik tamamen kapalı
SELECT * FROM LargTable
OPTION (MAXDOP 1);
MAXDOP Değeri Nasıl Belirlenir?
Microsoft'un önerdiği formül:
MAXDOP = MIN(8, CPU_Core_Count / NUMA_Node_Count)
Örnekler:
| Sistem Yapısı | Önerilen MAXDOP |
|---|---|
| 4 core, 1 NUMA | 4 |
| 8 core, 1 NUMA | 8 |
| 16 core, 1 NUMA | 8 |
| 16 core, 2 NUMA | 8 |
| 32 core, 4 NUMA | 8 |
| 64 core, 8 NUMA | 8 |
-- NUMA yapısını kontrol etme
SELECT
node_id,
COUNT(*) AS cpu_count
FROM sys.dm_os_schedulers
WHERE status = 'VISIBLE ONLINE'
GROUP BY node_id;
Cost Threshold for Parallelism
MAXDOP ile birlikte kullanılması gereken önemli bir ayar:
-- Sadece maliyeti 50'den yüksek sorgular paralel çalışsın
EXEC sp_configure 'cost threshold for parallelism', 50;
RECONFIGURE;
Varsayılan değer: 5 (çok düşük) Önerilen değer: 25-50 arası
-- Hangi sorguların paralel çalıştığını görme
SELECT
query_plan,
execution_count,
total_worker_time / execution_count AS avg_cpu_time,
degree_of_parallelism
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qp.query_plan LIKE '%Parallelism%'
ORDER BY total_worker_time DESC;
Yöntem 4: Maximum Worker Threads
Worker thread sayısını sınırlayarak dolaylı olarak CPU kullanımını kontrol edebilirsiniz.
-- Worker thread sayısını görüntüleme
SELECT
max_workers_count AS 'Configured Max Workers',
(SELECT COUNT(*)
FROM sys.dm_os_workers
WHERE state IN ('RUNNING', 'RUNNABLE')) AS 'Active Workers'
FROM sys.dm_os_sys_info;
-- Worker thread sayısını ayarlama
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
-- Örnek: 512 worker thread
EXEC sp_configure 'max worker threads', 512;
RECONFIGURE;
Varsayılan değerler (x64):
| CPU Sayısı | Varsayılan Worker Thread |
|---|---|
| ≤ 4 | 512 |
| 8 | 576 |
| 16 | 704 |
| 32 | 960 |
| 64 | 1472 |
⚠️ Dikkat: Çok düşük değer SQL Server'ı kilitleyebilir!
Yöntem 5: Windows İşletim Sistemi Seviyesi
Task Manager ile Affinity Ayarlama
- Task Manager açın (Ctrl + Shift + Esc)
- Details sekmesine gidin
- sqlservr.exe sürecini bulun
- Sağ tıklayın → Set affinity
- Kullanılmasını istemediğiniz CPU'ların işaretini kaldırın
- OK tıklayın
⚠️ Önemli: Bu ayar SQL Server servisini her restart ettiğinizde sıfırlanır.
PowerShell ile Kalıcı Affinity
# SQL Server servisinin CPU affinity'sini ayarlama
$service = Get-WmiObject Win32_Service -Filter "Name='MSSQLSERVER'"
$processId = $service.ProcessId
$process = Get-Process -Id $processId
# İlk 18 CPU'yu kullan (0x3FFFF = 262143)
$process.ProcessorAffinity = 262143
Windows Process Priority
-- SQL Server'ın priority boost ayarı (önerilmez)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'priority boost', 0; -- 0 = Normal, 1 = High
RECONFIGURE;
⚠️ Uyarı: Priority boost'u aktifleştirmek sistem kararsızlığına neden olabilir.
CPU Kullanımını İzleme ve Analiz
Gerçek Zamanlı CPU Kullanımı
-- SQL Server'ın anlık CPU kullanımı
DECLARE @ms_ticks INT;
SELECT @ms_ticks = ms_ticks FROM sys.dm_os_sys_info;
SELECT TOP 30
DATEADD(ms, -1 * (@ms_ticks - [timestamp]), GETDATE()) AS EventTime,
SQLProcessUtilization,
SystemIdle,
100 - SystemIdle - SQLProcessUtilization AS OtherProcessCPU
FROM (
SELECT
record.value('(./Record/@id)[1]', 'int') AS record_id,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization,
timestamp
FROM (
SELECT timestamp, CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%%'
) AS x
) AS y
ORDER BY record_id DESC;
En Çok CPU Tüketen Sorgular
-- Son 1 saat içinde en çok CPU kullanan sorgular
SELECT TOP 20
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS query_text,
qs.execution_count,
qs.total_worker_time / 1000 AS total_cpu_time_ms,
qs.total_worker_time / qs.execution_count / 1000 AS avg_cpu_time_ms,
qs.last_execution_time,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qs.last_execution_time > DATEADD(HOUR, -1, GETDATE())
ORDER BY qs.total_worker_time DESC;
CPU Beklemeleri (Wait Statistics)
-- CPU ile ilgili bekleme istatistikleri
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
max_wait_time_ms,
signal_wait_time_ms,
CAST(100.0 * wait_time_ms / SUM(wait_time_ms) OVER() AS DECIMAL(5,2)) AS percentage
FROM sys.dm_os_wait_stats
WHERE wait_type LIKE 'SOS_SCHEDULER_YIELD'
OR wait_type LIKE 'CXPACKET'
OR wait_type LIKE 'CXCONSUMER'
ORDER BY wait_time_ms DESC;
Scheduler Durumu
-- Her CPU scheduler'ının durumu
SELECT
scheduler_id,
cpu_id,
status,
is_online,
is_idle,
current_tasks_count,
runnable_tasks_count,
current_workers_count,
active_workers_count,
work_queue_count,
load_factor,
yield_count
FROM sys.dm_os_schedulers
WHERE scheduler_id < 255
ORDER BY cpu_id;
Pratik Senaryolar ve Çözümler
Senaryo 1: Raporlama Sunucusu CPU'yu Tüketiyor
Problem: SSRS (SQL Server Reporting Services) raporları production veritabanında çalışıyor ve tüm CPU'yu kullanıyor.
Çözüm (Enterprise Edition):
-- Raporlama için özel pool oluştur
CREATE RESOURCE POOL reporting_pool
WITH (
MAX_CPU_PERCENT = 30,
MIN_CPU_PERCENT = 5
);
CREATE WORKLOAD GROUP reporting_group
USING reporting_pool;
-- Raporlama kullanıcılarını sınıflandır
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
IF (APP_NAME() LIKE '%ReportServer%'
OR APP_NAME() LIKE '%Report Builder%')
RETURN 'reporting_group';
IF (SUSER_NAME() LIKE '%_report')
RETURN 'reporting_group';
RETURN 'default';
END;
GO
ALTER RESOURCE GOVERNOR
WITH (CLASSIFIER_FUNCTION = dbo.ResourceGovernorClassifier);
GO
ALTER RESOURCE GOVERNOR RECONFIGURE;
GO
Senaryo 2: Gece Batch İşleri
Problem: Gece çalışan ETL işleri gündüz performansı etkiliyor.
Çözüm:
-- Zaman bazlı sınırlama
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @hour INT = DATEPART(HOUR, GETDATE());
-- Gündüz saatleri (08:00-18:00)
IF (@hour >= 8 AND @hour < 18)
BEGIN
-- ETL işlerini sınırla
IF (APP_NAME() LIKE '%SSIS%' OR SUSER_NAME() = 'etl_user')
RETURN 'limited_workload_group';
END
RETURN 'default';
END;
GO
Senaryo 3: Multi-Tenant SaaS Ortamı
Problem: Farklı müşterilerin sorguları birbirini etkiliyor.
Çözüm:
-- Müşteri bazlı resource pool'lar
CREATE RESOURCE POOL premium_customer_pool
WITH (MAX_CPU_PERCENT = 60, MIN_CPU_PERCENT = 20);
CREATE RESOURCE POOL standard_customer_pool
WITH (MAX_CPU_PERCENT = 30, MIN_CPU_PERCENT = 5);
CREATE WORKLOAD GROUP premium_customers
USING premium_customer_pool;
CREATE WORKLOAD GROUP standard_customers
USING standard_customer_pool;
-- Classifier function
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @customer_tier VARCHAR(20);
-- Application name'den müşteri tier'ını al
-- Örnek: "MyApp_Premium_Customer123"
IF (APP_NAME() LIKE '%Premium%')
RETURN 'premium_customers';
ELSE IF (APP_NAME() LIKE '%Standard%')
RETURN 'standard_customers';
RETURN 'default';
END;
GO
Senaryo 4: Express Edition Sınırlamaları
Problem: Express Edition kullanıyorsunuz ve Resource Governor yok.
Çözüm:
-- 1. MAXDOP ile paralel sorgu sınırlama
EXEC sp_configure 'max degree of parallelism', 2;
RECONFIGURE;
-- 2. Cost threshold yükseltme
EXEC sp_configure 'cost threshold for parallelism', 50;
RECONFIGURE;
-- 3. Uygulama tarafında connection pool sınırlama
-- Connection String:
-- "Max Pool Size=10;Min Pool Size=2;"
-- 4. Query hints kullanma
SELECT * FROM LargeTable
WHERE Date > '2024-01-01'
OPTION (MAXDOP 1, RECOMPILE);
Best Practices (En İyi Uygulamalar)
1. Test Ortamında Deneyin
-- Production'a geçmeden önce test edin
-- Test ortamında Resource Governor yapılandırması
CREATE RESOURCE POOL test_pool
WITH (MAX_CPU_PERCENT = 50);
-- Yük testi yapın
-- sqlcmd veya ostress ile
2. İzleme ve Uyarı Sistemi Kurun
-- CPU kullanımı %90'ı geçince uyarı
CREATE PROCEDURE sp_MonitorCPUUsage
AS
BEGIN
DECLARE @cpu_usage INT;
SELECT @cpu_usage = SQLProcessUtilization
FROM (
SELECT TOP 1
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization
FROM (
SELECT CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
) AS x
ORDER BY record.value('(./Record/@id)[1]', 'int') DESC
) AS y;
IF @cpu_usage > 90
BEGIN
-- E-posta gönder veya log yaz
RAISERROR('CPU usage exceeded 90%%!', 16, 1);
END
END;
GO
-- SQL Agent job ile 5 dakikada bir çalıştır
3. Dokümantasyon
Her yapılandırma değişikliğini dokümante edin:
-- Değişiklik geçmişi tablosu
CREATE TABLE dbo.ResourceGovernorChanges
(
ChangeID INT IDENTITY PRIMARY KEY,
ChangeDate DATETIME DEFAULT GETDATE(),
ChangedBy NVARCHAR(128) DEFAULT SUSER_NAME(),
ChangeType NVARCHAR(50),
OldValue NVARCHAR(MAX),
NewValue NVARCHAR(MAX),
Reason NVARCHAR(500)
);
-- Değişiklik kaydı
INSERT INTO dbo.ResourceGovernorChanges
(ChangeType, OldValue, NewValue, Reason)
VALUES
('MAX_CPU_PERCENT', '100', '90', 'Reporting server performance impact');
4. Rollback Planı Hazırlayın
-- Emergency rollback script
-- Her yapılandırma değişikliğinden önce hazırlayın
-- Resource Governor'ı tamamen devre dışı bırakma
ALTER RESOURCE GOVERNOR DISABLE;
-- CPU Affinity'yi tüm CPU'lara açma
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = AUTO;
-- MAXDOP'u varsayılana döndürme
EXEC sp_configure 'max degree of parallelism', 0;
RECONFIGURE;
5. Baseline (Temel Çizgi) Ölçümleri Alın
-- Değişiklik öncesi performans metrikleri
SELECT
GETDATE() AS baseline_time,
@@SERVERNAME AS server_name,
(SELECT AVG(SQLProcessUtilization)
FROM (
SELECT TOP 60
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization
FROM (
SELECT CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
) AS x
) AS y) AS avg_cpu_last_hour,
(SELECT COUNT(*)
FROM sys.dm_exec_requests
WHERE status = 'running') AS active_requests,
(SELECT COUNT(*)
FROM sys.dm_os_waiting_tasks) AS waiting_tasks
INTO dbo.PerformanceBaseline;
Sorun Giderme (Troubleshooting)
Problem 1: Resource Governor Çalışmıyor
Belirti: Ayarlar yapıldı ama CPU kullanımı hala yüksek.
Kontroller:
-- 1. Resource Governor aktif mi?
SELECT is_enabled
FROM sys.resource_governor_configuration;
-- 0 = Devre dışı, 1 = Aktif
-- 2. Classifier function doğru mu?
SELECT object_name(classifier_function_id) AS classifier_function
FROM sys.resource_governor_configuration;
-- 3. Kullanıcılar doğru gruba atanmış mı?
SELECT
s.session_id,
s.login_name,
wg.name AS workload_group,
rp.name AS resource_pool
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_resource_governor_workload_groups wg
ON s.group_id = wg.group_id
LEFT JOIN sys.dm_resource_governor_resource_pools rp
ON wg.pool_id = rp.pool_id
WHERE s.is_user_process = 1;
-- 4. Classifier function test
SELECT dbo.ResourceGovernorClassifier() AS assigned_group;
Çözümler:
-- Resource Governor'ı yeniden etkinleştir
ALTER RESOURCE GOVERNOR RECONFIGURE;
-- Eğer devre dışıysa
ALTER RESOURCE GOVERNOR ENABLE;
ALTER RESOURCE GOVERNOR RECONFIGURE;
-- Mevcut bağlantılar için geçerli olması için kullanıcıların
-- yeniden bağlanması gerekir veya:
ALTER RESOURCE GOVERNOR RESET STATISTICS;
Problem 2: CPU Affinity Değişmiyor
Belirti: ALTER SERVER CONFIGURATION komutu hatasız çalışıyor ama CPU kullanımı değişmiyor.
Neden: SQL Server servisinin restart edilmesi gerekiyor.
Çözüm:
-- 1. SQL Server Agent'ı durdur
EXEC msdb.dbo.sp_stop_job @job_name = 'ALL';
-- 2. PowerShell veya Service Manager ile SQL Server'ı restart et
-- 3. Restart sonrası kontrol
SELECT
scheduler_id,
cpu_id,
is_online
FROM sys.dm_os_schedulers
WHERE scheduler_id < 255
ORDER BY cpu_id;
Problem 3: MAXDOP Ayarı Çalışmıyor
Belirti: MAXDOP ayarlandı ama sorgular hala tüm CPU'ları kullanıyor.
Kontroller:
-- 1. MAXDOP değerini kontrol et
EXEC sp_configure 'max degree of parallelism';
-- 2. Veritabanı seviyesi ayar var mı?
SELECT name, value, value_for_secondary
FROM sys.database_scoped_configurations
WHERE name = 'MAXDOP';
-- 3. Query Store hint var mı?
SELECT
qsqt.query_sql_text,
qsq.query_id,
qsh.query_hint_text
FROM sys.query_store_query qsq
INNER JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
LEFT JOIN sys.query_store_query_hints qsh
ON qsq.query_id = qsh.query_id
WHERE qsh.query_hint_text LIKE '%MAXDOP%';
-- 4. Plan cache'de MAXDOP hint'li planlar
SELECT
query_plan,
execution_count,
query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";
max(//p:StmtSimple/@StatementOptmLevel)', 'INT') AS MaxDOP
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";
max(//p:StmtSimple/@StatementOptmLevel)', 'INT') > 1;
Çözümler:
-- Veritabanı seviyesi ayarı kaldır (sunucu ayarını kullan)
ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = PRIMARY;
-- Plan cache'i temizle
DBCC FREEPROCCACHE;
-- Belirli bir sorgu için hint kullan
SELECT * FROM Orders
OPTION (MAXDOP 4, RECOMPILE);
Problem 4: "Insufficient System Resources" Hatası
Belirti: Değişiklik sonrası SQL Server yavaşladı veya bağlantılar kurulamıyor.
Msg 701, Level 17, State 123
There is insufficient system memory in resource pool 'default'
to run this query.
Neden: Resource Pool ayarları çok kısıtlayıcı.
Çözüm:
-- Mevcut resource pool ayarlarını görüntüle
SELECT
name,
max_memory_percent,
max_cpu_percent,
min_memory_percent,
min_cpu_percent
FROM sys.resource_governor_resource_pools;
-- Acil durum: Resource Governor'ı devre dışı bırak
ALTER RESOURCE GOVERNOR DISABLE;
-- Daha sonra ayarları düzelt
ALTER RESOURCE POOL limited_cpu_pool
WITH (
MAX_CPU_PERCENT = 90, -- 80'den 90'a çıkar
MIN_CPU_PERCENT = 10,
MAX_MEMORY_PERCENT = 90 -- Bellek de ekle
);
ALTER RESOURCE GOVERNOR RECONFIGURE;
ALTER RESOURCE GOVERNOR ENABLE;
Problem 5: Classifier Function Çalışmıyor
Belirti: Tüm kullanıcılar 'default' grubuna düşüyor.
Debug:
-- Classifier function'ı test et
CREATE PROCEDURE sp_TestClassifier
@test_user NVARCHAR(128) = NULL,
@test_app NVARCHAR(128) = NULL,
@test_host NVARCHAR(128) = NULL
AS
BEGIN
DECLARE @result SYSNAME;
-- Geçici context değiştir (test amaçlı)
PRINT 'Current User: ' + SUSER_NAME();
PRINT 'Current App: ' + APP_NAME();
PRINT 'Current Host: ' + HOST_NAME();
-- Classifier function'ı çağır
SELECT @result = dbo.ResourceGovernorClassifier();
PRINT 'Assigned Group: ' + @result;
END;
GO
EXEC sp_TestClassifier;
Yaygın Hatalar:
-- HATA: SCHEMABINDING kullanılmamış
-- YANLIŞ:
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
AS
BEGIN
-- ...
END;
-- DOĞRU:
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
-- ...
END;
-- HATA: NULL dönüyor
-- YANLIŞ:
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
IF (SUSER_NAME() = 'test_user')
RETURN 'test_group';
-- NULL döner!
END;
-- DOĞRU:
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
IF (SUSER_NAME() = 'test_user')
RETURN 'test_group';
RETURN 'default'; -- Varsayılan değer
END;
Performans Etkileri ve Optimizasyon
Resource Governor Overhead
Resource Governor'ın performans maliyeti minimumdur:
-- Resource Governor overhead ölçümü
DECLARE @start_time DATETIME2 = SYSDATETIME();
-- 1000 bağlantı simülasyonu
DECLARE @i INT = 0;
WHILE @i < 1000
BEGIN
-- Classifier function çağrılır
DECLARE @group SYSNAME = dbo.ResourceGovernorClassifier();
SET @i = @i + 1;
END
DECLARE @end_time DATETIME2 = SYSDATETIME();
SELECT DATEDIFF(MILLISECOND, @start_time, @end_time) AS overhead_ms;
-- Genellikle < 10ms
Optimizasyon İpuçları:
- Basit Classifier Function Yazın
-- KÖTÜ: Karmaşık sorgu
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
DECLARE @tier VARCHAR(20);
-- YAVAŞ: Tablo sorgusu
SELECT @tier = customer_tier
FROM dbo.Customers
WHERE username = SUSER_NAME();
RETURN @tier + '_group';
END;
-- İYİ: Basit kontroller
CREATE FUNCTION dbo.ResourceGovernorClassifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
-- Hızlı string kontrolleri
IF (SUSER_NAME() LIKE '%_premium')
RETURN 'premium_group';
IF (APP_NAME() LIKE '%Report%')
RETURN 'reporting_group';
RETURN 'default';
END;
- Gereksiz Classifier Function Kullanmayın
-- Eğer TÜM kullanıcılar aynı gruba gidiyorsa:
-- Classifier function kullanmayın, direkt default pool ayarını değiştirin
ALTER RESOURCE POOL [default]
WITH (MAX_CPU_PERCENT = 90);
ALTER RESOURCE GOVERNOR RECONFIGURE;
CPU Affinity vs Resource Governor
Performans Karşılaştırması:
| Özellik | CPU Affinity | Resource Governor |
|---|---|---|
| Overhead | Yok | Minimal (~0.1%) |
| Esneklik | Düşük | Yüksek |
| Dinamik | Hayır | Evet |
| Restart Gerekir | Evet | Hayır |
| Kullanıcı Bazlı | Hayır | Evet |
-- Benchmark: CPU Affinity
-- Sadece 18 CPU kullan
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0 TO 17;
-- Restart gerekli
-- Benchmark: Resource Governor
CREATE RESOURCE POOL limited_pool
WITH (MAX_CPU_PERCENT = 90);
-- Restart gerektirmez, anında etkili
MAXDOP Optimizasyonu
-- En uygun MAXDOP değerini bulma
WITH CPUStats AS (
SELECT
qs.query_hash,
COUNT(DISTINCT qs.plan_handle) AS plan_count,
AVG(qs.total_worker_time / qs.execution_count) AS avg_cpu_time,
MAX(qp.query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";
max(//p:RelOp/@EstimateRows)', 'FLOAT')) AS max_rows
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qs.execution_count > 10
GROUP BY qs.query_hash
)
SELECT
CASE
WHEN avg_cpu_time < 100 THEN 'MAXDOP 1 önerilir'
WHEN avg_cpu_time < 1000 THEN 'MAXDOP 2-4 önerilir'
WHEN max_rows < 10000 THEN 'MAXDOP 4 önerilir'
ELSE 'MAXDOP 8 önerilir'
END AS recommendation,
COUNT(*) AS query_count
FROM CPUStats
GROUP BY
CASE
WHEN avg_cpu_time < 100 THEN 'MAXDOP 1 önerilir'
WHEN avg_cpu_time < 1000 THEN 'MAXDOP 2-4 önerilir'
WHEN max_rows < 10000 THEN 'MAXDOP 4 önerilir'
ELSE 'MAXDOP 8 önerilir'
END;
Gelişmiş Senaryolar
Senaryo 1: Adaptive MAXDOP
SQL Server 2019+ Automatic Plan Correction ile:
-- Veritabanı seviyesinde Automatic Tuning aktifleştir
ALTER DATABASE CURRENT
SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = ON);
-- Query Store'u etkinleştir
ALTER DATABASE CURRENT
SET QUERY_STORE = ON (
OPERATI
MAX_STORAGE_SIZE_MB = 1024,
INTERVAL_LENGTH_MINUTES = 60
);
-- Regressed query'leri izle
SELECT
qsq.query_id,
qsqt.query_sql_text,
qsrs.avg_duration / 1000 AS avg_duration_ms,
qsrs.avg_cpu_time / 1000 AS avg_cpu_time_ms,
rs.reason
FROM sys.query_store_query qsq
INNER JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
INNER JOIN sys.query_store_plan qsp
ON qsq.query_id = qsp.query_id
INNER JOIN sys.query_store_runtime_stats qsrs
ON qsp.plan_id = qsrs.plan_id
LEFT JOIN sys.dm_db_tuning_recommendations rs
ON qsq.query_id = rs.details.value('(/state/query/@id)[1]', 'INT')
WHERE rs.reason = 'CPUThresholdExceeded'
ORDER BY qsrs.avg_cpu_time DESC;
Senaryo 2: Multi-Instance Sunucularda CPU Paylaşımı
Aynı sunucuda birden fazla SQL Server instance çalışıyorsa:
-- Instance 1 (Production): CPU 0-11 (12 core)
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0 TO 11;
-- Instance 2 (Reporting): CPU 12-19 (8 core)
-- Diğer instance'da çalıştır:
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 12 TO 19;
Windows Process Priority ile:
# Production instance: Normal priority
Get-Process -Name "sqlservr" |
Where-Object {$_.Path -like "*MSSQLSERVER*"} |
ForEach-Object {$_.PriorityClass = "Normal"}
# Reporting instance: Below Normal priority
Get-Process -Name "sqlservr" |
Where-Object {$_.Path -like "*REPORTING*"} |
ForEach-Object {$_.PriorityClass = "BelowNormal"}
Senaryo 3: Container/Kubernetes Ortamında SQL Server
Docker veya Kubernetes'te SQL Server çalıştırırken:
Dockerfile:
FROM mcr.microsoft.com/mssql/server:2019-latest
# CPU limitlerini environment variable olarak ayarla
ENV MSSQL_ENABLE_HADR=0
ENV MSSQL_CPU_LIMIT=8
ENV ACCEPT_EULA=Y
ENV SA_PASSWORD=YourStrong@Passw0rd
# SQL Server başlatma script'i
COPY ./configure-sql.sh /usr/config/
RUN chmod +x /usr/config/configure-sql.sh
CMD /usr/config/configure-sql.sh
configure-sql.sh:
#!/bin/bash
# SQL Server'ı başlat
/opt/mssql/bin/sqlservr &
# SQL Server'ın başlamasını bekle
sleep 30
# MAXDOP ayarla
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -Q "
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', $MSSQL_CPU_LIMIT;
RECONFIGURE;
"
# Foreground'da çalışmaya devam et
wait
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mssql-deployment
spec:
replicas: 1
template:
spec:
containers:
- name: mssql
image: mcr.microsoft.com/mssql/server:2019-latest
resources:
requests:
cpu: "4"
memory: "8Gi"
limits:
cpu: "8" # Maksimum 8 CPU
memory: "16Gi"
env:
- name: ACCEPT_EULA
value: "Y"
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql-secret
key: SA_PASSWORD
Senaryo 4: Azure SQL Database ve Managed Instance
Azure SQL'de CPU sınırlaması farklı çalışır:
-- Azure SQL Database'de DTU/vCore limitleri
-- Service Tier'a göre otomatik sınırlıdır
-- Mevcut limitleri görüntüle
SELECT
database_name,
sku,
service_objective,
elastic_pool_name,
avg_cpu_percent,
max_worker_percent
FROM sys.dm_db_resource_stats
ORDER BY end_time DESC;
-- Azure SQL Managed Instance'da
SELECT
virtual_core_count,
sku_name,
storage_size_gb,
max_storage_size_gb
FROM sys.server_resource_stats;
-- MAXDOP ayarlama (destekleniyor)
ALTER DATABASE SCOPED CONFIGURATION
SET MAXDOP = 8;
-- CPU intensive sorguları bulma
SELECT TOP 10
query_id,
SUM(total_cpu_time_ms) / SUM(count_executions) AS avg_cpu_ms,
SUM(count_executions) AS execution_count
FROM sys.query_store_runtime_stats_interval
WHERE end_time >= DATEADD(HOUR, -1, GETUTCDATE())
GROUP BY query_id
ORDER BY avg_cpu_ms DESC;
Monitoring Dashboard Oluşturma
Power BI ile CPU Monitoring
SQL Query (Power BI Dataset):
CREATE PROCEDURE sp_GetCPUMetrics
AS
BEGIN
-- CPU kullanım geçmişi
SELECT
DATEADD(ms, -1 * (si.ms_ticks - rb.timestamp), GETDATE()) AS measurement_time,
rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_percent,
rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_idle_percent,
100 - rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int')
- rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int')
AS other_process_cpu_percent
FROM (
SELECT timestamp, CONVERT(XML, record) AS rec
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%%'
) AS rb
CROSS JOIN sys.dm_os_sys_info si
WHERE rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') IS NOT NULL
ORDER BY measurement_time DESC;
-- Resource Governor istatistikleri
SELECT
rp.name AS pool_name,
rp.max_cpu_percent,
rp.total_cpu_usage_ms / 1000.0 / 60 AS total_cpu_minutes,
wg.name AS workload_group_name,
wg.total_request_count,
wg.active_request_count
FROM sys.dm_resource_governor_resource_pools rp
LEFT JOIN sys.dm_resource_governor_workload_groups wg
ON rp.pool_id = wg.pool_id
WHERE rp.name <> 'internal';
-- Top CPU consumers
SELECT TOP 20
SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS query_text,
qs.execution_count,
qs.total_worker_time / 1000 AS total_cpu_ms,
qs.total_worker_time / qs.execution_count / 1000 AS avg_cpu_ms,
qs.last_execution_time,
DB_NAME(st.dbid) AS database_name
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
ORDER BY qs.total_worker_time DESC;
END;
GO
Grafana Dashboard
Prometheus Exporter için SQL Query:
-- cpu_usage_percent
SELECT
'sql_server_cpu_usage_percent' AS metric,
SQLProcessUtilization AS value,
'sql_server' AS instance
FROM (
SELECT TOP 1
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization
FROM (
SELECT CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
) AS x
ORDER BY record.value('(./Record/@id)[1]', 'int') DESC
) AS y;
-- resource_pool_cpu_usage
SELECT
'sql_server_resource_pool_cpu_ms' AS metric,
name AS pool_name,
total_cpu_usage_ms AS value
FROM sys.dm_resource_governor_resource_pools
WHERE name <> 'internal';
Custom Alert System
CREATE TABLE dbo.CPUAlertLog
(
AlertID INT IDENTITY PRIMARY KEY,
AlertTime DATETIME DEFAULT GETDATE(),
CPUPercent INT,
TopQuery NVARCHAR(MAX),
AlertSent BIT DEFAULT 0
);
GO
CREATE PROCEDURE sp_MonitorAndAlert
AS
BEGIN
DECLARE @cpu_percent INT;
DECLARE @top_query NVARCHAR(MAX);
-- CPU yüzdesini al
SELECT TOP 1 @cpu_percent =
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int')
FROM (
SELECT CONVERT(XML, record) AS record
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
) AS x
ORDER BY record.value('(./Record/@id)[1]', 'int') DESC;
-- Eğer %90'ın üzerindeyse
IF @cpu_percent > 90
BEGIN
-- En çok CPU tüketen sorguyu bul
SELECT TOP 1 @top_query =
SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1)
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
INNER JOIN sys.dm_exec_query_stats qs
ON r.sql_handle = qs.sql_handle
ORDER BY r.cpu_time DESC;
-- Log'a kaydet
INSERT INTO dbo.CPUAlertLog (CPUPercent, TopQuery)
VALUES (@cpu_percent, @top_query);
-- Email gönder
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'DBA_Profile',
@recipients = 'dba@company.com',
@subject = 'CRITICAL: SQL Server CPU Usage > 90%',
@body = @top_query,
@body_format = 'TEXT';
END
END;
GO
-- SQL Agent Job: Her 5 dakikada çalıştır
Güvenlik ve İzinler
Resource Governor İzinleri
-- Resource Governor yönetimi için gereken izinler
-- ALTER SETTINGS server permission gereklidir
-- Kullanıcıya izin verme
GRANT ALTER SETTINGS TO [DBA_User];
-- Veya server role kullan
ALTER SERVER ROLE sysadmin ADD MEMBER [DBA_User];
-- Classifier function'ı test edebilmek için
GRANT EXECUTE ON dbo.ResourceGovernorClassifier TO [DBA_User];
-- Resource Governor objelerini görüntüleme
GRANT VIEW SERVER STATE TO [Monitor_User];
Audit (Denetim) Sistemi
-- Resource Governor değişikliklerini audit et
CREATE SERVER AUDIT ResourceGovernorAudit
TO FILE (FILEPATH = 'C:\SQLAudit\')
WITH (
CREATE SERVER AUDIT SPECIFICATION ResourceGovernorChanges
FOR SERVER AUDIT ResourceGovernorAudit
ADD (SCHEMA_OBJECT_CHANGE_GROUP);
ALTER SERVER AUDIT ResourceGovernorAudit
WITH (STATE = ON);
ALTER SERVER AUDIT SPECIFICATION ResourceGovernorChanges
WITH (STATE = ON);
-- Audit loglarını sorgulama
SELECT
event_time,
statement,
succeeded,
server_principal_name
FROM sys.fn_get_audit_file('C:\SQLAudit\*', DEFAULT, DEFAULT)
WHERE statement LIKE '%RESOURCE%GOVERNOR%'
ORDER BY event_time DESC;
Kapasite Planlama
CPU Büyüme Analizi
CREATE TABLE dbo.CPUUsageHistory
(
MeasurementDate DATE,
AvgCPUPercent DECIMAL(5,2),
MaxCPUPercent INT,
PeakHour INT
);
GO
-- Günlük CPU istatistiklerini toplama
CREATE PROCEDURE sp_CollectDailyCPUStats
AS
BEGIN
INSERT INTO dbo.CPUUsageHistory
SELECT
CAST(GETDATE() AS DATE),
AVG(CAST(SQLProcessUtilization AS DECIMAL(5,2))),
MAX(SQLProcessUtilization),
DATEPART(HOUR, MAX(measurement_time))
FROM (
SELECT
DATEADD(ms, -1 * (si.ms_ticks - rb.timestamp), GETDATE()) AS measurement_time,
rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization
FROM (
SELECT timestamp, CONVERT(XML, record) AS rec
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
) AS rb
CROSS JOIN sys.dm_os_sys_info si
WHERE DATEADD(ms, -1 * (si.ms_ticks - rb.timestamp), GETDATE()) >= CAST(GETDATE() AS DATE)
) AS daily_data;
END;
GO
-- Trend analizi
SELECT
MeasurementDate,
AvgCPUPercent,
AVG(AvgCPUPercent) OVER (ORDER BY MeasurementDate ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7day,
CASE
WHEN AVG(AvgCPUPercent) OVER (ORDER BY MeasurementDate ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) > 70
THEN 'Upgrade Recommended'
WHEN AVG(AvgCPUPercent) OVER (ORDER BY MeasurementDate ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) > 85
THEN 'URGENT: Upgrade Required'
ELSE 'Normal'
END AS recommendation
FROM dbo.CPUUsageHistory
WHERE MeasurementDate >= DATEADD(DAY, -90, GETDATE())
ORDER BY MeasurementDate DESC;
CPU Sizing Hesaplama
-- Mevcut yük için ideal CPU sayısı hesaplama
WITH CurrentLoad AS (
SELECT
AVG(CAST(SQLProcessUtilization AS FLOAT)) AS avg_cpu,
MAX(SQLProcessUtilization) AS peak_cpu
FROM (
SELECT TOP 1000
rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLProcessUtilization
FROM (
SELECT CONVERT(XML, record) AS rec
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
) AS x
WHERE rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') IS NOT NULL
) AS cpu_data
),
SystemInfo AS (
SELECT cpu_count AS current_cpus
FROM sys.dm_os_sys_info
)
SELECT
si.current_cpus AS 'Current CPUs',
cl.avg_cpu AS 'Average CPU %',
cl.peak_cpu AS 'Peak CPU %',
CEILING((cl.avg_cpu / 70.0) * si.current_cpus) AS 'Recommended CPUs (70% target)',
CEILING((cl.peak_cpu / 85.0) * si.current_cpus) AS 'Recommended CPUs (85% peak target)',
CASE
WHEN cl.avg_cpu > 70 THEN 'Consider adding ' +
CAST(CEILING((cl.avg_cpu / 70.0) * si.current_cpus) - si.current_cpus AS VARCHAR) + ' CPUs'
ELSE 'Current capacity adequate'
END AS 'Recommendation'
FROM CurrentLoad cl, SystemInfo si;
Checklist ve Özet
Pre-Production Checklist
☐ SQL Server Edition kontrolü yapıldı
☐ Mevcut CPU sayısı belirlendi
☐ Baseline performans metrikleri alındı
☐ MAXDOP değeri hesaplandı
☐ Cost threshold ayarlandı
☐ Resource Governor yapılandırması (Enterprise'da) tamamlandı
☐ CPU Affinity ayarları (gerekirse) yapıldı
☐ Monitoring ve alerting sistemi kuruldu
☐ Rollback planı hazırlandı
☐ Dokümantasyon tamamlandı
☐ Test ortamında doğrulama yapıldı
☐ Stakeholder'lara bilgi verildi
Production Deployment Checklist
☐ Değişiklik penceresi (maintenance window) planlandı
☐ Backup alındı
☐ Pre-deployment test yapıldı
☐ Deployment script'leri hazırlandı
☐ Rollback script'leri hazırlandı
☐ Monitoring dashboards açık
☐ DBA team'i hazırda
☐ Değişiklikler uygulandı
☐ SQL Server restart edildi (gerekirse)
☐ Post-deployment testler yapıldı
☐ Monitoring data toplanıyor
☐ Stakeholder'lara bilgi verildi
Post-Deployment Monitoring (İlk 24 Saat)
-- Saatlik CPU metrik raporu
SELECT
DATEPART(HOUR, measurement_time) AS hour_of_day,
AVG(cpu_percent) AS avg_cpu,
MAX(cpu_percent) AS max_cpu,
MIN(cpu_percent) AS min_cpu,
COUNT(*) AS sample_count
FROM (
SELECT
DATEADD(ms, -1 * (si.ms_ticks - rb.timestamp), GETDATE()) AS measurement_time,
rec.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS cpu_percent
FROM (
SELECT timestamp, CONVERT(XML, record) AS rec
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'
) AS rb
CROSS JOIN sys.dm_os_sys_info si
WHERE DATEADD(ms, -1 * (si.ms_ticks - rb.timestamp), GETDATE()) >= DATEADD(HOUR, -24, GETDATE())
) AS hourly_data
GROUP BY DATEPART(HOUR, measurement_time)
ORDER BY hour_of_day;
Sonuç
SQL Server'da CPU kullanımını sınırlamak, sistem performansını optimize etmek ve kaynak çekişmelerini önlemek için kritik öneme sahiptir. Bu rehberde ele aldığımız yöntemlerin özeti:
Edition Bazlı Öneriler
Express Edition:
- ✅ MAXDOP
- ✅ Cost Threshold for Parallelism
- ✅ Query Hints
- ❌ Resource Governor
- ❌ CPU Affinity
Standard Edition:
- ✅ MAXDOP
- ✅ Cost Threshold for Parallelism
- ✅ CPU Affinity
- ✅ Worker Threads
- ❌ Resource Governor
Enterprise/Developer Edition:
- ✅ Tüm özellikler
- ✅ Resource Governor (ÖNERİLEN)
- ✅ CPU Affinity
- ✅ MAXDOP
- ✅ İleri düzey optimizasyonlar
En İyi Yaklaşım
- İlk olarak: MAXDOP ve Cost Threshold ayarlarını optimize edin
- Enterprise Edition'da: Resource Governor kullanın
- Gerekirse: CPU Affinity ile fiziksel sınırlama yapın
- Mutlaka: Monitoring ve alerting sistemi kurun
- Sürekli: Performans metriklerini izleyin ve optimize edin
Son Tavsiyeler
- Test ortamında her zaman deneyin
- Baseline metrikler alın
- Rollback planı hazırlayın
- Dokümante edin
- İzlemeyi ihmal etmeyin
- Düzenli olarak gözden geçirin
Bu rehber, SQL Server'da CPU yönetimi için kapsamlı bir kaynak sunmaktadır. Her ortamın kendine özgü gereksinimleri olduğunu unutmayın ve yapılandırmalarınızı buna göre özelleştirin.