SQL Server Bakımı: Index Fragmentation, Rebuild ve Reorganize, İstatistik Güncelleme, DBCC CHECKDB ve Ola Hallengren
Index bakımında belirleyici olan fragmentation değil page density ve maliyettir: yüzde 39 fragmented bir index'te REORGANIZE offline REBUILD'in 3,6 katı, ONLINE rebuild 7 katı log üretti; rebuild sonrası UPDATE STATISTICS tam taramalı istatistiği yüzde 11 örneklemle değiştirdi. DBCC CHECKDB ile bozuk bir nonclustered index'i veri kaybetmeden onarmak ve Ola Hallengren'in IndexOptimize, DatabaseIntegrityCheck, DatabaseBackup procedure'leri.
Sürüm notu: Bu yazıdaki T-SQL örnekleri SQL Server 2025 CU10 (17.0.5035.5, Enterprise Developer Edition) üzerinde, Linux container'da serinin Bookstore laboratuvar veritabanıyla çalıştırılmıştır. Ola Hallengren'in SQL Server Maintenance Solution'ının 2026-09-12 sürümü (MIT lisanslı) kullanılmıştır.
Önce Tahmin Et: REORGANIZE Hafif, REBUILD Ağır mı?
Serinin laboratuvar veritabanında (sitedeki serilerin ortak örnek uygulaması Bookstore'un, bir online kitapçının veritabanı) bir ayı taklit ettim: eski siparişlerin beşte birinin kalemleri arşive taşınıp silindi, yeni siparişler için 200.000 kalem eklendi. Yeni kalemlerin BookId'leri rastgele dağıldığı için IX_OrderItems_BookId index'inin ortasına yazıldı:
DELETE FROM dbo.OrderItems WHERE OrderId % 5 = 0 AND OrderId <= 600000;
INSERT dbo.OrderItems (OrderId, BookId, Quantity, UnitPrice)
SELECT 900000 + g.value / 2, 1 + (g.value * 7919) % 40000, 1, 10.00
FROM GENERATE_SERIES(1, 200000) AS g;
Sonra veritabanını FULL recovery'de tutup bu index'i altı farklı şekilde bakımdan geçirdim. Her denemeden önce veritabanını aynı backup'tan geri yükledim ve bir log backup aldım; böylece her satırın başlangıç noktası aynı. Her denemeden sonra şu ölçümü yaptım:
SELECT CAST(ips.avg_fragmentation_in_percent AS decimal(5,1)) AS frag_pct,
CAST(ips.avg_page_space_used_in_percent AS decimal(5,1)) AS density_pct,
ips.page_count, sp.rows, sp.rows_sampled
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('dbo.OrderItems'), 4, NULL, 'DETAILED') AS ips
CROSS APPLY sys.dm_db_stats_properties(ips.object_id, ips.index_id) AS sp
WHERE ips.index_level = 0;
SET STATISTICS IO ON;
SELECT COUNT_BIG(*) AS lines FROM dbo.OrderItems WITH (INDEX (IX_OrderItems_BookId));
SET STATISTICS IO OFF;
SELECT CAST(log_since_last_log_backup_mb AS decimal(8,1)) AS log_mb FROM sys.dm_db_log_stats(DB_ID());
frag_pct density_pct page_count rows rows_sampled
-------- ----------- ---------- ------- ------------
39.1 77.0 5390 2500496 2500496
lines
-------
2399937
Table 'OrderItems'. Scan count 11, logical reads 5470, ...
Index yüzde 39 fragmented ve page'leri ortalama yüzde 77 dolu. Şimdi tahmin et: REORGANIZE "hafif" seçenek olarak bilinir. Hangi işlem en çok log üretir, hangisi istatistikleri günceller?
ALTER INDEX IX_OrderItems_BookId ON dbo.OrderItems REORGANIZE;
ALTER INDEX IX_OrderItems_BookId ON dbo.OrderItems REBUILD;
ALTER INDEX IX_OrderItems_BookId ON dbo.OrderItems REBUILD WITH (ONLINE = ON);
ALTER INDEX IX_OrderItems_BookId ON dbo.OrderItems REBUILD WITH (ONLINE = ON, RESUMABLE = ON);
ALTER INDEX IX_OrderItems_BookId ON dbo.OrderItems REBUILD;
UPDATE STATISTICS dbo.OrderItems IX_OrderItems_BookId;
| İşlem | Fragmentation | Page density | Page | Logical reads | Log (MB) | rows_sampled / rows |
|---|---|---|---|---|---|---|
| Bakımdan önce | %39,1 | %77,0 | 5.390 | 5.470 | - | 2.500.496 / 2.500.496 (eski) |
REORGANIZE | %0,3 | %95,6 | 4.340 | 4.420 | 127,7 | 2.500.496 / 2.500.496 (güncellenmedi) |
REBUILD (offline) | %0,5 | %99,9 | 4.156 | 4.206 | 35,2 | 2.399.937 / 2.399.937 |
REBUILD ONLINE | %1,0 | %99,6 | 4.464 | 4.569 | 251,9 | 2.399.937 / 2.399.937 |
REBUILD ONLINE, RESUMABLE | %2,3 | %99,2 | 4.484 | 4.729 | 254,7 | 274.341 / 2.399.937 |
REBUILD + UPDATE STATISTICS | %0,5 | %99,9 | 4.156 | 4.206 | 35,2 | 274.341 / 2.399.937 |
Bu tablodaki dört sonuç sezgiye ters. REORGANIZE, offline REBUILD'in 3,6 katı log üretti, çünkü satırları page page taşıyıp her taşımayı log'luyor; offline rebuild ise yeni index'i page olarak yazıyor. ONLINE rebuild, kullanıcılar tabloya yazmaya devam edebilsin diye iki kopyayı birlikte tuttuğu için yedi kat log üretti. REORGANIZE istatistiklere dokunmadı: rows hâlâ silinmiş satırları sayıyor. Ve rebuild'den sonra "garanti olsun" diye çalıştırılan UPDATE STATISTICS, rebuild'in tam taramayla (FULLSCAN) ürettiği istatistiği yüzde 11'lik örneklemle değiştirdi; resumable rebuild de aynı şeyi kendiliğinden yaptı. Okuma tarafında ise logical reads page sayısını izliyor; fragmentation değil, page density belirleyici. Bu yazıda index bakımını, istatistikleri, DBCC CHECKDB'yi ve bunları bir arada çalıştıran Ola Hallengren'in betiklerini bu ölçümlerle kuruyoruz.
Fragmentation, Page Density ve Microsoft'un Güncel Önerisi
sys.dm_db_index_physical_stats iki sayı verir. avg_fragmentation_in_percent, leaf seviyesindeki page'lerin mantıksal sırasının diskteki fiziksel sırayla ne kadar uyuşmadığıdır (logical fragmentation). avg_page_space_used_in_percent ise page'lerin ne kadar dolu olduğudur (page density). LIMITED mod yalnız fragmentation'ı ucuza hesaplar; density için SAMPLED ya da DETAILED gerekir ve büyük tablolarda DETAILED bütün index'i okur.
Yıllarca "yüzde 5 ile 30 arası reorganize, 30 üstü rebuild" kuralı uygulandı. Microsoft Learn'deki index bakımı belgesi bugün bu eşikleri önermiyor. Belgenin özü şu: bakım kararı sabit bir fragmentation ya da density eşiğine göre değil, iş yüküne göre ölçülerek verilmeli; birçok iş yükünde page density'yi artırmak fragmentation'ı azaltmaktan daha fazla fark yaratır; rebuild sonrası görülen hızlanmanın önemli bir kısmı aslında rebuild'in istatistikleri tam taramayla güncellemesinden gelir ve aynı etki çok daha ucuza yalnız istatistik güncelleyerek alınabilir. Fragmentation'ın zararı, büyük aralık taramalarında SQL Server'ın az sayıda büyük okuma yerine çok sayıda küçük okuma yapmasıdır; sıralı okumayla rastgele okuma arasında fark olmayan modern SSD'lerde (flash tabanlı diskler) bu etki küçülür. Belgenin önerdiği strateji de buna uygun: bakımın etkisini Query Store ile önce/sonra (A/B) ölç (8. yazı), rebuild işe yarıyorsa yerine istatistik güncellemeyi dene, fragmentation ve density'yi zaman içinde izle ve yalnız performansı düşen sorguların index'lerini bakıma al.
Fill factor da bu bağlamda önemli. Fill factor'ı 100'ün altına çekmek page'leri bilerek boş bırakmaktır; yani page density'yi kendin düşürürsün. Microsoft yalnız çok sık page split yaşayan index'ler (örneğin sıralı olmayan GUID (rastgele üretilen 16 byte'lık kimlik) key'li ve sık güncellenen) için 100'den farklı bir değer öneriyor. Serinin 2. yazısındaki NEWID deneyini hatırla.
REBUILD, REORGANIZE, ONLINE ve RESUMABLE
REORGANIZE her zaman online'dır, yalnız leaf seviyesini düzenler ve page'leri fill factor'a kadar sıkıştırır. Yarıda kesilirse yaptığı iş kaybolmaz; büyük bir index'i birkaç gecede parça parça reorganize edebilirsin. ALLOW_PAGE_LOCKS = OFF olan index'te çalışmaz ve istatistik güncellemez.
REBUILD index'i baştan yazar, bütün seviyelerdeki fragmentation'ı giderir ve rowstore index'lerde istatistiği tam taramayla günceller. Offline rebuild tabloyu süre boyunca kilitler ama en ucuz yoldur; yukarıda en az log'u o üretti. ONLINE = ON tabloyu sonuna kadar açık tutar, karşılığında daha çok log, tempdb ve süre harcar. RESUMABLE = ON online rebuild'i duraklatılabilir yapar. Bir oturumda büyük clustered index'in rebuild'ini başlatıp ikinci oturumdan duraklattım:
ALTER INDEX PK_OrderItems ON dbo.OrderItems REBUILD WITH (ONLINE = ON, RESUMABLE = ON, MAXDOP = 1);
ALTER INDEX PK_OrderItems ON dbo.OrderItems PAUSE;
SELECT name, state_desc, CAST(percent_complete AS decimal(5,1)) AS percent_complete, page_count
FROM sys.index_resumable_operations;
name state_desc percent_complete page_count
------------- ---------- ---------------- ----------
PK_OrderItems PAUSED 54.9 7265
İlk oturum ise şu mesajla koptu:
Msg 1219, Level 16, State 1, Server sql1, Line 1
Your session has been disconnected because of a high priority DDL operation.
ALTER INDEX PK_OrderItems ON dbo.OrderItems RESUME;
SELECT COUNT(*) AS resumable_ops FROM sys.index_resumable_operations;
resumable_ops
-------------
0
Rebuild yüzde 54,9'dan devam etti ve bitti. Bakım penceresi sabahın altısında kapanıyorsa MAX_DURATION = 120 MINUTES ile rebuild'i kendiliğinden duraklatıp ertesi gece kaldığı yerden sürdürebilirsin. İki bedeli var: duraklatılmış bir resumable işlem bitene kadar tablodaki her yazma index'in ikinci kopyasını da günceller, bu yüzden tamamlamayacaksan ABORT et; ve tablodaki gibi resumable rebuild istatistiği tam taramayla değil, varsayılan örneklemle günceller. ONLINE ve RESUMABLE Enterprise edition özellikleridir (Developer edition dahil); Standard edition'da offline rebuild ve reorganize kalır.
İstatistikleri Güncellemek
İstatistiğin optimizer için ne anlama geldiğini, histogram'ı ve otomatik güncelleme eşiğini 4. yazıda gördük. Bakım açısından üç kural yeter. Rebuild edilen index'in istatistiğine aynı gece bir daha dokunma; yukarıdaki son satır, tam taramalı istatistiği örneklemliyle değiştirmenin nasıl olduğunu gösteriyor. Reorganize edilen index'in ve index'e bağlı olmayan column istatistiklerinin (_WA_Sys_...) güncellenmesini ayrıca planla. Ve yalnız değişmiş istatistikleri güncelle: sys.dm_db_stats_properties'teki modification_counter bunu söyler.
AUTO_UPDATE_STATISTICS açık kalmalı. AUTO_UPDATE_STATISTICS_ASYNC ise sorgunun güncel olmayan istatistik yüzünden derlemede beklemesini engeller, güncellemeyi arka plana atar; eşik aşıldığı anda büyük bir tabloda istatistik güncellemesini bekleyen OLTP (çok sayıda kısa okuma/yazma yapan işlem yükü) sorguları görüyorsan değerlendir. Bazı istatistikler için örneklem yetmez; o durumda WITH SAMPLE ... PERCENT, PERSIST_SAMPLE_PERCENT = ON örneklem oranını sonraki otomatik güncellemeler için de sabitler.
DBCC CHECKDB: Ne Sıklıkla ve Corruption Bulununca İlk Adımlar
Backup bozuk bir veritabanını da yedekler; WITH CHECKSUM (20. yazı) yalnız page checksum'ı tutmayan page'leri yakalar. Veritabanının mantıksal ve fiziksel tutarlılığını kontrol eden tek araç DBCC CHECKDB'dir. Corruption'ı görmek için kendi deneme veritabanımda IX_OrderItems_BookId'nin bir leaf page'ini, belgelenmemiş DBCC WRITEPAGE ile, buffer pool'u atlayarak diske doğrudan yazıp bozdum. Bunu yalnız atılacak bir deneme veritabanında yap:
SELECT TOP (1) allocated_page_file_id, allocated_page_page_id
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.OrderItems'), 4, NULL, 'DETAILED')
WHERE page_type_desc = 'INDEX_PAGE' AND page_level = 0
ORDER BY allocated_page_page_id DESC;
ALTER DATABASE sqls_21 SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DBCC WRITEPAGE (N'sqls_21', 1, 58908, 4000, 4, 0x00000000, 1);
ALTER DATABASE sqls_21 SET MULTI_USER;
Uygulama bunu ancak bozuk page'i okuyan bir sorgu çalıştığında öğrenir:
SELECT COUNT_BIG(*) AS lines FROM dbo.OrderItems WITH (INDEX (IX_OrderItems_BookId));
Msg 824, Level 24, State 2, Server sql1, Line 1
SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0x73eb344b; actual: 0x52ca156a). It occurred during a read of page (1:58908) in database ID 43 at offset 0x0000001cc38000 in file '/var/opt/mssql/data/sqls_21.mdf'. ...
DBCC CHECKDB (sqls_21) WITH NO_INFOMSGS, ALL_ERRORMSGS;
Msg 8978, Level 16, State 1, Server sql1, Line 1
Table error: Object ID 1349579846, index ID 4, partition ID 72057594048348160, alloc unit ID 72057594055098368 (type In-row data). Page (1:55584) is missing a reference from previous page (1:58908). Possible chain linkage problem.
Msg 8976, Level 16, State 1, Server sql1, Line 1
Table error: Object ID 1349579846, index ID 4, partition ID 72057594048348160, alloc unit ID 72057594055098368 (type In-row data). Page (1:58908) was not seen in the scan although its parent (1:55456) and previous (1:58907) refer to it. Check any previous errors.
Msg 8939, Level 16, State 98, Server sql1, Line 1
Table error: Object ID 1349579846, index ID 4, partition ID 72057594048348160, alloc unit ID 72057594055098368 (type In-row data), page (1:58908). Test (IS_OFF (BUF_IOERR, pBUF->bstat)) failed. Values are 133129 and -4.
Msg 8928, Level 16, State 1, Server sql1, Line 1
Object ID 1349579846, index ID 4, partition ID 72057594048348160, alloc unit ID 72057594055098368 (type In-row data): Page (1:58908) could not be processed. See other errors for details.
CHECKDB found 0 allocation errors and 4 consistency errors in table 'OrderItems' (object ID 1349579846).
CHECKDB found 0 allocation errors and 4 consistency errors in database 'sqls_21'.
repair_allow_data_loss is the minimum repair level for the errors found by DBCC CHECKDB (sqls_21).
Son satır korkutucu: "en az repair_allow_data_loss gerekiyor". Bu noktada yapılacak en kötü şey o seçeneği çalıştırmaktır; adı üstünde, satır silerek tutarlılık sağlar. İlk adım hatanın nerede olduğunu okumaktır: bütün mesajlarda index ID 4 yazıyor. Index ID 0 heap, 1 clustered index, yani verinin kendisidir; 2 ve üstü nonclustered index'tir ve verinin bir kopyasını taşır. Bozulan bir nonclustered index'i silip yeniden oluşturmak veriyi clustered index'ten yeniden okur ve hiçbir şey kaybettirmez:
DROP INDEX IX_OrderItems_BookId ON dbo.OrderItems;
CREATE INDEX IX_OrderItems_BookId ON dbo.OrderItems (BookId);
DBCC CHECKDB (sqls_21) WITH NO_INFOMSGS;
SELECT COUNT_BIG(*) AS lines FROM dbo.OrderItems WITH (INDEX (IX_OrderItems_BookId));
DELETE FROM msdb.dbo.suspect_pages WHERE database_id = DB_ID('sqls_21');
lines
-------
2399937
CHECKDB bu sefer hiçbir şey yazmadı (NO_INFOMSGS ile temiz sonuç sessizdir) ve 2.399.937 satırın hepsi yerinde. REBUILD yerine silip oluşturmamın nedeni, online rebuild'in mevcut index'i kaynak olarak kullanabilmesi ve bozukluğu taşıyabilmesi. Bozulan clustered index ya da heap ise (index ID 0 veya 1) doğru yol backup'tan restore'dur: yalnız bozuk page'leri RESTORE DATABASE ... PAGE = '1:58908' ve ardından log zinciriyle restore edebilirsin (Enterprise edition'da veritabanı bu sırada açık kalır). Okunamayan page'ler msdb.dbo.suspect_pages'e yazılır; tablo 1.000 satırla sınırlı olduğu için olay kapandıktan sonra temizlenmeli. Ve corruption neredeyse her zaman depolama katmanından gelir: aynı diskte başka şeyler de bozuluyor olabilir.
Sıklık için Microsoft'un tek bir sayısı yok; yaygın uygulama şu: küçük ve orta veritabanlarında haftada bir tam CHECKDB. Büyük veritabanlarında gece WITH PHYSICAL_ONLY (page checksum'larını ve allocation yapısını kontrol eder, mantıksal kontrolleri atlar, çok daha kısa sürer) ve haftada bir ya da her restore tatbikatında tam CHECKDB. Tam kontrolü üretim sunucusu yerine restore edilen kopyada çalıştırmak hem yükü taşır hem de backup'ın gerçekten açıldığını kanıtlar.
Ola Hallengren Maintenance Solution
Bu işlerin hepsini kendin yazabilirsin, ama yaygın ve iyi test edilmiş bir çözüm var: Ola Hallengren'in MIT lisanslı SQL Server Maintenance Solution'ı. Tek bir betik IndexOptimize, DatabaseIntegrityCheck, DatabaseBackup procedure'lerini ve sonuçları yazan dbo.CommandLog tablosunu kurar. Betiğin başındaki iki satırı değiştirdim: nesneleri master yerine kendi veritabanıma kursun ve Agent job'larını oluşturmasın (job'ları 22. yazıda kuracağız):
USE [sqls_21]
DECLARE @CreateJobs nvarchar(max) = 'N'
Önce @Execute = 'N' ile, hiçbir şey çalıştırmadan, ne yapacağını görmek için bakım öncesi duruma döndürdüğüm veritabanında çağırdım:
EXEC dbo.IndexOptimize
@Databases = 'sqls_21',
@FragmentationLow = NULL,
@FragmentationMedium = 'INDEX_REORGANIZE,INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
@FragmentationHigh = 'INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
@FragmentationLevel1 = 5,
@FragmentationLevel2 = 30,
@MinNumberOfPages = 1000,
@UpdateStatistics = 'ALL',
@OnlyModifiedStatistics = 'Y',
@LogToTable = 'Y',
@Execute = 'N';
Command: ALTER INDEX [IX_OrderItems_BookId] ON [dbo].[OrderItems] REBUILD WITH (SORT_IN_TEMPDB = OFF, ONLINE = ON, RESUMABLE = OFF)
Comment: ObjectType: Table, IndexType: NonClustered, ImageText: No, FileStream: No, HasClusteredColumnstore: No, AllowPageLocks: Yes, PageCount: 5390, Fragmentation: 39.1095
Outcome: Not Executed
Command: ALTER INDEX [IX_OrderItems_OrderId] ON [dbo].[OrderItems] REORGANIZE WITH (LOB_COMPACTION = ON)
Comment: ObjectType: Table, IndexType: NonClustered, ImageText: No, FileStream: No, HasClusteredColumnstore: No, AllowPageLocks: Yes, PageCount: 4764, Fragmentation: 18.3249
Outcome: Not Executed
Eşikler hâlâ fragmentation'a göre (parametreler betiğin örneklerindeki gibi), ama iki iyi karar var: 1.000 page'ten küçük index'lere dokunmuyor ve sırada önce online rebuild'i deniyor. Gece kimse tabloya yazmıyorsa @FragmentationHigh = 'INDEX_REBUILD_OFFLINE' yedi kat daha az log demek. @Execute = 'Y' ile çalıştırıp sonucu tablodan okudum:
SELECT CommandType, ISNULL(IndexName, StatisticsName) AS object_name,
DATEDIFF(MILLISECOND, StartTime, EndTime) AS ms, ErrorNumber
FROM dbo.CommandLog
WHERE DatabaseName = 'sqls_21' AND ErrorNumber IS NOT NULL
ORDER BY ID;
CommandType object_name ms ErrorNumber
------------------ ------------------------- ---- -----------
UPDATE_STATISTICS PK_CommandLog 4 0
UPDATE_STATISTICS PK_OrderItems 65 0
ALTER_INDEX IX_OrderItems_BookId 795 0
ALTER_INDEX IX_OrderItems_OrderId 1860 0
UPDATE_STATISTICS IX_OrderItems_OrderId 82 0
UPDATE_STATISTICS _WA_Sys_00000002_5070F446 56 0
UPDATE_STATISTICS PK_Payments 48 0
UPDATE_STATISTICS PK_Shipments 48 0
Dikkat edilecek satırlar: rebuild edilen IX_OrderItems_BookId'nin istatistiği listede yok, reorganize edilen IX_OrderItems_OrderId'nin ve otomatik oluşturulmuş column istatistiğinin var. Yani betik, Önce Tahmin Et tablosundaki iki tuzağa da düşmüyor. Integrity check ve backup aynı kalıpla çalışıyor:
EXEC dbo.DatabaseIntegrityCheck @Databases = 'sqls_21', @CheckCommands = 'CHECKDB', @LogToTable = 'Y';
EXEC dbo.DatabaseBackup
@Databases = 'sqls_21',
@Directory = '/var/opt/mssql/backup',
@DirectoryStructure = '{DatabaseName}{DirectorySeparator}{BackupType}',
@FileName = '{DatabaseName}_{BackupType}_{Year}{Month}{Day}_{Hour}{Minute}{Second}.{FileExtension}',
@BackupType = 'FULL',
@Compress = 'Y',
@CheckSum = 'Y',
@Verify = 'Y',
@LogToTable = 'Y';
Command: DBCC CHECKDB ([sqls_21]) WITH ALL_ERRORMSGS
CHECKDB found 0 allocation errors and 0 consistency errors in database 'sqls_21'.
Outcome: Succeeded
Command: BACKUP DATABASE [sqls_21] TO DISK = N'/var/opt/mssql/backup/sqls_21/FULL/sqls_21_FULL_20261114_235903.bak' WITH CHECKSUM, COMPRESSION (ALGORITHM = MS_XPRESS, LEVEL = LOW)
Outcome: Succeeded
Command: RESTORE VERIFYONLY FROM DISK = N'/var/opt/mssql/backup/sqls_21/FULL/sqls_21_FULL_20261114_235903.bak' WITH CHECKSUM
The backup set on file 1 is valid.
Outcome: Succeeded
@DirectoryStructure ve @FileName'i bilerek verdim: varsayılan klasör yapısı sunucu adını da içeriyor. Varsayılanlar, Windows'ta ve Linux'ta aynı çalışan, kaynak kontrolünde tutulabilen T-SQL çağrıları; bu, SSMS'teki (SQL Server'ın masaüstü yönetim aracı) Maintenance Plan Wizard'a göre en büyük fark. Wizard'ın ürettiği plan bir SSIS (SQL Server Integration Services; Microsoft'un veri aktarım ve iş akışı aracı) paketi olarak msdb'de saklanır, diff'i okunmaz ve "bütün index'leri her gece rebuild et, ardından bütün istatistikleri güncelle" gibi tam da yukarıdaki tuzağı kuran seçenekleri yan yana sunar. Microsoft'un Learn'de referans verdiği bir başka hazır çözüm Adaptive Index Defrag'dır.
Bookstore: Gece Bakım Penceresini Ölçerek Kurmak
Bookstore'da siparişler gece 01:00 ile 05:00 arasında en düşük seviyede, ama sıfır değil: kurumsal müşterilerin entegrasyonları gece de sipariş yazıyor. Bu yüzden offline rebuild yalnız kısa süren küçük index'ler için uygun, OrderItems gibi büyük tablolarda online gerekiyor. Ölçümler de planın çerçevesini veriyor: bu index'te online rebuild 252 MB, reorganize 128 MB log üretti; aynı gece bütün büyük index'ler bakımdan geçerse log backup'ları ve replica gecikmesi bu kadar büyür. Bookstore için kurduğumuz plan şu: her gece yalnız değişmiş istatistikler; haftada bir, page density'si belirlenen bir eşiğin altına inen ve 1.000 page'ten büyük index'ler için online rebuild; reorganize yok (bu index'te log maliyeti rebuild'den yüksek ve istatistiği güncellemiyor); her gece PHYSICAL_ONLY, pazar günü restore tatbikatı veritabanında tam CHECKDB. Density eşiği için yüzde 80 bir başlangıç değeri; doğrusu, Query Store'da raporlama sorgularının süresini density ile birlikte birkaç hafta izleyip belirlemek. Ola'nın IndexOptimize'ı page density'yi eşik olarak almadığı için bu kurala uyan index'leri bir sorgu seçip @Indexes parametresine veriyor.
SQL Server 2019/2022'de farkı: RESUMABLE rebuild 2017'de, resumable online index oluşturma 2019'da geldi; MAX_DURATION ve WAIT_AT_LOW_PRIORITY üç sürümde de var. 2022'de istatistikler için AUTO_DROP seçeneği eklendi. 2025'te ordered columnstore index'ler online oluşturulabiliyor ve readable secondary'lerde kalıcı istatistik var. DBCC CHECKDB'nin söz dizimi ve davranışı üç sürümde aynı.
Üretimde Dikkat
Bakımın log'unu hesaba kat. Online rebuild yukarıda index'in kendisinden (yaklaşık 33 MB) yedi kat büyük log üretti; FULL recovery'de bu log backup'larına, availability group'ta secondary'lere gider. Büyük bir rebuild'den sonraki ilk log backup'ı ve replica gecikmesini izle.
Rebuild için diskte index'in ikinci bir kopyasına yer olmalı; sıralama için SORT_IN_TEMPDB = ON kullanıyorsan tempdb'de de.
CommandLog'u izle ve temizle. Hata veren komutlar ErrorNumber ile orada; tablo kendi kendine küçülmez; Ola'nın kurulumu bunun için "CommandLog Cleanup" adında ayrı bir job oluşturur.
Sık Düşülen Tuzaklar
Her gece her index'i rebuild etmek. Log, CPU ve replica gecikmesi harcar; kazancın çoğu zaman yalnız istatistik güncellemesinden gelir.
Rebuild'den sonra istatistikleri ikinci kez güncellemek. Tablodaki son satır: tam taramalı istatistik yüzde 11'lik örneklemle değişti.
REORGANIZE'ı her zaman ucuz sanmak. Bu index'te offline rebuild'in 3,6 katı log üretti.
DBCC CHECKDB'yi hiç çalıştırmamak ya da hata görünce REPAIR_ALLOW_DATA_LOSS'u ilk adım sanmak. Önce index ID'yi oku; nonclustered index ise sil ve yeniden oluştur.
Fill factor'ı "page split olmasın" diye her index'te 80'e çekmek. Bütün index'lerin page density'sini kalıcı olarak yüzde 80'e indirirsin.
Küçük Bir Deneme
★☆☆ Kendi veritabanında bütün index'lerin fragmentation ve page density değerlerini SAMPLED modda listele. Density'si yüzde 70'in altında olan ve 1.000 page'ten büyük index var mı?
★★☆ Önce Tahmin Et deneyini SIMPLE recovery'de tekrarla. Offline rebuild'in log'u nasıl değişiyor? Reorganize'ınki?
★★★ IndexOptimize'ı @Resumable = 'Y' ve @TimeLimit ile çalıştır ve yarıda kalan bir rebuild'in sys.index_resumable_operations'ta nasıl göründüğüne bak. Sonraki çalıştırma onu sürdürüyor mu?
Bu yazıdaki bütün işler bir zamanlayıcı bekliyor: gece çalışacaklar, başarısız olunca birine haber verecekler. Bir sonraki yazıda SQL Server Agent'ın job, step, schedule, alert ve operator yapısını, Database Mail'i ve Agent'ın Linux ve container'daki sınırlarını inceleyeceğiz.