Recent comments

None


İçerik Ara











Yasal Uyarı
Bu sitede sunulan tüm bilgi ve dökümanlar Turgay Sahtiyan tarafından yazılmaktadır. Yazıların kaynak göstermek şartıyla kullanılması serbesttir.

© Copyright 2009-2013
Takvim
<<  Haziran 2017  >>
PaSaÇaPeCuCuPa
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
Keywords

Mart 2011’de yayınladığım SQL Server’da Index Kavramı başlıklı makalemden sonra bugün de performans iyileştirmenin bir diğer büyük adımı olan SQL Server’da İstatistis (Statistics) Kavramı üzerine yazılmış detaylı makalemi paylaşıyorum.

Bu makale, SQL Server’da istatistiklerin kullanım amacını,faydalarını ve güncel olmalarının önemini sorguladıktan sonra, istatistiklerinin içeriğinin incelenmesi ve istatistiklerin otomatik oluşturulma ve güncellenme parametrelerinin ve bu parametrelerin best practice’lerinin neler oldukları üzerinde duracaktır.

[more]

Makaledeki ana başlıklar şu şekildedir.

  • İstatistik (Stat) Nedir?
  • DBCC SHOW_STATISTICS Komutu ile İstatistik İçeriğini Görüntülemek
  • İstatistiklerin Güncel Olmasının Önemi
  • İstatistiklerin Otomatik Oluşturulma ve Güncellenme Parametreleri
    • Auto_Create_Statistics
    • Auto_Update_Statistics
    • Auto_Update_Statistics_Async
  • SQL Server Profiler ile İstatistik Oluşma ve Güncellenme İşlemlerini İzlemek
  • Sonuç

Makale çok uzun olduğu için word formatında yayınlayacağım. Dosyayı buradan indirebilirsiniz.

 

İyi Çalışmalar

Turgay Sahtiyan

Not : Blog haricinde, faydali gördügüm yazilari ve linkleri twitter adresimden paylasiyorum. Beni twitter'da takip etmek için : twitter.com/turgaysahtiyan


Stored Procedure(SP) ilk çalıştırıldığı zaman istatistikler göz önüne alınarak Query Optimizer tarafından en optimum Query Plan oluşturulur ve daha sonra kullanılmak üzere Plan Cache’e konulur. Aynı SP farklı bir zamanda tekrar çalıştırıldığında Cache’deki plan’ın geçerliliği kontrol edilir ve eğer plan geçerli yani güncel ise tekrar Query Plan oluşturulmak için zaman harcanmayıp plan Cache’den çağırılır ve kullanılır.

Query Plan oluşturma işlemi bazı durumlarda çok fazla CPU kaynağı tükettiği için bu şekilde bir cache’lenme mekanizması kullanılır. Fakat bazı durumlarda cache’lenen plan güncelliğini yitirmiş olabilir. Örneğin SP içinde geçen bir tabloda plan cache’lendikten sonra çok fazla data değişimi olduysa bu durumda istatistikler güncelliğini yitirecek dolayısıyla da cache’lenen plan güncelliğini yitirecektir. Ya da SP’nin içinde geçen tablolarda index ekleme,silme gibi DDL (Data Definition Language) değişiklikleri yapılırsa yine cache’lenen plan güncelliğini yitirmiş olacaktır. Böyle bir durumda SP’nin yeniden derlenip yeni bir Query Plan’ın oluşturulması gerekmektedir. İşte bu duruma ReCompilation denilmektedir.

[more]

Yukarıda anlattığım şekilde istatistik değişimi ya da DDL değişikliği gibi işlemler sonucunda recompilation gerçekleşebileceği gibi SP oluşturulurken yazılan “WITH RECOMPILE” anahtar kelimesi SP’nin her çalışmasın recompile edilmesi sağlanabilir. Örneğin aşağıdaki şekilde oluşturulan SP her çağırılışında tekrar derlenecektir.

CREATE PROC dbo.sp_1
WITH RECOMPILE
AS
	SELECT * FROM Tbl1

 

WITH RECOMPILE anahtar kelimesi ile oluşturulan SP’lerin planları Plan Cache’de saklanmaz, SP her çalıştırıldığında tekrar oluşturulur. Bu seçenek, SP’nin aldığı parametrelere göre değişik Query Plan’lar oluşturduğu durumda kullanılabilir. Örneğin SP “a” parametresi ile çağırıldığında index scan, “b” parametresi ile çağırıldığında index seek yapıyorsa yani gönderilen parametreye göre Query Plan’ı değişiyorsa WITH RECOMPILE anahtar kelimesinin kullanılması mantıklı olacaktır.

WITH RECOMPILE ile SP’nin tamamı recompile edilir. SP’nin tamamının değilde içinde geçen bazı sorgu parçacıklarının yeniden derlenmesi isteniyorsa sorguların WITH RECOMPILE anahtar kelimesi ile yazılması gerekir.

SP’ler aşağıdaki durumlardan herhangi biri oluştuğunda recompile olacak yani yeniden derlenecektir.

  • Schema değiştiğinde
  • İstatistik değiştiğinde
  • SP’nin oluşturulma kodunda “With Recompile” anahtar kelimesi kullanıldığında
  • Set opsiyonları değiştiğinde
  • Temp table değiştiğinde
  • Remote rowset değiştiğinde
  • For browse perms değiştiğinde
  • Query notification environment değiştiğinde
  • Partition view değiştiğinde
  • Cursor seçenekleri değiştiğinde
  • Statement’ta Recompile seçeneği kullanıldığında

SP’ler yukarıda anlattığım şekilde recompile olabileceği gibi recompile olması için elle de tetiklenebilir.

EXEC sp_recompile N'dbo.sp_1'

 

Yukarıdaki kod vasıtasıyla sp_1 SP’sine recompile olması için bir işaret konulur. Bu SP bir dahaki sefer çalıştırılmak istendiğinde yeniden derlenecektir. sp_recompile sistem prosedürüne parametre olarak tablo ya da view de verilebilir. Bu durumda tabloya referans olan SP’ler recompile olacaktır.

SQL Server 2005’ten önceki sürümlerde SP recompilation işlemi SP’nin tamamında gerçekleştirilmekteydi. Yani örneğin SP’nin içinde bulunan bir sorgunun recompile edilmesi gerektiğinde SP’nin tamamı recompile edilmekteydi. 2005’ten sonraki versiyonlarda ise artık sadece recompile edilmesi gereken sorgu recompile edilmektedir. Bu şekilde çok büyük SP’lerde bir sorgu için SP’nin tamamı recompile edilmektense sadece ilgili kısım recompile edilebilmektedir. Bu da performans artışı anlamına gelmektedir.

Ayrıca SP recompile edilirken blocking’lere sebebiyet vermektedir. Bu yüzden çok fazla recompile olan SP ya da sorguların analiz edilmesinde fayda vardır.

ReCompilation Olaylarının İzlenmesi

SQL Server Profiler - SQL:StmtRecompile - SP:Recompile

Recompilation olayları SQL Server Profiler ile izlenebilmektedir. Bunun için kullanılan olaylar SQL:StmtRecompile ve SP:Recompile olaylarıdır.

SP:Recompile olayı ile recompile olan SP’ler izlenebilir. SQL:StmtRecompile olayı ile ise recompile olan sorgular izlenebilir. Daha önce bahsettiğim gibi SQL Server 2005 sürümünden sonra bir sorgunun recompile edilmesi gerektiğinde SP’nin tamamı değil sadece ilgili sorgu recompile edilmektedir. Dolayısıyla recompile işlemlerini izlemek için SP:Recompile yerine SQL:StmtRecompile olayının kullanılması daha mantıklı olacaktır.

Windows Performance Counter - SQL Re-Compilation/sec

Recomplation işlemleri windows performance counter’lar ile de izlenebilmektedir. Bunun için kullanılan performance counter SQL Re-Compilation/sec performance counter’ıdır. Genelde bu counter’ın toplam batch request’in %10’undan az olması beklenir. Bu değerden daha yüksek değerlerde, recompile olan sorgu veya SP’lerin detaylı incelenmesinde, neden recompile olduklarının analiz edilmesinde fayda vardır. Çünkü tekrar etmek gerekirse compilation işlemi CPU kaynaklarının çok fazla tüketilmesine sebep olabilir.

Activity Monitor

Recompilation olan SP ya da sorguların izlenmesi için kullanılan bir diğer araç ise SQL Server 2008 ile gelen Activity Monitor ekranıdır. Bu ekranda bulunan Recent Expensive Queries kısmındaki Plan Count kolonu ilgili SP’nin ya da sorgunun kaç kez recompile edildiğini göstermektedir.

1

Sys.dm_exec_query_stats – plan_generation_num

Recompilation’ları izlemenin bir diğer yolu da sys.dm_exec_query_stats DMV’sidir. Bu DMV’de bulunan plan_generation_num kolonu ile ilgili sorgunun kaç kez recompile edildiği bulunabilir.

select
	st.text,
	qs.sql_handle,
	qs.plan_generation_num,
	qs.creation_time,
	qs.execution_count,
	st.dbid,
	st.objectid
from sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(sql_handle) st
where qs.plan_generation_num>1 and st.objectid is not null
order by qs.plan_generation_num desc

 

2

Ayrıca gene bu DMV sonucunda gelen creation_time kolonu vasıtasıyla sorgunun en son ne zaman recompile edildiği bilgisine ulaşılabilmektedir.

Sonuç

SP’nin yeniden derlenmesi bazı durumlarda CPU kaynaklarının çok fazla kullanılmasına neden olabilir. Ayrıca yeniden derleme işlemi sırasında blocking’ler oluşabilir. Bu yüzden recompilation olayları izlenmeli, çok fazla recompile olan sorgu ya da SP’lerin recompile nedenleri analiz edilmelidir.

Not : Blog haricinde, faydali gördügüm yazilari ve linkleri twitter adresimden paylasiyorum. Beni twitter'da takip etmek için : twitter.com/turgaysahtiyan


Bugünkü makalemde, heap tablolarda karşımıza çıkan Forwarded Record konusu üzerine konuşuyor olacağız. Alt başlıklarımız şu şekilde;

  • Forwarded Record Nedir?
  • Forwarded Record Nasıl Oluşur?
  • Forwarded Record Neden Oluşur? SQL Server’ın Bu Davranışının Nedeni Nedir?
    • NonClustered Index İçeren Heap Tablolarda Forwarded Record
    • NonClustered Index İçermeyen Heap Tablolarda Forwarded Record
  • Forwarded Record’un Performansa Etkisi Nedir?
  • Clustered Index İçeren Tablolarda Neden Forwarded Record Oluşmaz?
  • Hangi Tablolarımda Forwarded Record Var?
  • Tablolarımda Bulunan Forwarded Record’lardan Dolayı Performans Sıkıntısı Yaşıyor muyum?
  • Forwarded Record Nasıl Düzeltilir?
  • Sonuç

[more]

Forwarded Record Nedir?

Forwarded Record heap yani Clustered Index’e sahip olmayan tablolarda karşımıza çıkan bir problemdir. Problemdir diyorum çünkü bir tabloda Forwarded Record olması demek bu tablodaki NonClustered Index’ler üzerinden çekilen sorgularda fazladan IO yapılması bu da performans sıkıntısı anlamına gelmektedir.

Heap tabloda bulunan bir kolon update edildiğinde, kaydın hali hazırda bulunduğu page eğer bu update’i karşılayamayacak kadar doluysa, ilgili kayıt yeni bir page’e taşınır ve eskiden olduğu page’e bir pointer konulur.

Örneğin varchar(8000) büyüklüğünde kolona sahip bir row’unuz var şu anda bu kolonun içinde ‘turgay’ yazmakta. Bu row’un Page1 adlı page’in içinde olduğunu düşünelim. Biz bu kolonu 8000 byte’lık bir veri ile update ettiğimizde eğer Page1’de bu 8000 byte’lık veriyi allocate edecek kadar yer yok ise bu kayıt yeni bir page’e yazılacak ve update’den önce Page1’de bulunduğu yere de yeni page’i işaret edecek bir pointer konulacaktır.

Bu durumda yeni page’e taşınan kayıda Forwarded Record, eski bulunduğu yere forwarding-stub denilmektedir ve tekrar etmek gerekirse forwarding-stub orjinal kayda erişmeye yarayan pointer bilgisini tutmaktadır.

Forwarded Record Nasıl Oluşur?

Şimdi gelin bir Forwarded Record oluşturma örneği yapalım.

Bunun için ilk olarak örnek bir tablo create edip içine 2 adet kayıt basıyorum.

if object_id('ForwardedRecord', 'U') is not null
	drop table ForwardedRecord
GO

create table ForwardedRecord(ID int, IntCol1 int, vcCol1 varchar(8000), vcCol2 varchar(8000))
GO

insert ForwardedRecord select 1,1,'row1col1','row1col2'
insert ForwardedRecord select 2,2,replicate('row2col1',100),replicate('row2col2',100)

 

Şu anda elimde 1 adet data page var. Bu page’in içeriğine bakalım.

Bunun için ilk olarak tablomda hangi page’lerin olduğuna bakmam gerekiyor.

DBCC IND('AdventureWorks2008','ForwardedRecord',0)

 

1

Data page’in ID’si 21216. Şimdi bu page’in içeriğine bakalım.

DBCC TRACEON (3604);
GO
DBCC page('AdventureWorks2008',1,21216,3)

 

Insert ettiğim 2 kayıtta bu page’in içerisinde.

Şimdi Forwarded Record oluşturacağım. Bunun için aşağıdaki update komutu ile daha önce 3 byte veri içeren kolonu 8000 byte’lık veri ile update ederek 21216 nolu page’in içerisine sığmamasını ve yeni bir page’e atılmasını sağlıyorum.

update ForwardedRecord
set vcCol1=REPLICATE('row1col1',1250)
where ID=1

 

Tekrar tablonun page’lerine bakıyorum.

DBCC IND('AdventureWorks2008','ForwardedRecord',0)

 

2

Gördüğünüz gibi 21218 nolu yeni bir page oluştu. Beklediğim şey ID’si 1 olan kaydın 21218 nolu page’in atılması ve 21216 nolu yani kaydın eski page’inde ilgili yere forwarding bilgilerinin yazılması. Bakalım öyle mi olmuş?

21218 nolu page’in içeriğine bakıyorum.

DBCC page('AdventureWorks2008',1,21218,3)

 

3

Beklediğimiz gibi update ettiğimiz kayıt eski olduğu page’de yeteri kadar yer olmadığı için yeni page’in yani 21218 nolu page’in içine taşındı.

Şimdide eski page’in yani 21216 nolu page’in içeriğine bakalım.

DBCC page('AdventureWorks2008',1,21216,3)

 

4

Gördüğünüz gibi ID’si 1 olan kaydın eski olduğu yere bir pointer konulmuş ve datanın orjinal yeri işaret edilmiş durumda.

Dolayısıyla ben bu kaydı okumak istediğimde ilk olarak 21216 nolu page’ geleceğim, kaydın burada olmadığını ama orjinal yerini gösteren bir pointer (file 1 page 21218 slot 0) göreceğim. Bu pointer vasıtasıyla kaydın olduğu page’e gidip okumayı tamamlayacağım.

Forwarded Record Neden Oluşur? – SQL Server’ın Bu Davranışının Nedeni Nedir?

Forwarded Record’un nasıl oluştuğunu bir önceki bölümde inceledik. Peki ama SQL Server neden böyle bir davranış içine girer.

Yani aslında demek istediğim şu. Kaydı yeni page’e taşıdıktan sonra eski yerine bir pointer koymaktansa “datanın asıl yeri artık burasıdır” diye neden denilmez?

Bu soruya NonClustered Index içeren ve içermeyen heap tablolar açısından cevap bulmaya çalışacağız.

NonClustered Index İçeren Heap Tablolarda Forwarded Record

Bildiğiniz gibi NonClustered Index’in Leaf Level’ında eğer tablo heap ise datanın geri kalanına ulaşabilmek için HEAP RID pointer bilgisi bulunur. Bu pointer vasıtası ile NonClustered Index üzerinde arama tamamlandığında kayıdın geri kalanına erişilir. (Index yapısı hakkında detaylı makalem için lütfen tıklayınız.)

İşte bu pointer yapısı sebebiyle eğer kaydın bulunduğu page değişirse ilgili tablo üzerinde bulunan bütün NonClustered Index’lerin Leaf Level’ında bulunan, update edilen kayda ait pointer bilgisinin yeni pointer bilgisi ile değiştirilmesi gerekmektedir. Bu da update işleminin daha uzun sürmesine sebep olacaktır. SQL Server linkleme işlemini bu şekilde yapmaktansa NonClustered Index’in işaret ettiği yeri değiştirmez, ama bu işaret edilen yere de bir pointer konularak kaydın gerçek yeri işaret edilir.

Şimdi yukarıda bahsettiğimiz işlemi deneyelim ve HEAP RID pointer bilgilerinin update olup olmadığını gözlemleyelim.

Bir önceki örneği NonClustered Index create ederek tekrar yapalım. Bu sefer kullanacağım çalışma tablosunun adı “ForwardedRecordNCIndex”.

if object_id('ForwardedRecordNCIndex', 'U') is not null
	drop table ForwardedRecordNCIndex
GO

create table ForwardedRecordNCIndex(ID int, IntCol1 int, vcCol1 varchar(8000), vcCol2 varchar(8000))
GO

insert ForwardedRecordNCIndex select 1,1,'row1col1','row1col2'
insert ForwardedRecordNCIndex select 2,2,replicate('row2col1',100),replicate('row2col2',100)

create nonClustered Index IX_1 on ForwardedRecordNCIndex (IntCol1)

 

Şimdi NonClusteredIndex’in Leaf Level page’ine bakalım. Bunun için ilk olarak Leaf Level Page’in PageID’sini öğrenmem gerekiyor.

DBCC IND('AdventureWorks2008','ForwardedRecordNCIndex',2)

 

5

NonClustered Leaf Level Page’in ID’si 21513. Şimdi bu page’in içeriğine bakalım.

DBCC page('AdventureWorks2008',1,21513,3)

 

6

Heap RID yani datanın geri kalanına erişmek için gerekli olan pointer bilgileri resimdeki gibi.

Şimdi bir Forwarded Record oluşturacağız ve Heap RID pointer bilgisinin değişip değişmediğine bakacağız.

update ForwardedRecordNCIndex
set vcCol1=REPLICATE('row1col1',1250)
where ID=1

 

Tekrar NonClustered Index Leaf Level Page’in içeriğine bakıyoruz.

7

Gördüğünüz gibi kaydın yeri değişmesine rağmen Heap RID pointer bilgisi değişmedi. İşte burada Heap RID hala eski yeri göstersede, eski page’e, kaydın gerçek yerini gösteren pointer bilgisi eklenmekte, bu sayede kayda erişilebilmektedir.

NonClustered Index İçermeyen Heap Tablolarda Forwarded Record

NonClustered Index içeren tablolarda neden Forwarded Record oluşturulmasının mantıklı olduğunu bir önceki bölümde konuşmuştuk. Kısaca üstünden geçmek gerekirse; eğer Forwarded Record oluşmaz ise NonClustered Index’in Leaf level page’inde bulunan Heap RID pointer’ının değişmesi gerekmekte bu da update işleminin daha uzun sürmesi anlamına gelmektedir.

Peki üzerinde NonClustered Index olmayan heap tablolarda Forwarded Record oluşmasının mantığı nedir?

Index olmayan yani Heap tablolara yapılan select işleminde table scan yapılmaktadır. Forwarded Record olsa da olmasa da tablonun tamamı okunacağı için Forwarded Record olup olmaması herhangi bir fark oluşturmaktadır. Bu yüzden NonClustered Index içeren tablolardaki davranışın aynısı burada da sürdürülmektedir.

Forwarded Record’un Performansa Etkisi Nedir?

Update performansının daha iyi olması için Forwarded Record davranışı sergilenir dedik. Peki bir tabloda Forwarded Record bulunmasının performansa ne gibi bir eksi etkisi vardır.

ForwardedRecordNCIndex çalışma tablosu kullanarak yaptığımız örnekte ID’si 1 olan kayıt 21515 nolu page’in içinde bulunmakta. Ama bu page’e erişmek için ilk olarak bu page’i işaret eden pointer bilgisine sahip olan page’in okunması gerekmektedir. Yani Forwarded Record’a sahip bir tabloda okuma yaparken gereksiz yere page okuma yani fazladan IO yapmak durumunda kalınmakta, bu da performans sıkıntısı oluşturmaktadır. Bu durumun örneğini daha sonraki bölümlerde yapacağım.

Clustered Index İçeren Tablolarda Neden Forwarded Record Oluşmaz?

Clustered Index içeren tablolar mantıksal olarak Clustered Index Key’lere göre dizilidir. Page’e sığmayan bir update işlemi gerçekleştiğinde kayıtlar yer değiştirmeli ve mantıksal olarak tekrar dizilmelidir. İşte bu yüzden Clustered Index’lerde Forwarded Record oluşmaz.

Ayrıca Clustered Index içeren tablolarda bulunan NonClustered Index’lerin Leaf Level’ında Heap RID yerine Clustered Index Key’leri bulunur. (Detaylı bilgi için bakınız.) Bu sebepten dolayı Clustered Index içeren bir tabloda page’e sığmayacak bir update işlemi yapıldığında ve update edilen kaydın ya da page’de bulunan diğer kayıtların yeri değiştiği zaman NonClustered Index’te herhangi bir değişiklik yapılmasına gerek yoktur. Dolayısıyla bu tarz bir update işleminde herhangi bir performans sıkıntısı oluşmaz.

Hangi Tablolarımda Forwarded Record Var?

Forwarded Record içeren tablolarda yapılan select işlemleri gereksiz yere fazladan IO yapılmasına neden olacağından dolayı hangi tablolarda Forwarded Record olduğu belirlenmeli ve gerekiyorsa düzeltilmelidir.

Bir tabloda Forwarded Record olup olmadığına sys.dm_db_index_physical_stats DMF’si ile bakılabilir. Örneğin ForwardedRecordNCIndex tablosu için bu DMF’i sorgularsak;

select object_name(object_id) as ObjectName
        ,index_id
        ,page_count
        ,avg_page_space_used_in_percent
        ,record_count
        ,forwarded_record_count
from sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('ForwardedRecordNCIndex'),null,null,'DETAILED')
where OBJECT_NAME(object_id)='ForwardedRecordNCIndex'

 

8

Heap için 1 tane Forwarded Record olduğunu görüyoruz.

Bir DB’de bulunan bütün tabloları aşağıdaki script ile sorgulayıp forwarded record count’ları öğrenebiliriz. (Kaynak: http://sqlserverpedia.com/blog/sql-server-2005/find-tables-with-forwarded-records)

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO

IF OBJECT_ID('tempdb..#HeapList') IS NOT NULL
    DROP TABLE #HeapList

CREATE TABLE #HeapList
    (
    object_name sysname
    ,page_count int
    ,avg_page_space_used_in_percent float
    ,record_count int
    ,forwarded_record_count int
    )

DECLARE HEAP_CURS CURSOR FOR
    SELECT object_id
    FROM sys.indexes i
    WHERE index_id = 0

DECLARE @IndexID int

OPEN HEAP_CURS
FETCH NEXT FROM HEAP_CURS INTO @IndexID

WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO #HeapList
    SELECT object_name(object_id) as ObjectName
        ,page_count
        ,avg_page_space_used_in_percent
        ,record_count
        ,forwarded_record_count
    FROM sys.dm_db_index_physical_stats (db_id(), @IndexID,  0, null,  'DETAILED'); 

    FETCH NEXT FROM HEAP_CURS INTO @IndexID
END

CLOSE HEAP_CURS
DEALLOCATE HEAP_CURS

SELECT *
FROM #HeapList
WHERE forwarded_record_count > 0
ORDER BY 1

 

Sonuç şuna benzer olacaktır.

9

Tablolarımda Bulunan Forwarded Record’lardan Dolayı Performans Sıkıntısı Yaşıyor muyum?

Bir önceki bölümde hangi tablolarda Forwarded Record olduğunu nasıl sorgulayacağımızı gördük. Peki ama bu Forwarded Record’lar şu an bir sıkıntı çıkarıyor mu? Yani çektiğim sorguların kaçı bu Forwarded Record’lardan etkileniyor?

Bu kontrolü SQLServer:Access Methods altında bulunan Forwarded Records/Sec performance counter’ı ile yapabiliriz. Bu performance counter saniyede gerçekleşen Forwarded işlemini yani, forward edilmiş bir kaydın okunma işlemini göstermektedir.

SQL Rap’te bu performance counter için verilen threshold değeri her 100 Batch Requests/Sec için 10 Forwarded Records/Sec. Dolayısıyla sunucu üzerinde Batch Requests/Sec değeri ile Forwarded Records/Sec counter’larını toplayıp herhangi bir Forwarded Record problemi olup olmadığını anlayabiliriz.

Forwarded Record Nasıl Düzeltilir?

Diyelim ki performance counter’ları izledik ve Forwarded Records/Sec oranının bizim beklediğimiz threshold değerinden fazla olduğunu gördük. Bu durumda Forwarded Record’ları düzeltmek istiyorum. Peki ama nasıl?

Forwarded Record’ları düzeltmenin en kısa yolu Forwarded Record içeren Heap tablo üzerinde Clustered Index oluşturup daha sonrada drop etmektir. Bu şekilde page’ler tekrar düzenlenecek ve Forwarded Record’lar ortadan kalkacaktır.

Şimdi bu işlemin etkisini görmek için ForwardedRecordNCIndex tablosu üzerinde bir örnek yapalım.

Şu anda ForwardedRecordNCIndex tablosunda 1 nolu kayıt Forwarded durumda. Bu kaydı okuduğumda ne kadar IO yapıldığına bakalım.

SET STATISTICS IO ON
select * from ForwardedRecordNCIndex where IntCol1=1

 

10

3 logical read yapıldı. Şimdi bu tablo üzerinde bir Clustered Index create ederek Forwarded Record’ları düzelteceğim. İşlem bittikten sonra Clustered Index’i drop edeceğim.

create clustered index CI_1 on ForwardedRecordNCIndex (ID) 
go
drop index CI_1 on ForwardedRecordNCIndex
go

 

Bu işlemden sonra Forwarded Record’ların düzelmiş olması gerekiyor. sys.dm_db_index_physical_stats DMF’sini sorgulayarak tabloda Forwarded Record olup olmadığına bakalım.

select object_name(object_id) as ObjectName
        ,index_id
        ,page_count
        ,avg_page_space_used_in_percent
        ,record_count
        ,forwarded_record_count
from sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('ForwardedRecordNCIndex'),null,null,'DETAILED')
where OBJECT_NAME(object_id)='ForwardedRecordNCIndex'

 

11

Gördüğünüz gibi daha önce 1 olan forwarded_record_count şu an 0. Yani Forwarded Record’lar düzeltildi.

Şimdi select sorgusunu tekrar çekelim ve Forwarded Record’lar düzeltildikten sonraki IO değerlerine bakalım.

12

Gördüğünüz gibi daha önce 3 logical read yapan sorgu Forwarded Record düzeltildikten sonra 2 logical read yaparak sonucu getirdi. Bunun sebebi daha önce Forwarded Record yüzünden fazladan okunan page’in artık okunmuyor olması.

Sonuç

Heap tablolarda oluşan Forwarded Record problemi bu tablolar üzerinde yapılan select sorgularında fazladan IO yapılmasına dolayısıyla performans sıkıntısına sebep olmaktadır. Bu yüzden Forwarded Record içeren tablolar periyodik olarak analiz edilmeli, eğer bir sıkıntı oluştuğu belirlenirse bu kayıtlar Clustered Index Create-Drop işlemi ile düzenlenmelidir.

 

İyi Çalışmalar

Turgay Sahtiyan

Not : Blog haricinde, faydali gördügüm yazilari ve linkleri twitter adresimden paylasiyorum. Beni twitter'da takip etmek için : twitter.com/turgaysahtiyan


Her Gün 1 DMV yazı dizimizde 24. Güne geldik. Bugüne kadar anlattığım DMV’lerin bir kısmı ile yaptığımız işlemleri windows server ile de yapabilmekteydik.Örneğin cluster node’larını yada cluster drive’larını DMV’ler ile nasıl sorgulayabileceğimize baktık ama bu bilgilere zaten windows server üzerinden de erişebiliyorduk.

Bu sorgulamaları DMV’ler ile yapmamızdaki ana amaç olabildiğince SQL Server üzerinden bu kontrolleri yaparak bir bütünlük sağlamak. Ayrıca bazen Windows Admin arkadaşlarla uğraşmaktansa kendi işimizi kendimiz yapabiliyor olmak daha kolay geliyor. :)

Bugün işleyeceğimiz DMV’de bu tarz bir DMV.Hepinizin bildiği gibi PerfMon yada diğer bazı tool’lar ile Performance Counter bilgileri toplanabiliyor. Biz ise sys.dm_os_performance_counters DMV’si ile SQL Server ile alakalı performance counter’ları TSQL ile nasıl sorgulayabileceğimizi görüyor olacağız.

[more]

Yukarıda da belirttiğim gibi sys.dm_os_performance_counters DMV’si ile SQL Server ile alakalı Performance Counter’ları sorgulayabiliyoruz.

select * from sys.dm_os_performance_counters

 

Sorgu sonucunda gelen kolonları incelersek;

  • object_name : Counter’ın ait olduğu ana başlığı verir.
  • counter_name : Performance Counter’ın adı.
  • instance_name : Database adı gibi alt kırılımlar için kullanılır.
  • cntr_value : Performance Counter’ın değeri. 3 değişik kullanım şekli vardır. Aşağıda bu konuyu detaylandıracağız.

 

Performance Counter Tipleri

Performance Counter değerlerinin 3 değişik tipi vardır. Bu tiplere göre sorgu sonucu gelen cntr_value kolonu değişik şekillerde hesaplanır.

  • /Sec Tipi Performance Counter’lar : “Number of Deadlocks/sec” gibi “saniyede gerçekleşen” performance counter’larda cntr_value değeri SQL Server restart’ından itibaren kümülatif olarak toplanan veridir. Yani bu örnek için cntr_value kısmında SQL Server Restart’ından itibaren gerçekleşen toplam deadlock sayısı yer alır.
  • Anlık Değer : “Page life expectancy” gibi counter’larda cntr_value değeri anlık değeri ifade eder. Sorgunun çekildiği anda cntr_value de ki değer o andaki Pafe Life Expectancy değeridir.
  • Yüzdesel Değerler : “Buffer cache hit ratio” gibi yüzdesel olarak ifade edilen Counter’ların hesabı biraz daha farklıdır. Bu counter’larda counter’ın değerinin counter’ın base haline oranına bakılır. Kafanız karıştı biliyorum aşağıdaki örnekler konuyu daha net hale getirecektir.

Şimdi her counter tipi için birer örnek yapalım.

 

sys.dm_os_performance_counters ile “Number of Deadlocks/sec” Kontrolü

Bu counter /sec tipinde bir counter olduğu için cntr_value değeri SQL Server restart’ından itibaren toplanan kümülatif veridir. Dolayısıyla sorgumuzda SQL Server Start tarihinide kullanarak gerçek /sec değerini vereceğiz.

select ut.UpTimeSec as UpTime_Sec,
	   ut.UpTimeSec/3600/24 as UpTime_Day,
	   pc.object_name,
	   pc.counter_name, 
	   pc.cntr_value as CounterValue_Total,	   
	   pc.cntr_value*0.1/ut.UpTimeSec as "CounterValue/Sec",
	   pc.cntr_value*3600.*24/ut.UpTimeSec as "CounterValue/Day"
from sys.dm_os_performance_counters pc
cross join (select DATEDIFF(SECOND,sqlserver_start_time, CURRENT_TIMESTAMP) as UpTimeSec 
				from sys.dm_os_sys_info) ut
where counter_name = 'Number of Deadlocks/sec' and instance_name='_Total'

 

1


Sorgumuzda gördüğümüz kadarıyla SQL Server 2678660 saniyedir UP mış. Bu değer 31 güne tekabül ediyormuş. Bu süre zarfında toplam 10 adet Deadlock gerçekleşmiş ve bu değerlerden yola çıkarak saniyedeki deadlock adetinin 0.00000037, 1 günde ki deadlock adetinin ise 0.32 olduğunu görüyoruz. Yani 3 günde 1 deadlock oluşmaktaymış.

 

sys.dm_os_performance_counters ile “Page life expectancy” Kontrolü

“Page life expectancy” counter’ı anlık değer veren counter tiplerinden birisidir. Memory’de bulunan page’lerin ne kadar süredir memory’de bulunduğunu saniye cinsinden gösterir. Yüksek olması arzu edilen bir durumdur.

select object_name,
	   counter_name,
	   cntr_value
from sys.dm_os_performance_counters
where counter_name = 'Page life expectancy' and instance_name = ''

 

2


Sorgu sonucunda Page life expectancy değerimizin 3611 saniye olduğunu görüyoruz.

 

sys.dm_os_performance_counters ile “Buffer Cache Hit Ratio” Kontrolü

“Buffer Cache Hit Ratio” counter’ı yüzdesel değer veren counter tiplerinden birisidir. Sorgu sonucu istenen page’lerin yüzde kaçının diskten değil memory’den getirildiğini belirtir. %97 üstünde olması arzu edilen bir durumdur.

3 Performance Counter tipinden hesaplaması en farklı olan counter tipi budur. Hesaplamada counter’ın değeri “base” değere bölünür ve 100 ile çarpılır. Yani bizim örneğimizde Buffer Cache Hit Ratio değerini Buffer Cache Hit Ratio Base değerine bölüp 100 ile çarparak sonucu bulacağız.

select pc1.cntr_value*100.0/pc2.cntr_value as 'Buffer Cache Hit Ratio'
from sys.dm_os_performance_counters pc1
join (select object_name,cntr_value
		from sys.dm_os_performance_counters
		where counter_name = 'Buffer cache hit ratio base'
	 ) pc2 on pc2.object_name=pc1.object_name
where pc1.counter_name = 'Buffer cache hit ratio'

 

3


Sorgu sonucunda da gördüğümüz gibi Buffer cache hit ratio değerimiz %100. Yani sorgu sonucu istenen page’lerin tamamı memory’den karşılanmakta.

 

Bugünkü DMV makalemizde sys.dm_os_performance_counters DMV’si ile performance counter’ları nasıl sorgulayacağımızı gördük ve 3 değişik counter tipinin her birinden birer örnek gerçekleştirdik. Sizde kendi ortamlarınızda periyodik olarak performance counter kontrollerini bu DMV ile gerçekleştirebilirsiniz.

 

İyi çalışmalar

Turgay Sahtiyan

Not : Blog haricinde, faydali gördügüm yazilari ve linkleri twitter adresimden paylasiyorum. Beni twitter'da takip etmek için : twitter.com/turgaysahtiyan