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

Query plan oluşturmak CPU ve IO açısından pahalı bir işlemdir. Bu yüzden oluşturulmuş olan Query Plan’lar Plan Cache’de saklanır ve aynı sorgu tekrar geldiğinde kullanılır. Objelerin (tablo vs.) two-part-name şeklinde kullanılmamaları bazı durumlarda Plan’ın tekrar kullanılmamasına ve yeni plan oluşturulmasına sebep olur. Bu da hem memory’nin optimum kullanılmamasına hem de performans sıkıntısına sebebiyet verir.

[more]

İlk olarak two-part-name ve one-part-name ifadelerinin ne olduğunu anlatmaya çalışayım. Aşağıdaki örnekte de gördüğünüz üzere schema adı kullanılarak obje çağırılmasına two-part-name kullanımı, schema adı kullanılmadan çağırılmasına ise one-part-name kullanımı denilmektedir.

--one-part-name kullanimi
select * from tbl1
--one-part-name kullanimi
select * from dbo.tbl1

 

One-Part-Name kullanılarak çağırılan objelerde bazı durumlarda Plan Cache’de bulunan plan kullanılmayıp yeni plan oluşturulur. Bunun sebeplerinden biri sorguyu çalıştıran user’ların farklı default schema’lara sahip olmalarıdır. Şimdi gelin bu durumu gerçekleştirmeye çalışalım.

--Bir calisma DB'si olusturuyoruz
create database dbTwoPart
go
use dbTwoPart
go
--Bir calisma tablosu olusturuyoruz
create table tblTwoPart(a int identity(1,1), b varchar(10))
GO
--tabloya 100 kayit insert ediyoruz
insert tblTwoPart
  select 'b'
GO 100  
--2 adet user olusturuyoruz
create user user1 without login
create user user2 without login
GO
--Bu user'lara yukaridaki tablo icin select hakki veriyoruz
grant select on tblTwoPart to user1,user2
GO
--Ilk user'in default schema'sini dbo olarak birakirken
--ikinci user'in default schema'sini farkli bir schema yapiyoruz.
create schema User2Schema authorization user2
go
alter user user2 with default_schema=User2Schema
GO

 

Ön tanımlamalar tamam. Şimdi sorguları çalıştırıp örneği gerçekleştirmeye başlayabiliriz.

İlk olarak One-Part-Name kullaniminda neler olduğunu görelim.

--Islemlere baslamadan once Plan Cache'i temizliyoruz
DBCC FREEPROCCACHE
GO
--Tabloya user1 olarak select cekiyoruz.
--Tabloya erisim metodu olarak One-Part-Name kullaniyoruz
execute as user='user1'
go
select * from tblTwoPart
go
revert
go
--Ayni islemleri simdi de user2 ile yapiyoruz
execute as user='user2'
go
select * from tblTwoPart
go
revert
go
--Query Plan'lari kontrol ediyoruz
select st.text, qs.execution_count, qs.*, p.*
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(plan_handle) p
where st.text not like '%st.text%'
order by qs.execution_count desc
GO

 

1

Gördüğünüz gibi 2 sorgu için ayrı ayrı plan oluşturuldu. Bunun sebebi plan_attribute’unda bulunan user_id bilgisi.

select * from sys.dm_exec_plan_attributes(0x06001F001992EB164001AC88000000000000000000000000)  
select * from sys.dm_exec_plan_attributes(0x06001F001992EB164041D284000000000000000000000000)

 

2

User_id kısmında sorguyu çalıştıran user’ın default schema bilgisi yazmakta. Yukarıda tanımladığımız 2 user’ın default schema’ları birbirinden farklı olduğu için aynı plan’ı kullanamadılar.

Peki Two-Part-Name kullansaydık durum nasıl olacaktı?

--Islemlere baslamadan once Plan Cache'i temizliyoruz
DBCC FREEPROCCACHE
GO
--Tabloya user1 olarak select cekiyoruz.
--Tabloya erisim metodu olarak One-Part-Name kullaniyoruz
execute as user='user1'
go
select * from dbo.tblTwoPart
go
revert
go
--Ayni islemleri simdi de user2 ile yapiyoruz
execute as user='user2'
go
select * from dbo.tblTwoPart
go
revert
go
--Query Plan'lari kontrol ediyoruz
select st.text, qs.execution_count, qs.*, p.*
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(plan_handle) p
where st.text not like '%st.text%'
order by qs.execution_count desc
GO

 

3

Two-Part-Name kullandığımız için aynı plan tekrar kullanılabildi. Plan_Attributes’a bakarsak;

4

User_id kısmında -2 yazmakta. Bunun anlamı; Two-Part-Name kullanıldığı için user_id’nin artık bir önemi kalmamış ve bu yüzden user_id’ye bunu belirtmek için -2 yazılmış durumdadır.

Bugün üzerinde durduğumuz örnek, ufak bir kodlama farkının performansa olan etkisine çok güzel bir örnek. Ayrıca Ona-Part-Name kullanımının Two-Part-Name kullanımına göre güvenlik açıkları da vardır. İlerleyen günlerde bu konuya da değinmeye çalışacağım.

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 bir sonraki makalemde kullanacağım bir ifadeden, Safe Query mantığından bahsediyor olacağım. Hangi durumlarda Query’nin Safe olarak düşünüldüğü hangi durumlarda Safe olmadığını görüyor olacağız

[more]

En basit anlatımıyla; bir Query’nin kullanılan parametrelere göre oluşturulabilecek birden fazla “en iyi plan” ı var ise bu Query Safe değildir. Ya da tersinden söyleyecek olursak; bir Query hangi parametreleri kullanırsa kullansın hep aynı plan onun için en iyi plan ise bu Query Safe’dir.

Bir örnek ile ne demek istediğimi anlatmaya çalışayım. Örneğin bir tablonuz var ve bu tabloya Col1 kolonu üzerinden select çekiyorsunuz. Diyelim ki Col1 kolonu için “a” değerini kullandığınızda index seek yapılıyor ama “b” değerini kullandığınızda index scan yapılmasına karar veriliyor. İşte bu şekilde kullanılan parametreye göre en iyi Query Plan değişiyorsa bu Query Safe değildir.

Canlı bir örnek yaparak konuyu daha da pekiştirelim.

Safe Query Örneği

Aşağıdaki gibi bir tablo create edip içerisine veri basıyorum.

create Table SafeQuery(ID int identity(1,1),col1 varchar(5),col2 varchar(10))
GO
declare @i int=65
while @i<=10000 begin
  insert SafeQuery select CAST(@i as varchar(5)),'Turgay'
  set @i=@i+1
end
GO
Create clustered index IX_1 on SafeQuery(ID)
GO
Create nonclustered index IX_2 on SafeQuery(col1)
GO

 

Sorguyu incelediğinizde sizin de farkedebileceğiniz gibi col1 kolonuna hep farklı değerler bastım ve en nihayetinde tablomda her col1 değeri için 1 kayıt oluşturdum.

Dolayısıyla bu tablo üzerinden yapacağım her sorgu aynı Query Plan’ı en iyi plan olarak kullanacaktır. O yüzden bu Query Safe’dir.

1

UnSafe Query Örneği

Aşağıdaki gibi bir tablo create edip içerisine veri basıyorum.

create Table UnSafeQuery(ID int identity(1,1),col1 varchar(1),col2 varchar(10))
GO
insert UnSafeQuery select 'a','Turgay'
GO 1
insert UnSafeQuery select 'b','Turgay'
GO 10
insert UnSafeQuery select 'c','Turgay'
GO 100
insert UnSafeQuery select 'd','Turgay'
GO 1000
Create clustered index IX_1 on UnSafeQuery(ID)
GO
Create nonclustered index IX_2 on UnSafeQuery(col1)
GO

 

Oluşan tabloda her Col1 değerine karşılık farklı sayıda kayıt dönecektir. Dolayısıyla bu tablo üzerinden Col1 kolonu kullanılarak çekilen sorgular farklı plan’ları en iyi plan olarak görebilirler.

Örneğin aşağıdaki sorgulardan ilki 1 kayıt döndürdüğü için Index Seek + LookUp yaparken, ikinci sorgu 1000 kayıt döndürdüğü için Clustered Index Scan yapmaktadır.

2

Bu yüzden yani kullanılan parametreye göre Query Plan’lar değiştiği için bu Query Safe değildir.

 

Bu makalem bir sonraki “Simple-Forced Parameterization” makalemin alt yapılarından birisi olacak. O yüzden daha öncesinde bu makaleyi kaleme almak istedim.

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


SQL Server 2008 ile gelen Policy Management özelliğini gene SQL Server’ın bir diğer özelliği olan Central Management Servers ile birden fazla sunucuda aynı anda çalıştırabilmemiz mümkün. Bu makalemde bu işlemi nasıl yapabileceğimize bakıyor olacağız.

[more]

Central Management Server’ı şu şekilde ayarladığımızı düşünelim. (Daha fazla bilgi için şu makaleyi okuyabilirsiniz.)

1

Amacım “servers” grubu altında bulunan server’ların tamamına aynı policy’i tek seferde uygulamak.

Bunun için “servers” yazısına sağ tıklayıp “Evaluate Policies” yazısını tıklıyorum.

2

“Choose Source” kısmındaki butona basarak gelen ekranda policy’lerin olduğu sunucuya bağlantı kuruyorum.

3

Bir önceki ekrana döndüğümde bağlantı kurduğum sunucudaki bütün policy’ler ekranımda listelenecektir. “servers” grubuna uygulamak istediğim policy’leri seçip “Evaluate” butonuna basıyorum.

4

İşlem tamamlandığım “servers” grubunda bulunan bütün sunucular için policy sonuçları listelenecektir.

 

İ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


SQL Server 2008 ile beraber gelen Policy Base Management özelliğinin ne gibi faydalar sağladığını şu makalede incelemiştik. Bu makalemde ise SQL Server 2008 kurulumu ile beraber default olarak gelen best-practice policy’lerini nasıl kullanabileceğimizi görüyor olacağız.

[more]

SQL Server 2008 kurulumu ile beraber kullanımımıza hazır olarak 50 tane database engine policy’si gelmektedir. Aralarında gerçekten güzel ve faydalı policy’ler bulunan bu policy’lere C:\Program Files\Microsoft SQL Server\100\Tools\Policies\DatabaseEngine\1033 klasöründen erişebilirsiniz.

Bu policy’leri kullanabilmek için ilk yapmamız gereken istediğimiz policy’leri Policy Base Management altına import etmektir.

Policy Management >> Policies kısmında sağ tıklayıp import policy yazısına tıklıyoruz.

1

Gelen ekranda “Files to import” yazısının yanındaki butona basıyoruz.

3

Açılan ekranda C:\Program Files\Microsoft SQL Server\100\Tools\Policies\DatabaseEngine\1033 klasörüne kadar inip bütün policy’leri seçerek “open” butonuna basıyoruz.

2

Bir önceki ekrana dönüyoruz ve bu ekranda OK’e basarak import işlemini sonlandırıyoruz.

Import işlemi tamamlandığında seçtiğimiz policy’ler artık Policy Management altında kullanılabilir hale gelmiş olacaktır.

4

Deneme yapmak için bir policy’i sağ tıklayıp “evaluate” yazısına tıklıyoruz.

5

Policy sonuçları yeni bir ekranda listelenecektir.

6

 

İ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


23 Kasım Çarşamba günü yapacağım webcast’in duyurusunu yapmak istiyorum.

SQL Server Performance Tuning İpuçları başlıklı webcast’im 23.11.2011 tarihinde 21:00-22:00 saatleri arasında gerçekleşecek.

Aşağıdaki url’i kullanarak vereceğim bu webcast’e katılabilirsiniz.

https://www.livemeeting.com/cc/mvp/join?id=6N4JP9&role=attend&pw=2NM%7E%60JK%7Bt

Webcast süresince şu performans ipuçları üzerinde duracağız.

  1. FILLFACTOR – PAD_INDEX
  2. Filtered Index
  3. Indexed View
  4. Filtered Index vs Indexed View
  5. NC Index’lerde Included Kolon Kullanımı
  6. Index Seek : PT Bitti mi?
  7. Where Bloğunda Case Kullanımı
  8. Where Bloğunda Collate Kullanımı
  9. Eksik Index (Missing Index) Analizi
  10. Index Maintenance
  11. İstatistiğin Güncel Olmasının Önemi
  12. Optimize For Ad Hoc Workloads
  13. Instant File Initialization
  14. Veritabanı Dosya Büyümeleri

 

Not:Katılım url’i webcast başlamadan yarım saat önce aktif hale gelecektir.

 

İ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


Dün akşam saatlerinde sürpriz bir şekilde SQL Server’ın yeni versiyonu olan SQL Server 2012’nin RC0’si çıktı. Sürpriz diyorum çünkü yakın zamanda yayınlanacağını bilmemize rağmen bu tarihin dün olacağını kimse bilmiyordu :)

[more]

RC0 ile beraber yeni SQL Server Edition’ı Business Intelligence’de ilk kez görücüye çıkıyor.

Diğer değişiklikler ve setup dosyası için aşağıdaki linki ziyaret edebilirsiniz.

http://www.microsoft.com/download/en/details.aspx?id=28145

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


Where Bloğunda Case Kullanmayın makalemden sonra ikinci “kullanmayın” makalem ile karşınızdayım :) Bu makalemde where bloğunda kullanılan “Collate” anahtar kelimesinin performansa olan etkisini örnekler ile inceleyeceğiz.

[more]

Aslında problem sadece “Collate” anahtar kelimesi değil. Index’in tanımlı olduğu kolondaki veriyi manupüle eden her anahtar kelimede aynı sıkıntı mevcut. Bunun da nedeni, en basitinden, Index’in tanımlı olduğu kolonun manupule edildiğinde sıralamanın(order) değişme ihtimali. Böyle bir durumda yani artık sıralı olmayan bir Index Key’de seek işleminin yani arama işleminin sağlıklı olarak yapılamamasından kaynaklı olarak Scan yapılma zorunluluğudur. Tekrar ayrı bir paragrafta belirterek altını çizmek gerekirse;

Collate Index Scan yapılmasına neden olur

Şimdi “Collate” özelinde bir örnek yapalım. Bu örneğimde aynı sorguyu “Collate” anahtar kelimesi varken ve yokken çalıştırıp Query Plan’larını karşılaştıracağım.

İlk olarak çalışma tablomuzu oluşturalım.

--Calisma DB'si olustuyorum.
--Collation olarak Latin1_General_CI_AS kullanıyorum.
Create Database DBCollateDeneme COLLATE Latin1_General_CI_AS
GO
Use DBCollateDeneme
GO
--Calisma tablosu olusturuyorum
create table tblDeneme1(a varchar(37), b char(400))
GO
--Tabloya 10000 kayıt basiyorum
declare @i int=0
declare @a char(37)
while @i<10000 begin
  select @a=CAST(NEWID() as CHAR(37))
  insert tblDeneme1
    select @a,REPLICATE('b',400)    
  set @i=@i+1
end
--a kolonu uzerine Clustered Index tanimliyorum
Create Clustered Index CX_1 on tblDeneme1(a)

 

Şimdi 2 farklı sorguyu aynı ekranda çekip Query Planlarını karşılaştıralım.

--Sorgu1
select * from tblDeneme1 
where a ='19D7D1DA-867A-4903-9B22-88171CF9D451' 
--Sorgu2
select * from tblDeneme1 
where a ='19D7D1DA-867A-4903-9B22-88171CF9D451' COLLATE TURKISH_CI_AS

 

image

Resimde de gördüğünüz üzere Collate kullanılmazken Index Seek yapılarak getirilen sorgu Collate kullanılınca Index Scan yaparak getirilmiş durumda.

Tekrar etmek gerekirse, Collate Index Scan yapılmasına neden olmaktadır. Bu yüzden gerçekten gerekli olan yerlerde Collate kullanmaya özen göstermek gerekmektedir.

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


SQL Server’da Index’lere erişim açısından 2 yöntem kullanılmaktadır. Bunlar; Index Seek ve Index Scan yöntemleridir. Index Scan, Index’in Leaf Level page’lerinin tamamının ya da belli bir range’inin okunması anlamına gelirken, Index Seek, B-Tree üzerinden arama gerçekleştirerek ilgili kayıt ya da kayıtlara erişilme yöntemidir. Bu yüzden performans çalışması yapan bizler Index Scan’den ziyade Index Seek görmek isteriz.

Peki Index Seek yapıldığı zaman performans çalışmalarımız bitmiş mi oluyor? Daha yapacak başka bir şeyimiz kalmadı mı?

[more]

Çoğu kişi açısından Index Seek performans açısından erişilebilecek son noktadır. Üzerinde çalışılan Query Plan’da Index Seek yapıldığı görülürse ekstra bir çalışma yapma gerekliliği olmadığı düşünülür.

Gerçekten öyle mi?

Bu soruya bir örnek ile cevap vereceğim. Örneğimde kullandığım sorgu Clustered Index Seek yapmakta. Ama ben bunun üzerine gene de performance tuning çalışmalarına devam edeceğim.

İlk olarak bir çalışma tablosu oluşturuyorum.

--Calisma DB'si olusturuyorum
create database db_Deneme
GO

Use db_Deneme
GO

--Calisma tablosu olusturuyorum
--Ozellikle buyuk kolonlar seciyorum
create table tbl_Deneme(ID int, Ad varchar(50), WideCol1 varchar(4000), WideCol2 varchar(4000),
				WideCol3 varchar(4000), WideCol4 varchar(4000))
GO

--Tabloya 1000 kayit basiyorum
declare @i int=1
while @i<1000 begin
  insert tbl_deneme
	select @i,'Turgay '+CAST(@i as varchar(5)),REPLICATE('1',4000),REPLICATE('2',4000),
		REPLICATE('3',4000),REPLICATE('4',4000) 
  set @i=@i+1 
end 
GO 

--ID uzerine clustered index olustuyorum 
Create Clustered Index CI_1 on tbl_Deneme(ID) 
GO

 

Şimdi performance tuning yapacağım sorguya bakalım. Sorgum şu şekilde;

select Ad from tbl_Deneme where ID=5

 

Yukarıdaki sorgunun Query Plan’ına baktığımda Clustered Index Seek yapıldığını görüyorum. Daha öncede belirttiğim gibi çoğu DBA bu aşamada daha fazla yapılacak bir şey olmadığını düşünür ve Performance Tuning işlemlerini sonlandırır. Ama biz böyle yapmayacağız.

1

Sorguyu incelediğimde sadece Ad kolonunu result set olarak döndürdüğümü görüyorum. Ama Clustered Index Seek yaptığım için Leaf Level’a erişip page’i okuduğum anda 4000 byte’dan oluşan diğer 4 kolonu da boşu boşuna okuyorum.

Ayrıca bu şekilde büyük kolonlara sahip olduğum için Clustered Index’in NonLeaf Page sayısı da artmış durumda. Bu durum da ekstradan IO yapılmasına sebebiyet veriyor.

Tekrar başa dönersek, sadece Ad kolonunu okuduğum ve ID üzerinden arama gerçekleştirdiğim için bu 2 kolonu cover eden bir Index benim işimi görecektir. Bu amaçla şu şekilde bir Included NonClustered Index oluşturuyorum.

Create NonClustered Index IX_1 on tbl_Deneme(ID) 
	INCLUDE (AD)

 

Şimdi her 2 durumu yaptıkları IO açısından birbiri ile karşılaştıracağım. İlk sorgumda Clustered Index’e force edip, ikinci sorgu da Included NonClustered Index’in kullanılması için herhangi bir force hint’i kullanmayacağım.

SET STATISTICS IO ON
--Clustered Index Seek
select Ad 
from tbl_Deneme WITH (Index=1)
where ID=5
--Included NonClustered Index Seek
select Ad 
from tbl_Deneme 
where ID=5

 

IO karşılaştırmasına bakarsak;

2

Clustered Index Seek yapılınca 6 logical read yapılırken yeni tanımladığım Index kullanılınca 2 logical read yapılırak sorgu sonucu getirilmektedir.

Performance Tuning çalışmaları sırasında Index Seek adımı son geliştirme adımı olarak düşünülmemelidir. Bazı durumlarda ek Index’ler kullanılarak ekstra performans sağlanabilir.

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


Keskin bir ifade oldu farkındayım :) O yüzden cümlenin başına “çok zorda kalmadıkça diye” ekleyelim. Kullanmayın deme sebebim ise where bloğunda case kullanımı, istatistikler üzerinden estimated rows hesabı yapılırken yanlış hesaplamalar yapılmasına dolayısıyla da estimate edilecek kayıt sayısının doğru tahminlenememesi anlamına gelmektedir. Bu durumda da Query Plan sağlıklı bir şekilde oluşturulamayacak ve gereksiz IO yapılmasından dolayı performans sıkıntısı ortaya çıkacaktır. Bugünkü yazımda bahsettiğim bu konuyu örnekler ile görüyor olacağız.

[more]

Örneklerimde AdventureWork.Person.Address tablosunu kullanıp City kolonu üzerinden sorgular çekeceğim. Bu yüzden ilk olarak City kolonu üzerine bir NonClustered Index oluşturuyorum.

IF  not exists (select * from sys.indexes where object_id = OBJECT_ID('Person.Address') AND name = 'IX_1')
  create nonclustered index IX_City on Person.Address
	(City)

 

Person.Address tablosuna çekeceğim sorgularda City bilgisi olarak Bothell. Normal şartlarda Bothell için çekilen sorguların Query Planı şu şekilde olmakta.

1

Şimdi where bloğunda case anahtar kelimesi içeren şöyle bir sorgu yazıyorum.

declare @val1 varchar(10)=NULL
declare @val2 varchar(10)='Bothell'
select * from Person.Address 
	where City = (case when @val1 is null then @val2
					else @val1 end)

 

Sorguda da gördüğünüz gibi eğer @val1 değişkeni null değil ise city bilgisi olarak bu değişken, eğer null ise city bilgisi olarak @val2 değişkeni kullanılacak. Bu durumda yukarıdaki sorguyu çalıştırdığımda “Bothell” için çalıştırmış olacağım.

2

Bothell için normal şartlarda Index Seek yaptığını daha önce görmüştük. Oysaki bu sorguda case kullandığım için artık Clustered Index Scan yapılmakta.

Bunun nedeni ise Estimated Rows kısmında gördüğümüz istatistikler üzerinden hesaplanan “tahmini dönecek kayıt sayısı” bilgisinin normalden çok olmasıdır. Aşağıdaki resimde 2 sorgu için tahminlenen kayıt sayılarını görebilirsiniz.

3

Bu yüzden ilk sorgu için Index Seek yapılmasına karar verilirken, ikinci sorguda dönecek kayıt sayısı olması gerekenden fazla yani yanlış hesaplandığı için Clustered Index Scan yapılmasına karar veriliyor.

İşte bu sebepten dolayı sorguların where bloğunda case kullanımından olabildiğince kaçınmakta fayda olduğunu söyleyebilirim.

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


SQL Server 2012 ile beraber yeni bir edition geliyor. Enterprise ile Standart edition’lar arasında olacak bu yeni edition’ın ismi Business Intelligence.

[more]

2012’nin ilk yarısında release olması beklenen SQL Server’ın yeni sürümü SQL Server 2012 yeni bir edition ile beraber geliyor. İçerdiği özellikler açısından standart edition ile enterprise edition arası olacak olan bu yeni edition’da en belirgin olarak Always On özelliğinin parçalı olacağından bahsedebilirim.

Detay bilgiye ve edition’ların karşılaştırmasına aşağıdaki url’den erişebilirsiniz.

http://www.microsoft.com/sqlserver/en/us/future-editions/sql2012-editions.aspx

 

İ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


Daha önceki makalelerimde (1|2) sys.dm_exec_query_stats DMV’si ile IO ve CPU açısından pahalı sorguların nasıl kontrol edilebileceğini görmüştük. Yapılan insert-update-delete işlemleri sys.dm_exec_query_stats DMV’sinde toplanmakta ve bu DMV kullanılarak IO ve CPU açısından maliyetli sorgular bulunabilmektedir. Fakat bu DMV ile DML hareketler toplanamaz. Örneğin bir SP’nin içerisinde bazı DML işlemler ve örneğin Index oluşturmak gibi bir DDL işlem var ise sys.dm_exec_query_stats DMV’si sadece DML işlemleri toplayacaktır. Oysaki sys.dm_exec_procedure_stats DMV’si ise prosedürün tamamımının çalışma bilgilerini tuttuğu için DDL işlemi de bu hesaplamanın içine katılır. Bu makalemde bu 2 DMV arasındaki farka bir örnek ile bakıyor olacağız.

[more]

İçeriğinde hem DML hem de DDL işlemleri bulunan şu şekilde bir SP oluşturalım

Use AdventureWorks
GO
create proc SPTest
as
	select * into testPerson from Person.Person
	
	select LastName, FirstName
	from testPerson
	where LastName in ('turgay','sahtiyan')		

	create index IX_1 on testPerson (LastName, FirstName)
GO

 

SP’yi execute edelim.

exec SPTest

 

Şimdi Toplam IO ve CPU değerlerine hem sys.dm_exec_query_stats hem de sys.dm_exec_procedure_stats DMV’leri ile ayrı ayrı bakalım.

select plan_handle, text
	,SUM(total_logical_writes+total_logical_reads+total_physical_reads) as TotalIO
	,SUM(total_worker_time) as TotalCPU
from sys.dm_exec_query_stats
cross apply sys.dm_exec_sql_text(sql_handle)
where text like '%SPTest%' and text not like 'select plan_handle%'
group by plan_handle, text
order by TotalIO

select plan_handle, text
	,total_logical_writes+total_logical_reads+total_physical_reads as TotalIO
	,total_worker_time as TotalCPU
from sys.dm_exec_procedure_stats
cross apply sys.dm_exec_sql_text(sql_handle)
where text like '%SPTest%'

 

aa1(1)

Gördüğünüz gibi 2 DMV sonucunda alınan TotalIO ve TotalCPU değerlerinde farklılık var. Bunun nedeni, giriş paragrafında da söylediğim gibi, sys.dm_exec_ procedure_stats DMV’sinde DDL hareketlerin olması ve sys.dm_exec_query_stats DMV’sinde DDL hareketlerin olmamasıdır.

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


Index oluşturulurken kullanılan FILLFACTOR ve PAD_INDEX seçenekleri ile daha sonra yapılacak DML işlemleri için Index sayfalarında (Page) ne kadar boş yer bırakılacağı belirlenebilir. Özellikle çok hızlı bir şekilde Fragmante olan Index’ler için bu değerler üzerinde oynama yapılmasında fayda olabilir. Fakat bu değerlerde yapılacak değişikliklerin sayfa sayısını arttıracağını ve dolayısıyla yapılacak IO miktarını arttıracağını unutmamak gerekir.

[more]

Index sayfaları B-Tree yapısında organize olurlar. Bu yapıda en alt bulunan sayfalar Leaf Level Page’ler en alt seviyenin üstünde bulunan sayfalara ise NonLeaf Level Page’ler olarak bilinir. (Daha detaylı bilgi için şu makaleyi okuyabilirsiniz.)

FILLFACTOR ve PAD_INDEX ile Index oluşturulurken ya da ReBuild edilirken NonLeaf ve Leaf Level sayfalarda ne kadarlık boş yer bırakılacağı ayarlanabilir.

FILLFACTOR : Leaf Level Page’lerde yüzde olarak ne kadarlık boş yer bırakılacağını belirlemek için kullanılır. 1 ile 100 arasında integer değer atanabilir. Default değeri 0’dır. Yani sayfalar oluşturulurken maksimum kapasitede doldurulmaya çalışılır. 100 atanması durumunda 0 ile aynı işi yapar.

Tekrar belirtmek gerekirse FILLFACTOR değerinde yapılacak değişiklik sayfalarda bulunan kayıt sayısını azaltacağı için toplam sayfa sayısını arttıracaktır. Örneğin FILLFACTOR değeri 0 iken toplam 100 Leaf Level Page varsa düz bir mantıkla FILLFACTOR değeri 50 olduğunda Leaf Level Page sayısı 200’e çıkacaktır. Bu da yapılacak IO miktarını arttıracaktır. Bu konuya ilerleyen bölümlerde örnek vererek daha detaylı değineceğim.

PAD_INDEX : Leaf Level Page’ler için belirlenen FILLFACTOR değerinin NonLeaf Level Page’ler için de uygulanıp uygulanmayacağını belirlemek için kullanılır. PAD_INDEX ON durumda ise FILLFACTOR değeri NonLeaf Level Page’lerde de uygulanır. Dolayısıyla PAD_INDEX’in çalışabilmesi için FILLFACTOR değerinin set edilmesi gerekmektedir.

Index oluştururken FILLFACTOR ve PAD_INDEX parametrelerini TSQL’de şu şekilde kullanabiliriz.

Create Clustered Index CX_1 on tblDeneme2(a)
	with (FILLFACTOR = 50, PAD_INDEX  = ON)

 

Bu script ile oluşturulan Index’in Leaf Level Page’leri %50 oranında doldurulacaktır. Aynı zamanda PAD_INDEX = ON denildiği için %50 oranı NonLeaf Level Page’ler içinde kullanılacaktır.

Aynı işlemi SSMS kullanarak ta yapabiliriz.

1

Şimdi 2 farklı bakış açısıyla FILLFACTOR ve PAD_INDEX değerlerini değiştirmenin bize getirdiği avantaj ve dezavantajlara bakalım.

FILLFACTOR ve PAD_INDEX Index Fragmantasyonunu Etkiler

Giriş paragrafında da söylediğim gibi çok hızlı bir şekilde fragmante olan Index’lerde FILLFACTOR ve PAD_INDEX değerleri değiştirilerek Page’lerde boş yer bırakılabilir. Bu şekilde, daha sonra oluşacak Insert ve Update’ler için yer bırakılmış olur. Bu da Index’te oluşacak fragmantasyonu geciktirir.

Şimdi bu konu üzerine bir örnek yapalım. Örneğimde birbirinin aynı 2 tablo kullanacağım. Bu tablolar üzerine Clustered Index oluşturacağım. Index’leri oluştururken ilk Index’te %0 FILLFACTOR değerini ikincisinde ise %50 FILLFACTOR değerini kullanacağım. Daha sonra tabloya Insert’ler yapıp 2 Index’in fragmantasyon değerlerini karşılaştıracağım.

--Calisma DB'si olusturuyorum
Create Database DBIndexDeneme
GO
--Calisma tablosu olusturuyorum
Use DBIndexDeneme
GO
--Karsilastirmada kullanacagim ayni ozellikle 2 tablo olusturuyorum
create table tblDeneme1(a char(37), b char(400))
GO
Create table tblDeneme2(a char(37), b char(400))
GO
--Tablolara 10000 kayýt insert ediyorum.
declare @i int=0
declare @a char(37)
while @i<10000 begin
  select @a=CAST(NEWID() as CHAR(37))
  insert tblDeneme1
    select @a,REPLICATE('b',400)    
  insert tblDeneme2
    select @a,REPLICATE('b',400)
  set @i=@i+1
end
--Clustered Index'leri olusturuyorum
--Ilk tablo icin FILLFACTOR degerini default yani 0 olarak birakiyorum
Create Clustered Index CX_1 on tblDeneme1(a)
--Ikinci tablo icin FILLFACTOR degerini 50 olarak set ediyorum
Create Clustered Index CX_1 on tblDeneme2(a)
	with (FILLFACTOR = 50, PAD_INDEX  = ON)

 

Çalışma tablolarım hazır. Şimdi bu tablolara 1000’er adet kayıt daha insert edeceğim. Kayıtlar aralara denk gelebileceği için fragmantasyon oluşacaktır. İşleme başlamadan önce şu anki fragmantasyon değerlerine bakıyorum.

select object_name(ps.object_id) as [tableName], 
	ps.index_id,
	i.name as indexName,
	ps.avg_fragmentation_in_percent, 
	ps.fragment_count,
	ps.avg_fragment_size_in_pages, 
	ps.page_count, 
	ps.avg_page_space_used_in_percent
from sys.dm_db_index_physical_stats(DB_ID(),null,null,null,null) ps
join sys.indexes i on i.object_id = ps.object_id and i.index_id = ps.index_id
where object_name(ps.object_id) in ('tblDeneme1','tblDeneme2')

 

2

Şu an için her 2 index’te de fragmantasyon bulunmamakta. Şimdi 1000’er adet insert’i her 2 tabloya yapalım.

declare @i int=0
declare @a char(37)
while @i<1000 begin
  select @a=CAST(NEWID() as CHAR(37))
  insert tblDeneme1
    select @a,REPLICATE('b',400)    
  insert tblDeneme2
    select @a,REPLICATE('b',400)
  set @i=@i+1
end

 

Fragmantasyon değerlerine tekrar bakalım.

select object_name(ps.object_id) as [tableName], 
	ps.index_id,
	i.name as indexName,
	ps.avg_fragmentation_in_percent, 
	ps.fragment_count,
	ps.avg_fragment_size_in_pages, 
	ps.page_count, 
	ps.avg_page_space_used_in_percent
from sys.dm_db_index_physical_stats(DB_ID(),null,null,null,null) ps
join sys.indexes i on i.object_id = ps.object_id and i.index_id = ps.index_id
where object_name(ps.object_id) in ('tblDeneme1','tblDeneme2')

 

3

Gördüğünüz gibi FILLFACTOR değeri %50 olarak ayarlanan Index’te hiç fragmantasyon oluşmamışken diğer Index’te %88 oranında fragmantasyon oluşmuş durumda.

Dolayısıyla tekrar etmek gerekirse; çok hızlı bir şekilde fragmante olan tablolarda FILLFACTOR değeri set edilerek fragmantasyon geciktirilebilir.

Yukarıdaki örnekte fragmante olan Index’i aşağıdaki script vasıtasıyla defragmante edebiliriz.

Alter Index CX_1 on tblDeneme1 Rebuild

 

FILLFACTOR ve PAD_INDEX Özelliklerinin IO’ya Etkisi

Daha öncede bahsettiğim gibi FILLFACTOR değerinin değiştirilmesi sayfada tutulacak kayıt sayısını etkilediği için bu durum da otomatikman toplam sayfa sayısını etkileyecektir. Dolayısıyla sorgular sonucunda okunacak sayfa sayısı da artabilir. Bu da daha fazla IO yapılması anlamına gelmektedir.

Şimdi bu aradaki farkı örneklendirmeye çalışalım. Çalışma tablosu olarak bir önceki başlıkta anlattığım tabloları kullanacağım.

İlk olarak 2 Index arasındaki sayfa sayısı farkına bakalım.

select schema_name(o.schema_id) as SchemaName,
         o.name as TableName,
         ps.index_id,
         i.name as indexName,
         fill_factor,
         is_padded,
         ps.used_page_count as LeafLevelPageCount,
         (ps.in_row_data_page_count*8.0) as LeafLevel_KB,
         ps.used_page_count-in_row_data_page_count as NonLeafLevelPageCount,
         ((ps.used_page_count-ps.in_row_data_page_count)*8.0) as NonLeafLevelSize_KB
from sys.dm_db_partition_stats ps
join sys.objects o on o.object_id = ps.object_id
join sys.indexes i on i.object_id = ps.object_id and i.index_id = ps.index_id
where o.name in ('tblDeneme1','tblDeneme2')
order by o.name

 

4

Gördüğünüz gibi FILLFACTOR değerini %50 olarak set ettiğimiz Index, diğer Index’e oranla hem Leaf hem de NonLeaf Level Page sayısı olarak neredeyse 2 katı page’e sahip.

Şimdi aşağıdaki gibi bir sorguyu 2 tabloda birden çalıştırıp yaptıkları IO miktarına ve sorgu masrafına bakalım.

SET STATISTICS IO ON
Select * from tblDeneme1 
	where a between 'C' and 'G'
Select * from tblDeneme2 
	where a between 'C' and 'G'

 

5

Aynı sorgu tblDeneme1 tablosundan 158 IO yapılarak getirilebiliyorken, tblDeneme2 tablosundan 257 IO yapılarak getirilebildi. Bu da neredeyse 2 katı performans kaybı anlamına gelmekte.

Son olarak Query Planlarına bakalım.

6

2 Query Planda da Index Seek yapılmasına rağmen IO farklılığından dolayı ilk sorgu %38 maliyete sahipken 2.sorgu %62 maliyete sahip.

ÖZET

FILLFACTOR ve PAD_INDEX parametrelerini set etmek çok hızlı fragmante olan tablolar için bir avantaj gibi gözükebilir. Fakat sayfa sayısı artacağı için IO açısından dezavantaj doğurabilir. Sorgu ve tablo bazında inceleme yapılıp karar verilmesinde fayda vardır.

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


Son 2 makalemde Filtered Index ve Indexed View konularına değindim. Bazı senaryolarda kullanım noktaları kesişen bu özelliklerin aralarındaki benzerlikler ve farklar hangi çözümün kullanılması gerektiği noktasında belirleyici olabilir.

[more]

Filtered Index ve Indexed Views arasındaki farkları karşılaştırmalı olarak bir tabloya doldurmayı uygun gördüm. Bu şekilde hızlı bir şekilde 2 özellik arasındaki farklar karşılaştırabilir.

 

Filtered Index

Indexed View

Bir ya da birden fazla kolon için oluşturabilir.

Bir ya da birden fazla kolon için oluşturabilir.

Bir tablo üzerine oluşturulabilir.

Birden fazla tabloyu içerecek şekilde oluşturulabilir.

SQL Server’ın bütün sürümlerinde kullanılabilir.

SQL Server’ın bütün sürümlerinde oluşturulabilir ve sorgularda view kullanılarak index kullanılabilir. Sorguda view kullanılmadan index’in kullanılabilmesi için Enterprise sürümü gerekir.

Unique olmayan Filtered Index oluşturulabilir.

Indexed View’ler Unique olmak zorundadır.

Tüm sistemler için idealdir.

Genelde OLTP sistemlerinde kullanılması tavsiye edilmez. OLAP sistemler için daha uygundur.

Filtre olarak sadece çok basit (IS IS NOT = <> != > >= !> < <= !<)) operatörler kullanılabilir.

Filtre olarak herhangi bir sınırlama yoktur. Kompleks filtreleme yapılabilir.

Online olarak Rebuild yapılabilir

Online olarak Rebuild yapılamaz.

2 özellik arasındaki farkları biliyor olmak senaryoya göre hangi özelliğin kullanılması gerektiği sorusuna cevap bulma aşaması için önemlidir.

 

İ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


Bu makalemde, özellikle DWH sistemlerde bulunan, bol join ya da hesaplama içeren, çok fazla kayıt döndüren sorgularda ciddi performans artışı sağlayan Indexed View’leri inceliyor olacağız.

[more]

Indexed View Nedir?

Klasik View’leri hepimiz biliyoruz. Bu View’lerde dönecek kayıt seti herhangi bir şekilde veritabanında tutulmaz ve View her çalıştığında ilgili tablolardan getirilir. View’den eğer çok fazla kayıt dönüyorsa ve ayrıca bu kayıtlarda join ve hesaplamla işlemleri bolca yapılıyorsa çoğu durumda sonucun gelmesi oldukça fazla zaman alabilir.

Yukarıda ki örneğe benzer View’lerde performans artışı için View üzerine Unique Clustered Index tanımlanabilir. Index tanımlandığında artık View’den dönecek kayıt seti veritabanında saklanıyor olacaktır. Dolayısıyla karışık sorgularda her defasında hesaplama yapılmayacak, kayıtlar veritabanında saklandığı yerden hızlı bir şekilde getirilecektir.

View’lere Index tanımlamanın bir diğer artısı, sorguda ilgili view kullanılmasa bile eğer sorgunun tamamı ya da bir kısmı view tarafından karşılanıyorsa Index’in kullanılıyor olmasıdır. Yani view üzerine tanımlanan Index’in kullanılması için View’in sorguda kullanılması şart değildir.

Indexed view SQL Server’ın tüm versiyonlarında bulunmaktadır. Fakat bir önceki paragrafta bahsettiğim özellik, yani View kullanılmadan Index’in kullanılması özelliği sadece Enterprise versiyonunda bulunmaktadır.

Hangi Sistemler İçin Uygundur? OLTP - OLAP

View üzerine Index tanımlandığında dönecek kayıt seti veritabanında saklandığı için, çok fazla update ya da insert gören tablolardan oluşan View’lere Index tanımlanması pek mantıklı değildir. Çünkü tablolarda oluşacak DML işlemleri view’lerde de yapılmak durumunda kalınacaktır. Bu da DML işlemlerinin performansını etkileyecektir.

OLAP sistemleri Indexed View kullanımı için çok iyi bir alandır. Olap sistemlerinde tablolar belirli bir zamanda (her gece beslenen DWH sistemleri gibi) DML işlemi görür ve View’ler çok fazla sayıda sorgulanır. O yüzden bu tarz sistemlerde Indexed View kullanımı mantıklı olmaktadır.

Ayrıca veritabanları beslemesinden önce Indexed View’ler kapatılabilir ve DML işlemleri tamamlandıktan sonra Indexed View’ler tekrar oluşturabilir. Bu şekilde veri beslemesi aşamasında da performans sıkıntısı ortadan kaldırabilir.

Indexed View Oluşturmanın Ön Şartları

Bir View üzerine Clustered Index tanımlamadan önce bazı ön şartların sağlanmış olması gerekmektedir. Bu ön şartlardan bazıları aşağıdaki gibidir:

  • İlgili View’in içinde başka bir View kullanılamaz.
  • View’in içinde kullanılan tüm tablolar View ile aynı veritabanında ve aynı schema sahibine ait olmak zorundadır.
  • View SchemaBinding opsiyonu ile oluşturulmak zorundadır.
  • View içinde kullanılan UDF (User Defined Functions) ‘larda SchemaBinding opsiyonu ile oluşturulmuş olmalıdır.
  • View içinde kullanılan tablo ve UDF’ler SchemaName+ObjectName şeklinde kullanılmalıdır. Örneğin Person.Address gibi.
  • View’de hesaplama fonksiyonu kullanıldı ise select kısmında COUNT_BIG(*) kullanılması zorunludur.
  • Ayrıca select kısmı için aşağıdaki engeller mevcuttur.
    • Select * şeklinde bir kullanıma izin verilmez. Kolon adları belirtilmek zorundadır.
    • Aynı kolon adı 1 den fazla kullanılamaz.(“Select col1 as a, col1 as b from tbl1” gibi)
    • CTE (Common Table Expression) kullanımına izin verilmez.
    • TOP ve Order By kullanılamaz.
    • Count kullanılamaz. Count yerine Count_Big kullanılmalıdır.

Ön şartların tamamına aşağıdaki BOL dokümanından erişebilirsiniz.

http://msdn.microsoft.com/en-us/library/ms191432.aspx

Indexed View’in Performansa Etkisi

Indexed View’ın performansa olan etkisini analiz etmek aşağıdaki örnek sorguyu kullanacağım.

SELECT TOP 5 ProductID, 
	Sum(UnitPrice*OrderQty) as SumUnitPrice,
	Sum(UnitPrice*OrderQty*UnitPriceDiscount) AS SumDiscountPrice
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY SumDiscountPrice DESC

 

Bu sorgu için bir Indexed View oluşturacağız. Daha sonra Indexed View’den önceki performansı ile karşılaştıracağız.

İlk olarak Indexed view oluşturmadan yukarıdaki sorgunun Query Planını inceleyelim.

1

Where bloğu kullanmadığım için Clustered Index Scan yapıldı. Ayrıca hesaplama fonksiyonu (sum) kullandığım için Hash Match yapıldı ki bu işlemin bana maliyeti %35.

Şimdi bu sorgu için bir View oluşturup bu View içinde bir Unique Clustered Index oluşturuyorum.

IF OBJECT_ID ('Sales.vSales', 'view') IS NOT NULL
	DROP VIEW Sales.vSales ;
GO

CREATE VIEW Sales.vSales
	WITH SCHEMABINDING -- Indexed View tanımlamak için şart
AS	
	SELECT ProductID,
	    Sum(UnitPrice*OrderQty) as SumUnitPrice,
		Sum(UnitPrice*OrderQty*UnitPriceDiscount) AS SumDiscountPrice,
		COUNT_BIG(*) as TotalSales --Group By kullanıldığında mecburi
	FROM Sales.SalesOrderDetail 
	GROUP BY ProductID
GO
--View üzerine Index tanımlıyoruz.
CREATE UNIQUE CLUSTERED INDEX VI_1
	ON Sales.vSales (ProductId)
GO

 

Şimdi 2 sorguyu beraber çalıştıracağım. IO değerlerini görmek için SET STATISTICS IO ON ile IO istatistiğini de aktif hale getiriyorum. Ayrıca ilk sorgumda View’in Index’i değil de Clustered Index kullanılması için Clustered Index’i force ediyorum.

SET STATISTICS IO ON
GO

--Clustered Index kullanılacak.
SELECT TOP 5 ProductID, 
	Sum(UnitPrice*OrderQty) as SumUnitPrice,
	Sum(UnitPrice*OrderQty*UnitPriceDiscount) AS SumDiscountPrice
FROM Sales.SalesOrderDetail WITH(INDEX(0))
GROUP BY ProductID
ORDER BY SumDiscountPrice DESC

--Indexed View kullanılacak.
SELECT TOP 5 ProductID, 
	Sum(UnitPrice*OrderQty) as SumUnitPrice,
	Sum(UnitPrice*OrderQty*UnitPriceDiscount) AS SumDiscountPrice
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY SumDiscountPrice DESC

 

İlk olarak Query Planlara bakalım.

2

İlk dikkatinizi çekmek istediğim nokta yukarıdaki sorgularda View’in adını kullanmıyor olmama rağmen 2.sorguda View üzerine tanımladığım Index otomatik olarak kullanıldı. Giriş bölümünde bahsettiğim durum yani Enterprise versiyonun bir özelliği bu.

Şimdi sorguların masraflarına bakalım. Indexed View tanımlanmadan önceki durum tanımlandıktan sonraki duruma oranla 99 kat daha performansız. Bunun nedeni Indexed View kullanarak hem daha az IO yapmış olmam hem de Group By’dan kaynaklanan Hash Match’ten kurtulmuş olmam.

Peki IO değerlerimiz nasıl?

3

İlk sorgum 1240 IO yaparken 2. Sorgum sadece 4 IO yaparak işlemi tamamlamış oldu.

Indexed View’lerde normal View’lerden farklı olarak dönecek kayıt setleri veritabanında saklanır dedik. Normal bir Clustered Index’in page’lerine bakar gibi Indexed View’in Clustered Index Page’lerine bakabilirim.

--Index'in page'leri
DBCC IND('AdventureWorks','Sales.vSales',1)

 

4

NonLeaf Level Page örneği

DBCC Page('AdventureWorks',1,8207,3)

 

5

Leaf Level Page örneği

DBCC TRACEON(3604)
GO
DBCC Page('AdventureWorks',1,8204,3)

 

6

Sonuç

Indexed View’lerde klasik View’lerden farklı olarak dönecek kayıt setleri veritabanında saklanırlar. Bu özelliklerinden dolayı çok fazla hit alan ve bol join ya da hesaplama içeren View’lerde Index kullanmak performans açısından oldukça avantaj sağlamaktadır. Tabloda yapılacak DML işlemleri ilgili View’i de etkileyeceğinden dolayı OLTP sistemlerden ziyade OLAP sistemlerinde kullanılmaları daha mantıklıdır.

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