SQL Server

SQL Server – Adım Adım Eksik Index’lerin Belirlenip Oluşturulması Operasyonu

Bir önceki makalemizde sistemde olmayan ama olması tavsiye edilen index’leri nasıl sorgulayabileceğimizi incelemiştik. (Bkz. Bir önceki makalemde)

Bugünkü makalemde ise belirlediğimiz bu missing index’leri adım adım analiz edip create edip etmemeye karar vereceğiz. Ve create ettikten sonrada nasıl monitör edebileceğimizi görüyor olacağız. Yani baştan sona bir missing index operasyonunun nasıl yapıldığını görüyor olacağız.

Makale şu alt başlıklar altında yer alacaktır;

1.       Missing Index’lerin sorgulanması ve create edilecek index’e karar verilmesi.

2.       Create edilmesi düşünülen index’in ve bulunduğu tablonun analizi

a.       Tablo kayıt sayısı ve boyut analizi

b.      Tablonun üzerinde bulunan diğer index’lerin kullanım istatistikleri

c.       Tablonun üzerinde bulunan index’lerin hangi kolonlar üzerinde bulunduklarının kontrolü

d.      Tablonun üzerinde bulunan index’lerin Include Column içerip içermediklerinin kontrolü

3.       Tablonun query stats’larına bakılıp ilgili missing index’e sebep olan script’in bulunması.

4.       Bulunan script’in şu anki IO değerlerinin sorgulanması.

5.       Index’in create edilmesi

6.       Sorgunun IO değerine tekrar bakılması ve ne kadar düştüğünün incelenmesi.

7.       Create edilen index’in kullanım istatistiklerinin monitör edilmesi

  

1. Missing Index’lerin sorgulanması ve create edilecek index’e karar verilmesi

Missing Index’lerin nasıl sorgulanabileceği ile ilgili detay bilgiye şu makaleden erişebilirsiniz. Bu makalemde detaya girmeden direk missing index sorgusunu çalıştıracağız.

Daha öncesinde bir Missing Index durumu oluşturmak için AdventureWorks te bulunan Person.Address tablosuna City ve PostalCode üzerinden select çekelim. Bu kolonlar üzerinde tanımla olan bir index olmadığı için bir missing index kaydı oluşacaktır.

–Aşağıdaki select’i birden fazla kez çalıştırınız

select * from Person.Address where City=‘Duvall’ and PostalCode=‘98019’

 

Şimdi AdventureWorks DB’si için Missing Index sorgumuzu çalıştıralım.

select DB_NAME(id.database_id) as databaseName,

                id.statement as TableName,

                id.equality_columns,

                id.inequality_columns,

                id.included_columns,

                gs.last_user_seek,

                gs.user_seeks,

                gs.last_user_scan,

                gs.user_scans,

                gs.avg_total_user_cost * gs.avg_user_impact * (gs.user_seeks + gs.user_scans)/100 as ImprovementValue                

from sys.dm_db_missing_index_group_stats gs

INNER JOIN sys.dm_db_missing_index_groups ig on gs.group_handle = ig.index_group_handle

INNER JOIN sys.dm_db_missing_index_details id on id.index_handle = ig.index_handle

where DB_NAME(id.database_id) = ‘AdventureWorks’

order by avg_total_user_cost * avg_user_impact * (user_seeks + user_scans)/100 desc

 

Sorgu sonucunda aşağıdaki gibi bir sonuç çıkacaktır.

image001

Gördüğünüz gibi Person.Address tablosu üzerinde City ve PostalCode kolonları için bir missing index önerisi çıktı. Bu index olsaydı 28 kez seek edileceğini ve en son 10.12.2010 14:33 tarihinde seek edildiği bilgilerine de bu sorgu sonucunda erişebiliyoruz.

Dolayısıyla üzerinde çalışacağımız ve create etmek istediğimiz index’i belirledik. Person.Address tablosu için City ve PostalCode kolonları.

Şimdi gerçekten böyle bir index’e ihtiyaç var mı diye detay analizlerimize başlayalım.

2. Create edilmesi düşünülen index’in ve bulunduğu tablonun analizi

 

Bu başlık altında aşağıdaki detay incelemeleri yapacağız.

·         Tablo kayıt ve boyut analizi = Tablonun kayıt sayısına bakarak index’in gerekliliğinin ilk sorgulamasını yapıyoruz. Eğer ufak bir tablo ise index’in create edilmesinin çok gerekli olmadığını düşünebiliriz. Ya da boyutu çok büyükse oluşturacağımız index’in de iyi bir yer kaplayacağını düşünerek create edip etmemeyi tekrar değerlendirebiliriz. Ama ben kendi çalışmalarımda bu adıma sadece kontrol için bakıyorum. Çoğu durumda bu adım index create edip etmeme kararıma çok fazla etki etmiyor.

·         Tablonun üzerinde bulunan diğer index’lerin kullanım istatistikleri = Tabloda bulunan diğer index’lerin kullanım istatistiklerine bakıyoruz. Bu kontrol sırasında diğer index’lerin kullanım rakamlarına göre bu yeni oluşturacağımız index’in cluster index ya da non-cluster index olup olmayacağına karar verebiliriz.

·         Tablonun üzerinde bulunan index’lerin hangi kolonlar üzerinde bulunduklarının kontrolü = Tablonun hali hazırda sahip olduğu index’lerin hangi kolonlar üzerine tanımlandıklarına bakıyoruz. Belkide bizim oluşturmak istediğimiz index’i başka bir index farklı kolon sıralaması ile cover etmekte. Eğer böyle bir durum var ise index create etmemeyi düşünebiliriz.

·         Tablonun üzerinde bulunan index’lerin Include Column içerip içermediklerinin kontrolü = sp_helpindex sistem procedure’ü included column’ları vermemekte. Dolayısıyla son adım olarak bu kontrolü de yapıyoruz.

 

Yukarıdaki analizlerin tamamı için aşağıdaki sorgu bloğunu kullanabiliriz. Parametre olarak hangi tabloyu analiz etmek istediğimizi belirtiyoruz.

declare @tablename varchar(100)=‘Person.Address’

 

–1. Tablonun kayıt sayısına ve size ına bakılır.

exec(‘sp_spaceused ”’+@tablename+”’ ‘)

 

–2. Tablonun diğer index lerinin Index Usage Statistics lerine bakılır.

select OBJECT_SCHEMA_NAME(us.object_id),OBJECT_NAME(us.object_id) as tableName,

    i.index_id,

                i.name as indexName,

                us.last_user_seek,

                us.user_seeks,

                CASE us.user_seeks WHEN 0 THEN 0

                               ELSE us.user_seeks*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS SeekPercentage,

                us.last_user_scan,

                us.user_scans,

                CASE us.user_scans WHEN 0 THEN 0

                               ELSE us.user_scans*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS ScanPercentage,

                us.last_user_lookup,

                us.user_lookups,

                us.last_user_update,

                us.user_updates,

                CASE us.user_scans + us.user_seeks WHEN 0 THEN 0

                               ELSE us.user_updates*1.0/(us.user_scans + us.user_seeks)*100.0 END as UpdatesPercentage            

FROM sys.dm_db_index_usage_stats us

INNER JOIN sys.indexes i ON i.object_id=us.object_id and i.index_id = us.index_id

WHERE us.database_id = DB_ID() and OBJECT_SCHEMA_NAME(us.object_id)+‘.’+OBJECT_NAME(us.object_id) = @tablename

–3. Diğer index’lerin hangi kolonlar üzerinde olduğuna bakılır.

exec(‘sp_helpindex ”’+@tablename+”’ ‘)

 

–4. Diğer index lerden included column içeren var mı yok mu diye bakılır.

select * from sys.index_columns where OBJECT_ID(@tablename)=object_id and is_included_column!=0

Sorgu sonucu şu şekilde bir sonuç olacaktır.

image002

3. Tablonun query stats’larına bakılıp ilgili missing index’e sebep olan script’in bulunması

Missing index’in hangi sorgu sonucunda oluştuğunu bilmek istiyorum. Çünkü bizim yaptığımız örneğin tersine reel de ben hangi sorgudan bu index’in missing olarak belirlendiğini bilmiyorum. Bunun için sys.dm_exec_query_stats dmv’sini kullanıyorum.

select

     st.[text],

     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 statement_text,       

     qs.last_execution_time,

     qs.execution_count,

     qs.total_logical_reads as total_logical_read,

     qs.total_logical_reads/execution_count as avg_logical_read,

     qs.total_worker_time/1000000 as total_cpu_time_sn,

     qs.total_worker_time/qs.execution_count/1000 as avg_cpu_time_ms,

     qp.query_plan,

     qs.last_logical_reads,

     qs.plan_generation_num

from sys.dm_exec_query_stats qs

cross apply sys.dm_exec_sql_text(qs.sql_handle) st

cross apply sys.dm_exec_query_plan(qs.plan_handle) qp

where 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) like ‘%Person.Address%’

Sorgu sonucu aşağıdaki gibi bir sonuç olacaktır.

image003

Gördüğünüz gibi ilk satırda bizi ilgilendiren script’i bulduk. Bu script 28 kez çalıştırılmış ve her çalışmada 236 logical read yapmış.

 

4.Bulunan script’in şu anki IO değerlerinin sorgulanması

 

Tanımlayacağımız index’in logical read değerlerinde bir değişiklik yapıp yapmadığını görmek için şu anki yaptığı IO değerine bakmak istiyorum. Bir önceki adımda ilgili script’i bulmuştum. Şimdi bu script’in IO değerlerine bakıyorum.

set statistics IO ON

set statistics profile ON

select * from Person.Address where City=‘Duvall’ and PostalCode=‘98019’

 

image004

Görüldüğü gibi index creatinden önce bu sorgu 236 logical read yapmakta.

5. Index’in create edilmesi

 

Bütün analizlerimizi yaptık ve index’i create etmeye karar verdik.

USE [AdventureWorks]

GO

CREATE NONCLUSTERED INDEX [IX_Address_City_PostalCode]

ON [Person].[Address] ([City],[PostalCode])

6.Sorgunun IO değerine tekrar bakılması ve ne kadar düştüğünün incelenmesi

 

Index’i create ettikten sonra tekrar IO sorgumuzu çekelim ve yapılan IO da bir iyileştirme olup olmadığını kontrol edelim.

set statistics IO ON

set statistics profile ON

select * from Person.Address where City=‘Duvall’ and PostalCode=‘98019’

 

image005

Gördüğünüz gibi index create’inden önce 236 olan logical read index create’inden sonra 22’ye düşmüş durumda. Bu durumda 10 kat iyileştirme yaptığımızı düşünebiliriz.

7.Create edilen index’in kullanım istatistiklerinin monitor edilmesi

 

Missing Index’leri create ettikten sonra periyoduk olarak (ben 1 haftada bir yapıyorum) create ettiğimiz indexlerin usage statistic’lerine bakıp ne kadar seek,scan aldığına, ne kadar update gördüğüne bakmakta fayda var. Bu şekilde create edip kullanılmadığını gördüğümüz index’leri drop etmeyi düşünebiliriz.

 

select OBJECT_NAME(us.object_id) as tableName,

                i.name as indexName,

                us.last_user_seek,

                us.user_seeks,

                CASE us.user_seeks WHEN 0 THEN 0

                               ELSE us.user_seeks*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS SeekPercentage,

                us.last_user_scan,

                us.user_scans,

                CASE us.user_scans WHEN 0 THEN 0

                               ELSE us.user_scans*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS ScanPercentage,

                us.last_user_lookup,

                us.user_lookups,

                us.last_user_update,

                us.user_updates

FROM sys.dm_db_index_usage_stats us

INNER JOIN sys.indexes i ON i.object_id=us.object_id and i.index_id = us.index_id

WHERE us.database_id = DB_ID(‘AdventureWorks’) and OBJECT_NAME(us.object_id) = ‘Address’

 

image006

Gördüğünüz gibi create ettiğimiz index çok güzel bir şekilde seek alarak kullanılmakta. Eğer seek+scan değeri update den küçük olsaydı bu index’i drop etmeyi düşünebilirdik.

Adım adım missing index operasyonunu nasıl gerçekleştirdiğimizi incelemeye çalıştık. Index kullanımı büyük projeler için çok önemli olduğundan bu kontrollerin periyodik olarak production ortamları için yapılmasının önemine tekrar vurgu yaparak yazımı noktalamak istiyorum.

İlgili Makaleler

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Başa dön tuşu