Windows Server 2022 RDS (Remote Desktop Services) Licensing Problemi ve Çözümü
Remote Desktop Services (RDS) ortamlarında karşılaşılan en yaygın probleml...
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'ın farklı sürümlerinin CPU kullanımı konusunda farklı yetenekleri ve kısıtlamaları vardır:
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, üç ana bileşenden oluşur:
-- 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;
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
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 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;
-- Ö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;
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, SQL Server'ın hangi işlemci çekirdeklerini kullanabileceğini belirler. Bu yöntem Standard ve Enterprise Edition'larda çalışır.
-- 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;
-- 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, 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.
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:
Çö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;
MAXDOP, bir sorgunun aynı anda kaç CPU çekirdeği kullanabileceğini belirler. Tüm SQL Server sürümlerinde çalışır.
-- 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';
-- 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';
-- 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);
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;
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;
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!
⚠️ Önemli: Bu ayar SQL Server servisini her restart ettiğinizde sıfırlanır.
# 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
-- 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.
-- 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;
-- 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 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;
-- 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;
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
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
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
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);
-- 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
-- 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
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');
-- 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;
-- 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;
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;
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;
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);
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;
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;
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ı:
-- 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;
-- 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;
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
-- 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;
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 (
    OPERATION_MODE = READ_WRITE,
    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;
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"}
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
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;
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
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';
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
-- 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];
-- Resource Governor değişikliklerini audit et
CREATE SERVER AUDIT ResourceGovernorAudit
TO FILE (FILEPATH = 'C:\SQLAudit\')
WITH (ON_FAILURE = CONTINUE);
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;
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;
-- 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;
☐ 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☐ 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-- 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;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:
Express Edition:
Standard Edition:
Enterprise/Developer Edition:
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.