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
<<  Ağustos 2017  >>
PaSaÇaPeCuCuPa
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Keywords

SQL Server 2008 Service Pack 2 (SP2) bugün yayınlandı. SP1 den sonra çıkartılan 8 tane cumulative update’in fix’lerini içeren bu SP’nin download bilgileri aşağıdaki gibi.

SQL Server 2008 SP2

SQL Server 2008 SP2 Express

SQL Server 2008 SP2 Feature Packs

 

İ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ımda bugün son günümüz :) Bugünkü yazımda yeni bir DMV anlatmaktansa geride kalan 29 gün boyunca işlediğimiz DMV’leri bir başlık altında toplamayı uygun gördüm. Bu şekilde tek bir post’tan bütün DMV’lere hızlıca erişebileceğiz.

[more]

Bu 29 günlük periyotta 39 tane DMV’yi inceleyip, bu DMV’leri production ortamlarında nasıl ve hangi amaçla kullanabileceğimizi inceledik.

Gün gün işlenen DMV’lerin detayı aşağıdaki gibi.

Gün 1 – Giriş
DMV-DMF konusuna giriş yazısı.

Gün 2 - sys.dm_exec_sql_text - sys.dm_exec_query_plan
sys.dm_exec_sql_text
sys.dm_exec_query_plan

Gün 3 - DMW’ler ile Session(Process) Kontrolleri
sys.dm_exec_connections
sys.dm_exec_sessions
sys.dm_exec_requests

Gün 4 - sys.dm_exec_query_stats ile Query İstatistikleri
sys.dm_exec_query_stats

Gün 5 - sys.dm_db_persisted_sku_features
sys.dm_db_persisted_sku_features

Gün 6 - sys.dm_os_wait_stats
sys.dm_os_wait_stats

Gün 7 - sys.dm_os_buffer_descriptors
sys.dm_os_buffer_descriptors

Gün 8 - sys.dm_io_virtual_file_stats
sys.dm_io_virtual_file_stats

Gün 9 - sys.dm_io_pending_io_requests
sys.dm_io_pending_io_requests

Gün 10 - sys.dm_io_cluster_shared_drives
sys.dm_io_cluster_shared_drives

Gün 11 - DBCC SQLPERF ile DMW İstatistiklerini Resetleme
DBCC SQLPERF('sys.dm_os_latch_stats',CLEAR);
DBCC SQLPERF('sys.dm_os_wait_stats',CLEAR);

Gün 12 - sys.dm_db_index_physical_stats
sys.dm_db_index_physical_stats

Gün 13 - sys.dm_db_index_usage_stats
sys.dm_db_index_usage_stats

Gün 14 - sys.dm_db_index_operational_stats
sys.dm_db_index_operational_stats

Gün 15 – DMV’ler ile Eksik Index Sorgulama
sys.dm_db_missing_index_group_stats
sys.dm_db_missing_index_groups
sys.dm_db_missing_index_details
sys.dm_db_missing_index_columns

Gün 16 – sys.dm_exec_procedure_stats ile Stored Procedure Çalışma İstatistikleri
sys.dm_exec_procedure_stats

Gün 17 - sys.dm_db_partition_stats
sys.dm_db_partition_stats

Gün 18 – dm_os_sys_info ile Database Server Bilgileri
sys.dm_os_sys_info

Gün 19 – sys.dm_os_sys_memory ile Database Server Memory Bilgileri
sys.dm_os_sys_memory

Gün 20 – sys.dm_os_schedulers ile CPU’da Bekleyen İşleri Sorgulama
sys.dm_os_schedulers

Gün 21 – sys.dm_exec_cached_plans
sys.dm_exec_cached_plans

Gün 22 – DMV’ler ile Birbirine Dependent (Bağımlı) Objelerin Sorgulanması
sys.dm_sql_referencing_entities
sys.dm_sql_referenced_entities
sys.sql_expression_dependencies

Gün 23 – DMV’ler ile Transaction Kontrolleri
sys.dm_tran_database_transactions
sys.dm_tran_active_transactions
sys.dm_tran_session_transactions

Gün 24 – sys.dm_os_performance_counters ile Performance Counter’lar
sys.dm_os_performance_counters

Gün 25 – sys.dm_os_cluster_nodes
sys.dm_os_cluster_nodes

Gün 26 – sys.dm_exec_trigger_stats ile Trigger İstatistikleri
sys.dm_exec_trigger_stats

Gün 27 – DMV’ler ile Lock’lanan Kaynakların ve Block’lanan Session’ların Sorgulanması
sys.dm_tran_locks
sys.dm_os_waiting_tasks

Gün 28 – sys.dm_exec_cursors ile Açık Olan Cursor’ları Sorgulama
sys.dm_exec_cursors(0)

Gün 29 – sys.dm_os_latch_stats
sys.dm_os_latch_stats

 

İ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


sys.dm_os_wait_stats DMV yazımda SQL Server’ın network, CPU gibi hangi dış etkenlerde beklediğini nasıl belirleyebileceğimizi görmüştük. Bugünkü yazımda ise sys.dm_os_latch_stats DMV si ile SQL Server boş page bulma gibi SQL Server ilişkili beklemeleri nasıl sorgulayacağımızı görüyor olacağız.

[more]

select * from sys.dm_os_latch_stats

 

Sorgu sonucunda gelen kolon bilgileri aşağıdaki gibidir.

Kolon Adı

Açıklama

latch_class

Latch Class’ın adı

waiting_requests_count

Bu latch class için kaç defa bekleme yaşandığı.

wait_time_ms

Milisaniye cinsinden toplam bekleme zamanı.

max_wait_time_ms

Bu bekleme tipi için gerçekleşen maksimum bekleme zamanı

Sorgu sonucunda gelen lacth type’ların detayına bu makalede girmeyeceğiz. Latch type’lara aşağıdaki BOL dokümanından erişebilirsiniz.

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

sys.dm_os_latch_stats DMV’si de sys.dm_os_wait_stats DMV’si gibi SQL Server restart’ından beri oluşmuş beklemeleri kümülatif olarak tutar. Ve bu DMV’de sys.dm_os_wait_stats DMV’si gibi manuel olarak resetlenebilmektedir.

DBCC SQLPERF ('sys.dm_os_latch_stats', CLEAR);

 

Bu DMV’deki wait_time_ms kolonu bizim için en üzerinde duracağımız kolon olacaktır. Dolayısıyla sorgumuzu order by ile çekerek incelemekte fayda var.

select * from sys.dm_os_latch_stats
order by wait_time_ms desc

 

Bu DMV’ide de delta sorgu çalışması yapılarak SQL Server restart’ından itibaren değilde belirli bir süre için izleme yapılabilir. Bu örneğe de sys.dm_os_wait_stats makalemden bakabilirsiniz.

 

İ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


Hepinizin bildiği gibi bir result set üzerinde satır satır hareket edebilmek için Cursor’lar kullanılmaktadır. Bugünkü yazımda bir dabatase üzerindeki açık olan cursor’ları, bu cursor’ları kimin ne zaman açtığını ve query’lerinin neler olduğunu nasıl sorgulayabileceğimize bakıyor olacağız.

[more]

Cursor sorgulamalarımız için sys.dm_exec_cursors DMF’sini kullanacağız.

select * from sys.dm_exec_cursors(0)

 

Bu DMF Session ID parametresi almaktadır. Parametre olarak belirli bir Session ID verilirse, verilen session için açık olan cursor’lar görüntülenir. Eğer parametre olarak sıfır(0) verilirse sorgunun çalıştığı database üzerindeki bütün açık cursor’lar listelenir.

Sorgu sonucu gelen kolonların üzerinde duracak olursak;

  • session_id : Cursor’ın hangi session üzerinde açıldığını belirtir. Bu kolonu sys.dm_exec_sessions DMV’sine bağlayarak diğer session bilgilerine erişeceğiz.
  • cursor_id : Cursor’ın ID’sini belirtir.
  • name : Cursor’ın adını ifade eder.
  • properties : Cursor’ın hangi özellikler ile açıldığını gösterir.
  • sql_handle : Cursor’ın sql_handle’ıdır. Bu kolonu sys.dm_exec_sql_text DMF’sine bağlayarak query bilgisine erişeceğiz.
  • creation_time : Cursor’ın oluşturulduğu zamanı verir.
  • fetch_status : Cursor’ın en son fetch durumunu verir. Bilgi @@FETCH_STATUS ‘ten alınıp görüntülenir.
  • dormant_duration : En son open veya fetch işleminden beri geçen zamanı milisaniye cinsinden verir.

 

Şimdi örneğimize geçelim. Bunun için bir cursor oluşturalım.

declare @DBName varchar(100)=''
declare @DBCount int=0

declare CursorX cursor for
select name from sys.databases
open Cursorx
fetch from Cursorx into @DBName
while @@FETCH_STATUS=0
begin
  WaitFor Delay '00:00:10'
  set @DBCount=@DBCount+1
  fetch next from Cursorx into @DBName
end
close CursorX
deallocate CursorX

select @DBCount

 

Sorgumuz çok basit. Bütün database’ler üzerinde 10 saniye bekleyerek gezen cursor, toplam database sayısını bulmaya çalışmakta J

Yukarıda sorguyu çalıştırdıktan sonra şimdi açık olan cursor’ları sorgulayalım.

select ec.cursor_id,
	ec.name as cursor_name,
	ec.creation_time,
	ec.dormant_duration/1000 as dormant_duration_sec,
	ec.fetch_status,
	ec.properties,
	ec.session_id,
	es.login_name,
	es.host_name,
	st.text,
    SUBSTRING(st.text, (ec.statement_start_offset/2)+1, 
        ((CASE ec.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text)
         ELSE ec.statement_end_offset
         END - ec.statement_start_offset)/2) + 1) AS statement_text
from sys.dm_exec_cursors(0) ec
cross apply sys.dm_exec_sql_text(ec.sql_handle) st
join sys.dm_exec_sessions es on es.session_id=ec.session_id

 

1


Sorgu sonucunu yorumlayacak olursak. FUNNYCIK makinasından funnycik\tugi kullanıcısı 27.09.2010 21:47:22 tarihinde 51 nolu session üzerinden CursorX isimli bir cursor açmış. Bu cursor’ın son fetch işleminden beri 9 saniye geçmiş ve text ve statement_text bilgileride rapor sonucunda gözüktüğü gibi.

 

Sizde production ortamlarınızda uzun süredir açık olan cursor’ları yukarıdaki script vasıtasıyla sorgulayabilir ve cursor’ların kill edilip edilmemesi üzerinde çalışma yapabilirsiniz.

 

İ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’da blocking yani engelleme durumları en sık baş ağrıtan konulardan biridir. İyi tasarlanmamış yapılarda bir session diğerini bloklar ve buda performans olarak negatif bir etki yaratır. İşin özünde aynı kaynağın birden fazla session tarafından kullanılma isteği yatar. Örneğin bir tabloya a session’ı insert yaparken b session’ı select çekmek isterse b session’ın a session’ın işini bitirmesini beklemesi gerekmektedir. Gerçi “isolation level” da ne set edildiği önemli bir noktadır ama bugünkü yazımızda bu konuya girmeyerek isolation level’ın ReadCommitted olduğunu varsayacağız. İşte session’ların bu tarz birbirini engellemesi durumuna blocking denir. Bugünkü makalemizde hangi session’ın hangi kaynakları lock’ladığına ve hangi session’ın hangi session’ı blockladığı sorularına DMV’ler ile yanıt bulmaya çalışacağız.

[more]

Bugünkü yazımda şu DMV’leri kullanacağım;

select * from sys.dm_tran_locks 
select * from sys.dm_os_waiting_tasks

 

Üzerinde çalışacağımız bir table create edelim.

Use AdventureWorks
GO
create table table1(col1 int, col2 varchar(10))
GO

 

Şimdi şöyle bir örnek yapalım. 1.Session table1 tablosuna transaction içinde insert yaparak bu tabloyu lock’lasın. 2.Session’dan da bu tabloya select çekmeye çalışalım.

Session 1;

begin tran
insert table1 select 1,'a'

 

Session 2;

begin tran
select * from table1

 

Şimdi ilk sorgumuzu çekelim.

 

Session’ların Lock’ladığı Resource’ların Raporu

select DB_NAME(resource_database_id) as DBName,
	request_session_id,
	resource_type,
	resource_subtype,
	resource_description,
	request_mode,
	request_type,
	request_status
from sys.dm_tran_locks 
where request_session_id>50 
	and request_session_id <> @@SPID
	and resource_database_id=DB_ID()
order by request_session_id

 

1


Şimdi sorgu sonucunu yorumlayalım. Gördüğünüz gibi rapor sonucunda 54 ve 55 diye 2 session için lock bilgileri gelmiş. Bu session’lar bizim yukarıda belirttiğimiz Session 1(54) ve Session 2(55) sessionları.

Gördüğünüz gibi 1.Session bir object’e(table1) IX lock koymuş. Aynı zamanda 1 nolu File daki 1056 nolu page’ede lock koymuş (1:1056). Ve son olarak yaptığı insert işlemiyle 1056 nolu page teki 1 nolu satırada lock koymuş. Diğer taraftan select çekmeye çalışan 2.Session 1:1056:1 row’u locklu olduğu için bu row’a lock koymaya çalışmış ama hala WAIT statüsünde beklemekte.

Bu şekilde sys.dm_tran_locks DMV’sini kullanarak hangi kaynaklara hangi session’ların lock koyduğunu veya koymak istediğini sorgulayabiliriz.

Yukarıda belirttiğim gibi 2.Session lock koymak istiyor ama bekliyordu. Şimdi bu session’ı hangi session’ın beklettiğini nasıl sorgulayacağımıza bakalım.

 

Blocking Sorgulama

select session_id,
	wait_duration_ms,
	wait_type,
	blocking_session_id,
	resource_description
from sys.dm_os_waiting_tasks 
where session_id>50

 

2


Rapor sonucunda 55 nolu session’ın 3613792 milisaniyedir beklediğini görüyoruz. Beklemeye neden olan session ise 54 nolu session. Beklenen obje ise AdventureWorks DB’sindeki 1 nolu File’daki 1056 nolu Page’de.

Sorgumuza sys.dm_tran_locks’uda joinleyerek rapor sonucunu detaylandıralım.

select wt.session_id,
	wt.wait_duration_ms,
	wt.wait_type,
	wt.blocking_session_id,
	DB_NAME(tl.resource_database_id) as DBName,
	tl.resource_type,
	tl.resource_subtype,
	tl.resource_description,
	tl.request_mode,
	tl.request_type,
	tl.request_status
from sys.dm_os_waiting_tasks wt
inner join sys.dm_tran_locks tl on tl.lock_owner_address=wt.resource_address
where session_id>50

 

3


Blocklayan ve blocklanan sorguları da görerek daha detaylı bilgi almak için aşağıdaki script’i kullanabilirsiniz. Ben de sistemlerinde bu sorguyu kullanmaktayım.

select t1.resource_type as [lock type]
	,db_name(resource_database_id) as [database]
	,t1.resource_associated_entity_id as [blk object]
	,t1.request_mode as [lock req]			-- lock requested
	,t1.request_session_id as [waiter sid]  -- spid of waiter
	,t2.wait_duration_ms as [wait time]	
	,(select text from sys.dm_exec_requests as r  --- get sql for waiter
		cross apply sys.dm_exec_sql_text(r.sql_handle) 
		where r.session_id = t1.request_session_id) as waiter_batch
	,(select substring(qt.text,r.statement_start_offset/2, 
			(case when r.statement_end_offset = -1 
			then len(convert(nvarchar(max), qt.text)) * 2 
			else r.statement_end_offset end - r.statement_start_offset)/2) 
		from sys.dm_exec_requests as r
		cross apply sys.dm_exec_sql_text(r.sql_handle) as qt
		where r.session_id = t1.request_session_id) as waiter_stmt    --- this is the statement executing right now
	 ,t2.blocking_session_id as [blocker sid] -- spid of blocker
     ,(select text from sys.sysprocesses as p		--- get sql for blocker
		cross apply sys.dm_exec_sql_text(p.sql_handle) 
		where p.spid = t2.blocking_session_id) as blocker_stmt
	from 
	sys.dm_tran_locks as t1, 
	sys.dm_os_waiting_tasks as t2
where 
	t1.lock_owner_address = t2.resource_address

 

image

Sorgu sonucunda da gördüğümüz gibi update işlemi select işlemini blocklamakta.

 

Blocking durumları başımıza sıkça ağrıtan durumlardan birisidir. Sizde yukarıdaki sorguları kullanarak lock’lanan kaynakları, birbirini block’layan session’ları sorgulayabilirsiniz.

 

İ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


Query ve Stored Procedure’lerin kullanım istatistikleri ile alakalı daha önce 2 makale yayınlamıştım. Bu makalelerde Query ve Stored Procedure’lerin kullanımlarına bakmış, CPU,IO gibi kaynak tüketimlerini inceleyip problemli olanları analiz etmiştik.

Bugünkü yazımda ise bu kapsamdaki son DMV olan sys.dm_exec_trigger_stats ile trigger istatistiklerini inceleyip gene CPU,IO gibi kaynak tüketimleri açısından analizlerini yapacağız. Aynı zamanda gene bu DMV vasıtasıyla kullanılmayan trigger’ları raporlayacağız.

[more]

select * from sys.dm_exec_trigger_stats

 

Sorgu sonucu gelen kolonlar hemen hemen sys.dm_exec_query_stats DMV’sinde gelen kolonlar ile aynı. O yüzden bu kolon açıklamalarına sys.dm_exec_query_stats makalesinden bakabilirsiniz.

http://www.turgaysahtiyan.com/post/SQL-Server-e28093-Her-Gun-1-DMW-Gun-4-sysdm_exec_query_stats-ile-Query-Istatistikleri.aspx

 

En Çok CPU Tüketen İlk 50 Trigger (Top 50 CPU Consume Trigger)

Şimdi detaylı bir analiz sorgusu yazalım. Bu sorgumuz ile AdventureWorks DB’si üzerinde çalışan trigger’lari analiz edip en çok CPU tüketen trigger’a göre sıralayacağız.

Benim bu amaç için production ortamlarımda kullandığım Query aşağıdaki gibi.

select top 50 
	   DB_NAME(database_id) as DBName,
	   SCHEMA_NAME(o.schema_id) as TableSchemaName,
	   o.name as TableObjectName, 
	   t.name as TriggerObjectName,
       st.[text] as TriggerCode,
	   qp.query_plan,
	   cached_time as first_execution_time,
	   last_execution_time,
	   execution_count,
       ps.total_logical_reads as total_logical_read,
       ps.total_logical_reads/execution_count as avg_logical_read,
       ps.total_worker_time/1000 as total_cpu_time_ms,
       ps.total_worker_time/ps.execution_count/1000 as avg_cpu_time_ms
	   --*
from sys.dm_exec_trigger_stats ps
left join sys.triggers t on t.object_id=ps.object_id
left join sys.objects o on o.object_id=t.parent_id
cross apply sys.dm_exec_sql_text(ps.plan_handle) st
cross apply sys.dm_exec_query_plan(ps.plan_handle) qp
where DB_NAME(database_id)='AdventureWorks'
order by ps.total_worker_time desc

 

Sorgu sonucu bende boş geldi çünkü AdventureWorks’te hiç SP trigger çalımlamış demekki. İstatistik oluşturmak için trigger’ları tetikleyebilmek amaçlı aşağıdaki sorguları yazan sayı kadar çalıştırıyorum.

--3 kez çalıştırılacak
update Production.WorkOrder 
set StartDate=GETDATE(), EndDate=GETDATE(), DueDate=GETDATE()
where WorkOrderID=1

--2 kez çalıştırılacak
update Sales.SalesOrderHeader
set OrderDate=GETDATE(), DueDate=GETDATE(), ShipDate=GETDATE()
where SalesOrderID=43659

 

Şimdi yukarıdaki DMV select’ini tekrar çekelim.

Sonucu 2 resme bölerek gösteriyorum.

1

2


Sorgu sonucunda gelen trigger’lar bizim az önce tetiklediğimiz trigger’lar. Bu trigger’ların ilk kez ve son kez ne zaman çalıştırıldıklarını görebiliyoruz.Aynı zamanda çalışma istatistiklerini de analiz edebiliyoruz. Örneğin Production.WorkOrder table’ı üzerinde bulunan uWorkOrder trigger’ı toplam 3387 kez çalışmış, her çalışmasında ortalama 50 logical read yapmış ve her çalışması ortalama 6 milisaniye sürmüş.

 

Kullanılmayan Trigger Raporu

Uygulama geliştirme aşamasında gerekli olan trigger’lar yazılır fakat bu trigger’ların bazıları uzun vadede kullanılmamaya başlayabilir. Bu yüzden periyodik olarak trigger’larin kullanım istatistikleri incelenip kullanılmayan trigger’lar yorumlanmalı ve drop edip edilmemeleri üzerinde çalışma yapılmalıdır.

Trigger’larin kullanılmama raporu hazırlanırken DMV’de uzun süredir istatistiki bilgi toplandığından emin olunmalıdır. Örneğin dün restart ettiğiniz SQL Server’da toplanan DMV bilgisi size genel resmi çizmeyecektir. O yüzden service’in analiz yapmak için yeterli süredir UP olduğundan emin olunmalıdır.

Ben production ortamlarımda, kullanılmayan trigger raporu için aşağıdaki Query’i kullanmaktayım.

select SCHEMA_NAME(o.schema_id) as TableSchemaName,
	   o.name as TableObjectName, 
	   t.name as TriggerObjectName,
	   t.create_date,
	   t.modify_date
from sys.triggers t
left join sys.objects o on t.parent_id=o.object_id
where t.is_disabled=0 and t.parent_id>0
	and not exists(Select ps.object_id 
					from sys.dm_exec_procedure_stats ps 
					where ps.object_id=t.object_id
					  and ps.database_id=DB_ID('AdventureWorks')
				  )
order by SCHEMA_NAME(o.schema_id),o.name,t.name

 

Bu sorgu size AdventureWorks DB’si için kullanılmayan trigger’ları sorgulayacaktır.

3

 

Sizde yukarıdaki sorguları kullanarak trigger’larınızın kaynak kullanım istatistiklerini analiz edip kötü yazılmış trigger’larda iyileştirme çalışmaları yapabilirsiniz. Ayrıca kullanılmayan trigger raporu çekip bu trigger’ları drop edip etmeme üzerinde çalışabilirsiniz.

 

İ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 – Her Gün 1 DMV - Gün 10 - sys.dm_io_cluster_shared_drives adlı yazımda Cluster bir environment’ta bulunan drive’ların neler olduklarına TSQL komutları ile nasıl bakabileceğimizi görmüştük. Bugün üzerinde duracağımız sys.dm_os_cluster_nodes DMV’si ile cluster bir environment’taki node’ların neler olduklarını TSQL komutu ile nasıl sorgulayacağımızı görüyor olacağız.

[more]

Failover Cluster olarak kurulmuş bir SQL Server’da minimum 2 adet node bulunmaktadır. Aktif olan node’da bir problem olduğunda failover cluster sistemi devreye girer ve servis’leri 2.node’a otomatik olarak yönlendirerek yüksek erişebilirliği sağlar.

Cluster environment’taki node listesine windows server üzerindeki ClusterAdmin ekranından bakabiliriz. Ama bizim amacımız bu kontrolü TSQL komutları ile yapabilmek.

SQL Server 2000’de gelen fn_virtualservernodes() fonksiyonu ile bu isteğimize cevap bulabiliyoruz. Lakin Microsoft artık bu fonksiyon yerine aşağıdaki DMV’in kullanılmasını önermekte çünkü bu fonksiyon ileriki SQL Server versiyonlarında bulunmayabilir.

select * from sys.dm_os_cluster_nodes

select * from fn_virtualservernodes()

 

Her 2 sorgumuzun sonucuda aşağıdaki gibi bir sonuç olacaktır.

1


Küçük ama önemli olduğunu düşündüğüm bu DMV’ide bilgi dağarcığımızı ekleyerek bugünkü yazımıda noktalıyorum.

 

İ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


SQL Server’da yazılan birçok uygulamada transaction blokları kullanılmaktadır. Transaction’lar begin trans komutu ile başlatılır ve commit tran ya da rollback tran komutuyla sonlandırılır. Commit tran ile sonlandırıldığında bloğun içinde yapılmış her şey onaylanır ve disk’e yazılır duruma gelir. Rollback tran komutuyla bitirilen transaction’lar içindeki yapılmış bütün değişiklikler ise geriye alınır. Bu şekilde transaction bloğu sonuna kadar hata almadan gelirse yapılan işlemlerin tamamı beraber onaylanır ya da geri alınır. Bu mantıkta bize datanın bütünlüğünü sağlamış olur.

Bugünkü yazımda sunucu üzerinde bulunan aktif olan transaction’ları nasıl sorgulayacağımızı göreceğiz. Bu transaction’ların ne zaman, kim tarafından, hangi makine üzerinden, hangi database üzerinde başlatıldığını, son uyguladığı script bilgisi gibi detay bilgileri sorgulayacağız.

[more]

Sorgularımda 3 adet transaction DMV’si kullanacağım. Bunlar;

select * from sys.dm_tran_database_transactions
select * from sys.dm_tran_active_transactions  
select * from sys.dm_tran_session_transactions

 

Ayrıca detaylı sorgularımda bu transaction DMV’lerini aşağıdaki DMV ve DMF’ler ile join yapacağım.

  • sys.dm_exec_sessions
  • sys.dm_exec_connections
  • sys.dm_exec_requests
  • sys.dm_exec_sql_text
  • sys.dm_exec_query_plan

Sorgularıma geçmeden önce bir transaction başlatıyorum ve başlattıktan 10 saniye sonrada bir update komutu çalıştırıyorum.

begin tran mytran
WAITFOR DELAY '00:00:10'
update table1 set col2='g' where col1=5

 

Şimdi detaylı sorgularımıza geçelim.

 

SQL Server Üzerinde Bulunan Aktif Transaction’ları Sorgulama

select tdt.transaction_id,
	tat.name as transaction_name,
	tdt.database_id,
	DB_NAME(tdt.database_id) as DBName,
	tat.transaction_begin_time,
	tdt.database_transaction_begin_time, 
	tdt.database_transaction_type,
	tdt.database_transaction_state,
	tdt.database_transaction_begin_lsn, 
	tdt.database_transaction_last_lsn
from sys.dm_tran_database_transactions tdt
join sys.dm_tran_active_transactions tat on tat.transaction_id=tdt.transaction_id
where tdt.database_id>4
order by 1

 

1

2


Sorgu sonucundan şunu anlıyorum. 14.09.2010 23:48:39 tarihinde AdventureWorks DB’si üzerinde 3079781 ID’li ve mytran adında bir transaction başlatmışım. Bu transaction’da log’u etkileyecek bir hareketi (update-insert) 14.09.2010 23:48:50 tarihinde yapmışım. Bu transaction’ın type’ı 1 miş yani hem okuma hem de yazma amacıyla açılmış. State’i ise 4 müş yani yani bu transaction log kayıdı üretmiş. Ve son olarak bu transaction’ın başlangıç ve bitiş LSN(Log Sequence Number) bilgilerini görmekteyim.

 

Transaction’ların Session ve Connection Bilgilerinin Sorgulanması

Transaction’ların hangi session’lara bağlı oldukları bilgisi sys.dm_tran_session_transactions DMV’sinde tutulmaktadır. Şimdi bu DMV’yi kullanarak yukarıdaki sorgu sonucu bulduğum 3079781 ID’li transaction’ın session ve connection detaylarını öğrenelim. Bu bilgileri sys.dm_exec_sessions ve sys.dm_exec_connections DMV’lerinden alacağız. Bu DMV’ler ile alakalı yazdığım ağağıdaki yazıyı okumanızı tavsiye ederim.

http://www.turgaysahtiyan.com/post/SQL-Server-e28093-Her-Gun-1-DMW-Gun-3-DMWe28099ler-ile-Session%28Process%29-Kontrolleri.aspx

select tst.transaction_id,
	tst.session_id,
	es.login_name,
	es.login_time, 
	es.host_name,
	st.text as LastSQLStatement
from sys.dm_tran_session_transactions tst
join sys.dm_exec_sessions es on es.session_id=tst.session_id
join sys.dm_exec_connections ec on ec.session_id=tst.session_id
cross apply sys.dm_exec_sql_text(ec.most_recent_sql_handle) st 
where tst.transaction_id=3079781

 

3


Sorgu sonucunu yorumlarsak. 3070781 ID’li Transaction’a 54 session_id’li session bağlıymış. Bu session’ı funnycik\tugi kullanıcısı ile FUNNYCIK makinası üzerinden 14.09.2010 22:37:17 tarihinde açmışım. Ve bu session’a bağlı connection’ın en son çalıştırdığı SQL Statement ise bla bla imiş. Gördüğünüz SQL Statement bizim transaction’ı başlatmak için kullandığımız script. Gerçektende en son olarak bu query’i çalıştırmıştım J

 

Aynı zamanda yukarıdaki DMV’yi sys.dm_exec_request DMV’sine de bağlamamız mümkün. Ama şunu unutmamak lazım ki transaction’a bağlı olan session her daim aktif olarak iş yapan yani sys.dm_exec_request DMV’sinde olan bir session olmayabilir. Bunu göz önünde bulundurarak aşağıdaki final sorgumuzu yazıyoruz.

select tdt.transaction_id,
	tat.name as transaction_name,
	tdt.database_id,
	DB_NAME(tdt.database_id) as DBName,
	es.login_name,
	es.host_name,
	st.text as LastSQLStatement,
    SUBSTRING(st.text, (er.statement_start_offset/2)+1, 
        ((CASE er.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text)
         ELSE er.statement_end_offset
         END - er.statement_start_offset)/2) + 1) AS statement_text,
    er.blocking_session_id,
    er.status,
    er.wait_type,
    er.wait_time,
    er.percent_complete,
    er.estimated_completion_time,
    qp.query_plan
from sys.dm_tran_database_transactions tdt
join sys.dm_tran_active_transactions tat on tat.transaction_id=tdt.transaction_id
join sys.dm_tran_session_transactions tst on tst.transaction_id=tdt.transaction_id
join sys.dm_exec_sessions es on es.session_id=tst.session_id
join sys.dm_exec_connections ec on ec.session_id=tst.session_id
left join sys.dm_exec_requests er on er.session_id=tst.session_id
cross apply sys.dm_exec_sql_text(ec.most_recent_sql_handle) st 
OUTER apply sys.dm_exec_query_plan(er.plan_handle) qp

 

Bu sorguda ilk sorgumuza ek olarak ; hali hazırda çalışmakta olan statement_text’i, bir bekleme var ise bekleme zamanını ve tipini, query’nin ne zaman sona ereceğini ve çalışmakta olan query’nin query plan’ınını sorgulayabiliriz.

 

İ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


SP ve Function’lar gibi bir çok yerde kullanılmakta olan bir tablom var ve ben bu tabloda kolon ekleme ya da silme gibi structure değişikliği yapmak istiyorum. Değişikliği yapmadan önce hangi objeler bu tablomu kullanıyor bilmeliyim ki sistemi nasıl etkileyeceğimi kestirebileyim.Ya da tanımladığım bir user-defined datatype’ım var. Bu datatype’ta değişiklik yapmak istiyorum ama hangi objeler bu datatype’ı kullanıyor öğrenmek istiyorum.

Bugünkü makalemde bu sorularımıza cevap bulmak için SQL Server’da Dependency sorgularının DMV’ler ile nasıl yapıldığına bakıyor olacağız.

[more]

SQL Server 2008 ile beraber Dependency kontrollerini yapabilmek amaçlı 2 DMF ve 1 Catalog View geldi. Bu DMF ve Catalog View’lar ve açıklamaları aşağıdaki gibi.

  • sys.dm_sql_referencing_entities : Parametre olarak verilen objenin hangi objelerde kullanıldığını yani hangi objelerin bu parametre olarak verilen objeye dependent olduğunu sorgular.
  • sys.dm_sql_referenced_entities : Parametre olarak verilen objenin hangi objeleri kullandığını yani hangi objelere dependent olduğu sorgular.
  • sys.sql_expression_dependencies : Bir database için tüm dependeny listesini getirir.

SQL Server 2008 database engine objelerin dependency’lerini otomatik olarak kontrol etmekte ve yapılan drop,alter,create işlemleri neticesinde bunları sytem catalog view’lerinde saklamaktadır. Bizde yukarıdaki DMF’leri kullanarak bu bilgilere erişebilmekteyiz.

Şimdi gelin bir çalışma ortamı hazırlayalım ve dependency’leri nasıl sorgulayabildiğimize bakalım.

Çalışma ortamımız için 1 datatype, 1 table ve bu objeleri kullanan 1 SP ve 1 function create edeceğiz.

--Çalışma ortamı için bir DB Create edelim
CREATE DATABASE DBDependency
GO

USE DBDependency
GO

--FullName VarChar(200) şeklinde yeni bir data type tanımlayalım.
CREATE TYPE dbo.FullName FROM VARCHAR(200) NOT NULL
GO

--Customer tablosunu create edelim.
CREATE TABLE dbo.Customer (ID int, Name varchar(100), SurName Varchar(100))
GO

--Parametre olarak verilen FullName bilgisine uyan kaç kayıt olduğunu döndüren function
--Customer tablosuna ve FullName dateype'ına dependent
CREATE FUNCTION dbo.FullNameSearchCount
	(@ParamFullName dbo.FullName)
RETURNS INT
AS
BEGIN
  DECLARE @ReturnValue int=0
  
  SELECT @ReturnValue = Count(Name)
  FROM dbo.Customer WHERE Name + ' ' + Surname = @ParamFullName  
  
  Return(ISNULL(@ReturnValue,0))
END

GO

--ID parametresi ile verilen müşterinin Full adını döndüren Stored Procedure
--Customer tablosuna ve FullName dateype'ına dependent
CREATE PROCEDURE dbo.CustomerFullName 
	@ID int
AS
  DECLARE @ResultFullName dbo.FullName
   
  SELECT @ResultFullName = Name + ' ' + SurName 
  FROM dbo.Customer WHERE ID = @ID

  SELECT @ResultFullName
GO  

 

Çalışma ortamı tanımlamalarımız bitti. Yaptığımız tanımlamaları bir daha gözden geçirelim.

FullName adında bir datatype, Customer adında bir tablo create ettik. Daha sonra FullNameSearchCount adında bir function create ettik ve bu function’da FullName ve Customer objelerini kullandık. Aynı şekilde CustomerFullName adında bir SP create ettik ve bu SP’de de FullName ve Customer objelerini kullandık.

Şimdi dependency sorgulamalarımıza geçelim.

 

Bir DataType’a Dependent Olan Objeler

Bu sorgumuzda FullName DataType’ına dependent olan objeleri sorgulayacağız.

select re.referencing_class Class,
    re.referencing_class_desc as ClassDesc,
	o.type_desc as ObjectType,
	re.referencing_schema_name as SchemaName,
	re.referencing_entity_name as ObjectName,
	re.referencing_id as ObjectID
from sys.dm_sql_referencing_entities ('dbo.FullName', 'type') re
left join sys.objects o on o.object_id = re.referencing_id

 

1


Sorgu sonucunda gördüğünüz gibi CustomerFullName SP’si ve FullNameSearchCount Function’ı FullName DataType’ına dependent’mış. Yani bu objelerde FullName DataType’ı kullanılmış.

 

Bir Table’a Dependent Olan Objeler

Bu sorgumuzda Customer Table’ına dependent olan objeleri sorgulayacağız.

select referencing_class as Class,
    re.referencing_class_desc as ClassDesc,
	o.type_desc as ObjectType,
	re.referencing_schema_name as SchemaName,
	re.referencing_entity_name as ObjectName,
	re.referencing_id as ObjectID
from sys.dm_sql_referencing_entities ('dbo.Customer', 'Object') re
left join sys.objects o on o.object_id = re.referencing_id

 

2


Sorgu sonucunda Customer tablosunu CustomerFullName SP’sinin ve FullNameSearchCount Function’ının kullanmakta olduğunu görüyoruz. Dolayısıyla bu tabloda bir değişikliğe gitmeden önce bu objeleri kontrol etmekte fayda olduğu sonucuna varıyoruz.

 

Bir SP’nin Dependent Olduğu Objeler

Şimdi sorgumuzu tersten çekeceğiz. Bu sorgumuzda CustomerFullName SP’sinin hangi objeleri kullandığına yani hangi objelere dependent olduğuna bakacağız.

select re.referenced_class_desc,
	   re.referenced_schema_name,
	   re.referenced_entity_name,
	   re.referenced_minor_name	   	   
from sys.dm_sql_referenced_entities('dbo.CustomerFullName','object') re

 

3


Sorgu sonucunda gördüğümüz gibi CustomerFullName SP’si Customer Table’ını ve FullName DataType’ını kullanmakta. Ayrıca Customer Table’ından hangi kolonları spesifik olarak kullandığını da görmemiz mümkün.

 

Bir Database’deki Tüm Dependency’leri Sorgulama

Yukarıdaki sorgularda DMF’leri kullandığımız için parametre vererek sorgu çekmek durumunda kalmıştık. Bir database’de bulunan bütün Dependency’leri ise sys.sql_expression_dependencies catalog view’ına select çekerek sorgulayabiliriz.

select referencing_class_desc,
	   o1.type_desc as referencing_object_type,   
       SCHEMA_NAME(o1.schema_id)+'.'+o1.name as referencing_entity_name,
	   referenced_class_desc,
	   o2.type_desc as ReferencedObjectType,   
	   ed.referenced_schema_name+'.'+ed.referenced_entity_name as referenced_entity_name
from sys.sql_expression_dependencies ed
left join sys.objects o1 on o1.object_id = ed.referencing_id
left join sys.objects o2 on o2.object_id = ed.referenced_id

 

4

Bu sorgumuzda da çalışma ortamı database’imizde bulunan bütün Dependency’leri sorguladık. Sorgu sonucunda örneğin CustomerFullName SP’sinin Customer Table’ına ve FullName DataType’ına Dependent olduğunu görebiliyoruz.

 

İ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 execute edilen procedure, trigger ya da adhoc query’ler için oluşturduğu Query Plan’larını saklar ve daha sonraki kullanımlarda bu query plan’ları kullanarak işlemin daha hızlı sonuçlanmasını sağlar.

sys.dm_exec_cached_plans DMV’si ile bu saklanan Query Plan’larını sorgulayabilir, hangi obje için oluşturulduğunu, query plan’ın ne olduğunu, bu query plan’ın kaç defa kullanıldığını ve memory’de ne kadar yer kapladığını öğrenebiliriz.

[more]

select * from sys.dm_exec_cached_plans 

 

Üzerinde duracağımız kolonlar ve açıklamaları şu şekildedir.

  • usecounts : Cache’lenen query plan’ın cache’lendikten sonra kaç defa kullanıldığını gösterir.
  • size_in_bytes : Cache’lenen Query Plan’ın memory’de byte cinsinden ne kadar yer kapladığını gösterir.
  • objtype : Cache’lenen object’in type’ını verir. Proc , Adhoc, Trigger, View gibi değerler alabilir. Full listeye BOL’dan erişebilirsiniz.
  • plan_handle : Cache’lenin object’in plan’ını verir. Bu kolonu sys.dm_exec_query_plan’a cross’layarak Query Plan’a, sys.dm_exec_sql_text’e cross’layarak da statement text’e erişebiliriz.

Şimdi sorgumuzu detaylandırarak örneklerimize geçelim.

Cache’lendikten Sonra Kullanılmış Query Plan Raporu

Bu sorgu ile Cache’lenmiş objelerden kullanım sayısı 1 den fazla olanları sorgulayıp kullanım sayılarına göre dizeceğiz. Ayrıca sorgumuza statement_text ve Query_plan’ı ekleyerek bu bilgileri görüntüleyeceğiz.

select cp.usecounts,
	   st.text,
	   qp.query_plan,	   
	   cp.cacheobjtype,
	   cp.objtype,
	   cp.size_in_bytes
from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_query_plan(cp.plan_handle) qp
cross apply sys.dm_exec_sql_text(cp.plan_handle) st
where cp.usecounts>1
order by cp.usecounts desc

 

1

 

Cache’lenen Stored Procedure’lerin Query Planları

Benim favorim olan ve production ortamlarımda sürekli kullandığım bu sorgu ile Cache’lenen Stored Procedure’lerin Query Planlarına, statement_text’lerine ve kullanım sayılarına erişebileceğiz.

Sorgumuzu çekmeden önce veri oluşturmak açısından aşağıdaki SP’leri yanlarındaki adet kadar çalıştıralım.

exec dbo.uspGetBillOfMaterials 15,'01.01.2010' -- 4 Kez Çalıştırılacak
exec dbo.uspGetEmployeeManagers 11 -- 3 Kez Çalıştırılacak

 

DMV sorgu sonucumuzda uspGetBillOfMaterials ve uspGetEmployeeManagers SP’lerinin cache’lenmiş olduğunu ve 3 ve 2 kez kullanılmış olduklarını görmeyi planlıyorum.

Sorgumuzu çekelim.

select DB_NAME(st.dbid) as DBName,
	   OBJECT_SCHEMA_NAME(st.objectid,st.dbid) as SchemaName,
	   OBJECT_NAME(st.objectid,st.dbid) as ObjectName,
	   st.text,
	   qp.query_plan,
	   cp.usecounts,
	   cp.size_in_bytes
from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_query_plan(cp.plan_handle) qp
cross apply sys.dm_exec_sql_text(cp.plan_handle) st
where st.dbid<>32767 --Resource DB'yi exclude ediyoruz.
   and cp.objtype='Proc'

 

2


Gördüğünüz gibi sorgu sonucunda gelen ilk 2 satır bizim az önce execute ettiğimiz SP’ler. Bu satırlardan uspGetEmployeeManagers SP’si üzerinde duracak olursak; bu SP’nin cache’lendikten sonra 3 kez kullanıldığını görüyoruz. Ayrıca bu plan’ın memory’de 128880 byte kapladığı bilgisi de gelen bilgiler arasında. Text kısmından bu SP’nin koduna erişebilmekteyiz. Ve son olarak query_plan kısmındaki XML kod’una tıklarsak eğer yeni bir sayfada bu SP için oluşturulmuş Query Plan’ı diagram olarak görmemiz mümkün.


3

 

Cache’lenen Plan’ların Obje Tipine Göre Dağılımı

SP,View,Trigger ya da Adhoc gibi sorguların cache’lendiğini söylemiştik. Aşağıdaki sorgu vasıtasıyla her tipten kaç adet Query Plan’ın cache’lendiği bilgisine erişebilirsiniz.

select cp.objtype,COUNT(*) as ObjectCount
from sys.dm_exec_cached_plans cp
group by cp.objtype
order by 2 desc

 

4


Bu sorgu sonucundan 20 View’in , 14 Stored Procedure’ün query plan’ının cache’lenmiş olduğu bilgisine erişiyoruz.

 

Sizde sunucularınızda sys.dm_exec_cached_plans için hazırlamış olduğum yukarıdaki sorguları çekeren cache’lenmiş query planları sorgulayabilirsiniz. Bu sorgular vasıtasıyla hangi obje’ler için hazırlanmış query plan’ların cache’lendiğini, cache’lendikten sonra kaç kez kullanıldıkları bilgilerine erişebilirsiniz.

 

İ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


Sistemimizde gerçekleşen bir performans sıkıntısını anlık olarak kontrol ederken sys.dm_io_pending_io_requests DMV vasıtasıyla aktif olarak disk üzerinde bekleyen bir işlem olup olmadığını kontrol ettik ve olmadığını gördüğümüzü düşünelim. Peki bir sonraki adımımız ne olacak. Nasıl ki disk üzerinde ki anlık beklemeleri sorguluyorsak aynı şekilde CPU üzerinde de bir bekleme var mı diye kontrol etmeliyiz. İşte bugünkü yazımızda CPU üzerinde herhangi bir bekleme var mı, CPU yetersizliğinden dolayı performans sıkıntısı çekiyor muyum gibi sorularımıza sys.dm_os_schedulers DMV’si ile cevap arayacağız.

[more]

select * from sys.dm_os_schedulers 

 

Sorgu sonucunda gelen satırlardan scheduler_id si 255 ve büyük olanlar sistemin kendi işleri için kullandığı scheduler’lar. 255 ten küçük olanlar ise kullanıcı isteklerini gerçekleştiren scheduler’lardır. Dolayısıyla sorgularımızda scheduler_id<255 where clause’unu kullanacağız.

Benim production ortamlarında CPU’da bekleyen işleri sorgulamak için kullandığım sorgu aşağıdaki gibi.

select scheduler_id,
	   cpu_id,
	   is_idle,
	   current_tasks_count,
	   runnable_tasks_count
from sys.dm_os_schedulers
where scheduler_id<255

 

1


Bizi bu sorguda en fazla ilgilendiren kolon runnable_tasks_count kolonu. Bu kolon şu anda CPU’lar üzerinde kaç tane işin beklediğini gösteriyor.

Best practise olarak bu değerin her bir satır için 4 ten fazla olmaması şeklinde. Eğer toplamda runnable_tasks_count değeri CPU sayısının 4 katından fazla ise bir CPU yetersizliği olduğu sonucuna varabiliriz.

select case when (SUM(runnable_tasks_count)/COUNT(*)>4) then 'CPU sıkıntısı var! '
			else 'CPU sıkıntısı yok! ' end as CPUStatus		 
from sys.dm_os_schedulers
where scheduler_id<255

 

2


Bu DMV’den elde edebileceğimiz diğer bir önemli bilgi hangi scheduler için disk üzerinde bir bekleme yaşanıp yaşanmadığı bilgisi. Bu bilgiyi pending_disk_io_count kolonu ile alabiliriz.

Daha önce sys.dm_io_pending_io_requests DMV’si ile anlık olarak disk üzerinde yaşanan beklemeleri nasıl sorgulayacağımıza bakmıştık. pending_disk_io_count kolonuda bu DMV ile aynı bilgiyi dönmektedir.

Şimdi bu 2 sorguyu tek bir ekranda görelim ve ne demek istediğimizi daha anlaşılır hale getirelim.

select scheduler_id,
         pending_disk_io_count,
         cpu_id
from sys.dm_os_schedulers
where scheduler_id<255

select * from sys.dm_io_pending_io_requests

 

3


Gördüğünüz gibi 1 nolu CPU’da bir IO beklemesi var ve bunu her 2 sorguda teyit ediyor.

 

Sizde yukarıdaki sorguları kullanarak CPU üzerinde herhangi bir sıkıntı olup olmadığını sorgulayabilirsiniz. Tabi ki bu DMV’e çekilecek bir sorgu sonucunda hemen CPU alımı kararı vermemek gerekiyor. CPU’yu yoran query’leri, procedure’leri sorgulamalı ve eğer performans sıkıntısı çıkaran sorgular var ise ilk önce bunlar üzerinde çalışma yapılmalıdır. Uygulama olarak yapılacak performans artırımların tamamı yapıldıktan sonra hala CPU sıkıntısı çekiliyorsa işte o zaman CPU alımı düşünülmelidir.

 

İ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ünkü DMV yazımda sys.dm_os_sys_info DMV’si ile Server bilgilerini nasıl kontrol edip yorumlayabileceğimizi görmüştük. Bugünkü konumuz olan sys.dm_os_sys_memory ile de server’ın total physical memory değerini, kullanılabilir physical memory değerini, pagefile değerleri gibi memory ile alakalı bilgileri kontrol edebiliriz.

[more]

select * from sys.dm_os_sys_memory

 

Sorgu sonucu gelen kolonların bazılarına bakacak olursak;

  • total_physical_memory_kb : Sunucuda bulunan toplam fiziksel memory değeri
  • available_physical_memory_kb : Bu fiziksel memory’nin ne kadarının boşta olduğu. Dolayısıyla total den bu değeri çıkarırsak şu an kullanılan memory değerini erişmiş oluruz.
  • total_page_file_kb : Page file’ın toplam ulaşabileceği boyutu
  • available_page_file_kb : Kullanılabilecek page file boyutu. Dolayısıyla total den bu değeri çıkarırsak şu anki page file’ın boyutuna erişmiş oluruz.
  • system_memory_state_desc : Memory değerlerini kontrol ederek aşağıdaki mesajlardan birini verir. Bu kolondaki bilgiyi kullanarak şu anki memory durumumuz hakkında genel bir bilgi edinebiliriz.
    • Available physical memory is high
    • Available physical memory is low
    • Physical memory usage is steady

Benim 3GB Memory’li bilgisayarımda gelen sorgu sonucu aşağıdaki gibidir.

select total_physical_memory_kb/1024 as total_physical_memory_mb,
	   (total_physical_memory_kb-available_physical_memory_kb)/1024 as used_physical_memory_mb,
	   available_physical_memory_kb/1024 as available_physical_memory_mb,
	   total_page_file_kb/1024 as total_page_file_mb,
	   (total_page_file_kb-available_page_file_kb)/1024 as used_page_file_mb,
	   available_page_file_kb/1024 as available_page_file_mb,
	   system_memory_state_desc
from sys.dm_os_sys_memory

 

1


Sizde yukarıdaki sorgu ile özellikle system_memory_state_desc kolonunu kontrol ederek herhangi bir memory sıkıntısı var mı diye sorgulayabilirsiniz. Hatta bunun için bir job hazırlayıp 1 saatte bir durumu mail attırtabilirsiniz. Onun haricinde TSQL komutları ile toplam ve kullanılan memory bilgilerine ulaşarak server üzerinden bakma zorunluluğunuzu ortadan kaldırabilirsiniz.

 

İ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 DMV’leri arasında en sevdiğim DMV’lerden biride sys.dm_os_sys_info DMV’sidir. Bu DMV ile Server’ın ne zaman start olduğu, SQL Server’ın en son ne zaman restart edildiği, CPU adeti ve fiziksel memory boyutu gibi server bilgilerini TSQL komutları ile alabilmemiz mümkün.

[more]

select * from sys.dm_os_sys_info

 

Sorgu sonucu gelen kolonların bazılarına bakacak olursak;

  • sqlserver_start_time : SQL Server service’inin ne zaman start olduğu.
  • ms_ticks : Milisaniye cinsinden server’ın en son start olduğu zamandan beri geçen süre.
  • sqlserver_start_time_ms_ticks : SQL Server service’i start olduğunda ms_ticks süresi. ms_ticks kolonundan bu kolon çıkartıldığında milisaniye cinsinden SQL Server service’inin en son restart’ından itibaren geçen süreye erişebiliyoruz.
  • cpu_count : Server’ın sahip olduğu logical CPU yani core CPU sayısı.
  • physical_memory_in_bytes : Server’ın fiziksel memory boyutu.
  • virtual_memory_in_bytes : Virtual Memory’nin boyutu.
  • max_workers_count : Gerektiğinde maksimum kaç worker yani thread’in oluşturulabileceği
  • scheduler_count : Kullanıcı işlerini yapacak kaç adet scheduler’ın olduğu. sys.dm_os_schedulers DMV’sindeki scheduler_id si 255 ten küçük olan scheduler sayısı ile karşılaştırılabilir.
  • scheduler_total_count : Toplam(User+System) scheduler adeti. sys.dm_os_schedulers DMV sinde toplam satır sayısı ile karşılaştırılabilir.

Benim 1 CPU’lu Dual Core ve 3GB Memory’li laptop’ımda gelen sorgu sonucu aşağıdaki gibidir.

select sqlserver_start_time,
	   ms_ticks,
	   sqlserver_start_time_ms_ticks,
	   cpu_count,
	   physical_memory_in_bytes/1024/1024 as physical_memory_MB,
	   virtual_memory_in_bytes/1024/1024 as virtual_memory_MB,
	   max_workers_count,
	   scheduler_count,
	   scheduler_total_count
from sys.dm_os_sys_info

 

1


Özellikle SQL Server’ın ne zaman start olduğu bilgisini değişik sorgularımızda kullanabiliriz. Örneğin DMV istatistiklerini topladığımız sys.dm_exec_query_stats gibi DMV’lerde bir query’nin toplam execution sayısını görebilmenin yanında dakikada yada saniyede kaç kez execute edildiği bilgisine bu bilgi vasıtasıyla erişebiliriz.

Şimdi 2 değişik yöntemle SQL Server’ın kaç dakikadır çalışır vaziyette olduğu bilgisini bulmaya çalışalım.


SQL Server UpTime – 1.Yöntem

Bu yöntemde sqlserver_start_time kolonunu kullanacağız.

select DATEDIFF(MINUTE, sqlserver_start_time, CURRENT_TIMESTAMP) as SQLServerUpTime_minute
from sys.dm_os_sys_info

 

2


SQL Server UpTime – 2.Yöntem

Bu yöntemde ise ms_ticks ve sqlserver_start_time_ms_ticks kolonlarını kullanacağız.

select (ms_ticks-sqlserver_start_time_ms_ticks)/1000/60 as SQLServerUpTime_minute
from sys.dm_os_sys_info

 

3

Server’ın bilgilerine erişebildiğiniz sys.dm_os_sys_info DMV’sindeki bilgileri diğer DMV’ler ile cross join yaparak daha etkili rapor sonuçları elde edebilirsiniz. Ayrıca memory ve cpu değeri gibi bilgilere server’a ulaşmadan TSQL komutları ile erişebilirsiniz.

 

İ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


Bugün işleyeciğimiz sys.dm_db_partition_stats DMV’si ile sistemde bulunan table’lardaki bütün index’ler için partition bilgileri, kullandıkları page sayısı, satır sayıları gibi bilgileri sorgulayacağız.

Aslında bu bilgileri bu DMV olmadan da table table yada index index dönerek toplamamamız mümkün. Ama sys.dm_db_partition_stats DMV’si tek bir sorgu ile bu bilgilerin tamamına kolayca erişmemizi sağlıyor.

[more]

DMV’ye geçmeden önce partition ve index’ler konusundaki bilgilerimizi biraz tazeleyelim.

SQL Server’da partition kullanarak row bazında kayıtları fiziksel olarak farklı table’larda tutmak mümkün. Genelde performans artışı için kullanılan bu tekniğe partitioning denmekte. Default olarak ilk partition ID 1’dir. Eğer bir table’da 4 partition var ise bu table 4 parçaya ayrılmış diye düşünebiliriz.

Index konusunda bilgi tazelemek gerekirse, bildiğiniz gibi her table da ancak 1 clustered index olabilmekte ve eğer varsa table dataları bu clustered index’e göre fiziksel olarak tutulmaktadır. Dolayısıyla Clustered Index’e fiziksel index diyebiliriz. Clustered Index’in ID’si her daim 1’dir.

Clustered index olmayan table’larda Heap olmaktadır. Yani bir table’da ya clustered index vardır ya da Heap. Heap’in ID’si de 0’dır.

NonClustered index ise 1 den fazla olabilmekte. Bu index’e de mantıksal index diyebiliriz ve ID’si 2 den büyük olan index’ler NonClustered Index manasına gelmektedir.

Şimdi DMV’mize ilk sorgumuzu çekelim. AdventureWorks DB’sinde aşağıdaki sorguyu çalıştıralım.

select * from sys.dm_db_partition_stats

 

Bizi ilgilendiren kolonlar ve açıklamaları şu şekilde;

  • object_id : Index’in bağlı bulunduğu table’ın ID’si. sys.objects’e join edeceğiz.
  • index_id : Index’in ID si. sys.indexes’e join edeceğiz.
  • partition_number : Partition number’ı. AdventureWorks’te hiç partitioning kullanılmadığı için bu değer bütün index’ler için 1’dir.
  • in_row_data_page_count : Data’yı tutmak için kullanılan page sayısı. Index eğer Heap ise bu değer heap level’daki data page’lerin sayısını ifade eder. Eğer Clustered yada NonClustered Index ise bu değer leaf level’daki data page’leri ifade eder. NonLeaf’leri içermez.
  • used_page_count : NonLeaf ve Leaf level’lardaki page’lerin toplamını ifade eder.
  • reserved_page_count : Toplam rezerve edilen page sayısını ifade eder. Table’ın toplam kapladığı alan bu page sayısı üzerinden hareket edilerek bulunur.
  • row_count : Bahsi geçen partition’daki satır sayısını ifade eder. AdventureWorks’te partitioning kullanılmadığı için her index için table’da bulunan satır sayısını ifade eder.

Şimdi sorgumuzu detaylandıralım. Ben production ortamlarım için aşağıdaki gibi bir sorgu kullanmaktayım. Bu sorguyu AdventureWorks DB’sinde çalıştıralım.

select schema_name(o.schema_id) as SchemaName,
	   o.name as TableName,
	   ps.index_id,
	   i.name as indexName,
	   case ps.index_id when 0 then 'Heap' when 1 then 'Clustered' else 'NonClustered' end IndexType,
	   ps.partition_number,
	   ps.in_row_data_page_count,
	   ps.used_page_count,
	   ps.reserved_page_count,		   
	   ps.row_count 
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.type='U'
order by o.name

 

1


Gelen sonuç üzerinde biraz konuşalım. Örneğin Person.Address tablosunu ele alalım. Gördüğünüz gibi bu tablo için 4 tane index kullanılmış ve bu index’lerden ilki clustered index. Yani bu tabloda bir Heap durumu yok. 1 ID nolu index yani clustered index 278 data page’den oluşmaktaymış. Leaf yani en alt level’da 278 data page bulunmaktaymış. Toplam leaf ve nonleaf data page sayısı 280 mişki buradan nonleaf data page sayısının 2 olduğunu çıkarıyoruz. Ayrıca toplamda bu index için 283 page rezerve edilmiş. Ve son olarak bu index’teki kayıt sayısının 19614 olduğunu görüyoruz.


AdventureWorks DB’sindeBulunan Tüm Table’ların Kayıt Sayıları

Şimdi başka bir sorgu çekelim. Bu sorgumuzda AdventureWorks DB’sinde bulunan table’ların kayıt sayılarını alalım büyükten küçüğe dizelim. Bunun için index_id in (0,1) where clause’unu kullanacağız. Çünkü daha öncede bahsettiğimiz gibi bir table da ya Heap olur ya da Clustered Index.

select schema_name(o.schema_id) as SchemaName,
	   o.name as TableName,
	   ps.row_count 
from sys.dm_db_partition_stats ps
join sys.objects o on o.object_id = ps.object_id
where o.type='U' and ps.index_id in (0,1)
order by ps.row_count desc

 

2


AdventureWorks DB’sindeBulunan Tüm Table’ların Boyutları Raporu

Şimdi aynı mantıkta table’ların boyutlarının raporunu çekelim. Daha önce bu rapor ile alakalı aşağıdaki makaleyi yayınlamıştım.

http://www.turgaysahtiyan.com/post/SQL-Server-e28093-Bir-DB-de-Bulunan-Table-larc4b1n-Boyutlarc4b1-Raporu.aspx

Bu makalede table’ların boyutlarını alabilmek için tabiri caizse göbeğimiz çatlamıştı. Oysaki bu DMV ile tek bir sorgu vasıtasıyla bu raporu çekebilmemiz mümkün.

select schema_name(o.schema_id) as SchemaName,
	   o.name as TableName,
	   SUM(ps.reserved_page_count)*8 as TableSize_KB
from sys.dm_db_partition_stats ps
join sys.objects o on o.object_id = ps.object_id
where o.type='U'
group by o.schema_id, o.name
order by SUM(ps.reserved_page_count) desc

 

3

 

sys.dm_db_partition_stats DMV’sinin faydalı 3 değişik kullanımını gördük. Bir sonraki DMV yazımızda görüşmek üzere.

 

İ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


sys.dm_exec_query_stats ile Query İstatistikleri yazımda Database Engine’in çalıştırdığı bütün query’lerinin kaynak tüketimi açısından nasıl analiz edilip kötü yazılmış query’lerin bulunabileceğinden bahsetmiştik. Bugünkü yazımda ise aynı performans kontrollerinin Stored Procedure’ler üzerinde nasıl yapılacağına bakıyor olacağız.

Stored Procedure’lerin istatistiklerini incelemek için sys.dm_exec_procedure_stats DMV’sini kullanacağız. Bu DMV ile CPU, IO gibi değerler üzerinden SP’lerin kaynak tüketimine bakabileceğimiz gibi aynı zamanda kullanılmayan SP’leride analiz edebileceğiz.

[more]

select * from sys.dm_exec_procedure_stats

 

Sorgu sonucu gelen kolonlar hemen hemen sys.dm_exec_query_stats DMV’sinde gelen kolonlar ile aynı. O yüzden bu kolon açıklamalarına sys.dm_exec_query_stats makalesinden bakabilirsiniz.
http://www.turgaysahtiyan.com/post/SQL-Server-e28093-Her-Gun-1-DMW-Gun-4-sysdm_exec_query_stats-ile-Query-Istatistikleri.aspx

En Çok CPU Tüketen İlk 50 Stored Procedure (Top 50 CPU Consume Stored Procedure)

Şimdi detaylı bir analiz sorgusu yazalım. Bu sorgumuz ile AdventureWorks DB’si üzerinde çalışan SP’leri analiz edip en çok CPU tüketen SP’ye göre sıralayacağız.

Benim bu amaç için production ortamlarımda kullandığım Query aşağıdaki gibi.

select top 50 
   DB_NAME(database_id) as DBName,
   OBJECT_NAME(object_id) as SPName,
   st.[text] as SPCode,
   qp.query_plan,
   cached_time as first_execution_time,
   last_execution_time,
   execution_count,
   ps.total_logical_reads as total_logical_read,
   ps.total_logical_reads/execution_count as avg_logical_read,
   ps.total_worker_time/1000 as total_cpu_time_ms,
   ps.total_worker_time/ps.execution_count/1000 as avg_cpu_time_ms
from sys.dm_exec_procedure_stats ps
cross apply sys.dm_exec_sql_text(ps.plan_handle) st
cross apply sys.dm_exec_query_plan(ps.plan_handle) qp
where DB_NAME(database_id)='AdventureWorks'
order by ps.total_worker_time desc

 

Sorgu sonucu bende boş geldi çünkü AdventureWorks’te hiç SP execute etmemişim. İstatistik oluşturmak için aşağıdaki SP’leri yanlarında yazan sayı kadar çalıştırıyorum.

exec dbo.uspGetBillOfMaterials 15,'01.01.2010' -- 3 Kez Çalıştırılacak
exec dbo.uspGetEmployeeManagers 11 -- 2 Kez Çalıştırılacak

 

Şimdi yukarıdaki select’imizi tekrar çekelim.

Sonucu 2 resme bölerek gösteriyorum.


1

2


Sorgu sonucunda gelen SP’ler bizim az önce execute ettiğimiz SP’ler. Bu SP’lerin ilk kez ve son kez ne zaman çalıştırıldıklarını görebiliyoruz.Aynı zamanda çalışma istatistiklerini de analiz edebiliyoruz. Örneğin uspGetEmployeeManager SP’si toplam 2 kez çalışmış, her çalışmasında ortalama 67 logical read yapmış ve her çalışması ortalama 2 milisaniye sürmüş.


Kullanılmayan Stored Procedure Raporu

Uygulama geliştirme aşamasında gerekli olan SP’ler yazılır fakat bu SP’lerin bazıları uzun vadede kullanılmamaya başlayabilir. Bu yüzden periyodik olarak SP’lerin kullanım istatistikleri incelenip kullanılmayan SP’ler yorumlanmalı ve drop edip edilmemeleri üzerinde çalışma yapılmalıdır.

SP’lerin kullanılmama raporu hazırlanırken DMV’de uzun süredir istatistiki bilgi toplandığından emin olunmalıdır. Örneğin dün restart ettiğiniz SQL Server’da toplanan DMV bilgisi size genel resmi çizmeyecektir. O yüzden service’in analiz yapmak için yeterli süredir UP olduğundan emin olunmalıdır. Ayrıca bazı SP’lerin aylık ya da yılık olarak çalışabilecekleri de göz ardı edilmemelidir.

Ben production ortamlarımda, kullanılmayan SP raporu için aşağıdaki Query’i kullanmaktayım.

select SCHEMA_NAME(o.schema_id) as SchemaName,
       p.name as ObjectName, 
       p.create_date,
       p.modify_date
from sys.procedures p
left join sys.objects o on p.object_id=o.object_id
where p.type='P'
	and not exists(Select ps.object_id 
					from sys.dm_exec_procedure_stats ps 
					where ps.object_id=p.object_id
					  and ps.database_id=DB_ID('AdventureWorks')
				  )
order by SCHEMA_NAME(o.schema_id),p.name

 

Bu sorgu size AdventureWorks DB’si için kullanılmayan SP’leri sorgulayacaktır.


3


Sizde yukarıdaki sorguları kullanarak SP’lerinizin kaynak kullanım istatistiklerini analiz edip kötü yazılmış SP’lerde iyileştirme çalışmaları yapabilirsiniz. Ayrıca kullanılmayan SP raporu çekip bu SP’leri drop edip etmeme üzerinde çalışabilirsiniz.

 

İ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 da index kullanımının önemine hemen hemen her yazımda değiniyorum.Etkili index kullanımının performans’a inanılmaz artı etkiler yarattığını hepimiz biliyoruz. Yeni bir uygulama geliştirirken index’leri ihtiyaca göre create ediyoruz. Fakat uygulama yaşamaya devam ettikçe yeni index ihtiyaçları ortaya çıkıyor. Bir kısmı bizim kontrolümüz altında olup farkedilebilirken bir kısmını gözden kaçırmamız mümkün.

Bugün anlatacağım 4 DMV ile SQL Server’ın bize create edilmesini önerdiği eksik index’leri nasıl sorgulayacağımızı göreceğiz.

[more]

Aslında mantık çok basit. Her sorgu database engine’den geçiyor ve database engine gelen sorgu için bir query plan oluşturarak en optimum kullanımı bulmaya çalışıyor. Bu optimum kullanımı oluştururken index’leri kullanmaya çalışıyor fakat eğer bizim sorgumuza uyan bir index yok ise bunuda az sonra belirteceğim DMV’lerde log’luyor.

Kullanacağımız DMV ve DMF ler şu şekilde;

  • sys.dm_db_missing_index_group_stats - DMV
  • sys.dm_db_missing_index_groups - DMV
  • sys.dm_db_missing_index_details - DMV
  • sys.dm_db_missing_index_columns - DMF

Kısa kısa bu DMV’leri tanıyalım.


sys.dm_db_missing_index_group_stats

Eksik index’ler hakkında özet bir bilgi sunar.

select * from sys.dm_db_missing_index_group_stats

 

Üzerinde duracağımız kolonlar şu şekilde;

  • group_handle : Grup’u belirten ID dir. Server bazında unique değer içerir.
  • unique_compiles : Kaç adet compilation ve recompilation’ın bu eksik index’ten etkilendiğini gösterir.
  • user_seeks : Eksik olan bu index’e kaç defa seek yapıldığını gösterir.
  • user_scan : Eksik olan bu index’e kaç defa scan yapıldığını gösterir.
  • last_user_seek : En son ne zaman seek yapıldığını gösterir.
  • last_user_scan : En son ne zaman scan yapıldığını gösterir. Last tarihler bizim için önemli değerlerdir. Seek ve scan adetleri fazla olabilir ama belkide artık ihtiyaç kalmamıştır ve uzun süredir bu eksik index kullanılmıyordur. Bunu kontrol etmek için Last tarih değerlerini kullanabiliriz.
  • avg_total_user_cost : Eksik olan bu index tanımlanırsa eğer query cost’unda ne kadarlık bir düşme olacağını belirtir.
  • avg_user_impact : Eksik olan bu index tanımlanırsa eğer query cost’unda yüzde olarak ne kadarlık bir etki yaratacağını belirtir.


sys.dm_db_missing_index_groups

Bu DMV’yi sys.dm_db_missing_index_group_stats ile sys.dm_db_missing_index_details DMV sini join’lemek için kullanacağız.


sys.dm_db_missing_index_details

Eksik index hakkında kolon bilgileri gibi detaylı bilgileri döndürür.

select * from sys.dm_db_missing_index_details

 

Üzerinde duracağımız kolonlar şu şekilde;

  • index_handle : Bu kolonu master DMV’lere join için kullanacağız.
  • database_id : Eksik olan index’in bulunduğu DB
  • object_id : Eksik olan index’in bulunduğu table
  • equality_columns : Index’te eşitlik olan kolonlar. Birden fazla kolon “,” ile ayrılır.
  • inequality_columns : Index’te eşitlik olmayan kolonlar. Birden fazla kolon “,” ile ayrılır.
  • included_columns : Index te included olması gereken kolonlar. Birden fazla kolon “,” ile ayrılır. Bu 3 kolonu örneğimizde detaylı inceleyeceğiz.
  • statement : Eksik olan Index’in bulunduğu table


sys.dm_db_missing_index_columns

Bu DMF’de eksik olan index’in kolonları hakkında bilgi verir. sys.dm_db_missing_index_group_details ten en büyük farkı her bir kolon için bir satır olarak bilgi döndürmesidir.

select * from sys.dm_db_missing_index_columns(index_handle)

 

Sorgu sonucu gelen kolon ve açıklamaları şu şekildedir.

  • column_id : Kolon’un ID si
  • column_name : Kolon’un adı
  • column_usage : Kolon’un query de ne şekilde kullanıldığı. Alabileceği değerler EQUALITY, INEQUALITY ve INCLUDE

 

Teorik bilgilerden sonra şimdi sorgularımıza başlayalım. İlk olarak eksik index kaydı oluşturmak için AdventureWorks DB sinde 2 tabloya index olmayan kolonları kullanarak select çekiyorum.

select * from AdventureWorks.Person.Contact where FirstName='Ahmet'
select * from AdventureWorks.Person.Address where City='Ankara'

 

Contact table’ındaki “FirstName” ve Address table’ındaki “City” kolonları index kolonlar değil. Dolayısıyla eksik index sorgularımda bu kolonları göreceğim diye düşünüyorum.


Eksik Index Sorgulama

Aşağıdaki sorgu ile eksik index’leri sorgulayacağım. Sorgumu <I style="mso-bidi-font-style: normal">avg_total_user_cost * avg_user_impact * (user_seeks + user_scans)</I> değerine göre DESC olarak dizeceğim ve eksik olan bu index’lerden en önemli yani tanımladığım anda sistemime en çok yararı olacak ilk 10 index’e bakacağım.

Beklentim Contact tablosu için FirstName kolonu, Address tablosu içinde City kolonunu bu raporda görmek.

select TOP 10 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) 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
order by avg_total_user_cost * avg_user_impact * (user_seeks + user_scans) desc

 

1


Beklediğimiz gibi Contact ve Address tablosu için eksik index bilgisi raporlandı. Ve bu kolonların eşitlik kolonları olduğu bilgisini görüyoruz. Şimdi aşağıdaki select i çekelim ve inequality ve included kolonlarını inceleyelim.

select * from AdventureWorks.Sales.Customer where AccountNumber<>'ABC'

 

Tekrar eksik index sorgusunu çektiğimizde;


2


Customer tablosu için inequality ve included kolon bilgileri ile bir index tavsiyesinde bulunuldu.


Eksik Index Sorgulama - 2

Bu sorgulamamızda eksik index’in kolon bilgilerini “,” ile değil her birini ayrı bir satırda almak istiyoruz.

SELECT ig.*, statement AS table_name,
    column_id, column_name, column_usage
FROM sys.dm_db_missing_index_details AS id
CROSS APPLY sys.dm_db_missing_index_columns (id.index_handle)
INNER JOIN sys.dm_db_missing_index_groups AS ig ON id.index_handle = id.index_handle
ORDER BY ig.index_group_handle, ig.index_handle, column_id;

 

3


Sizde periyodik olarak yukarıdaki eksik index sorgularını kullanarak sisteminizde olması gerekip olmayan index’leri sorgulayabilir ve performans artışı için bu indexlerin oluşturulmasını gündeme getirebilirsiniz.

 

İ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


sys.dm_db_index_usage_stats(urll) DMV sinde index’lerin kullanım oranını görmüştük. Bunun ötesinde hangi index’de update,insert,delete işlemlerinin ayrı ayrı kaç kez gerçekleştiğini, index’te kaç defa bekleme yaşandığını ve IO beklemelerini sorgulamak isteyebiliriz.

Performans sıkıntılarını monitör etmek amaçlı yapılabilecek bu sorgulamalar için sys.dm_db_index_operational_stats DMF sini kullanabiliriz.

[more]

select * from sys.dm_db_index_operational_stats(null,null,null,null)

 

Bu DMF 4 adet parametre almaktadır. Bu parametreler;

  • database_id : İstenilen bir database üzerindeki index’leri sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün database lerde yapılır.
  • object_id : İstenilen bir table üzerindeki index’leri sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün table larda yapılır.
  • index_id : İstenilen bir index’i sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün index lerde yapılır.
  • partition_number : İstenilen index’in istenilen bir partition’ı sorgulanmak istendiğinden bu parametre kullanılabilir. NULL verilirse kontrol bütün partition’larda yapılır.

Sorgu sonucunda gelen bilgileri 3 başlık altında inceleyeceğiz.


1 - Index’i etkileyen insert-update-delete işlemlerinin sorgulanması

Bu sorgulama altında aşağıdaki 3 kolonu inceleyeceğiz.

  • leaf_insert_count : Index leaf’ine toplam yapılan insert sayısı
  • leaf_update_count : Index leaf’inde toplam yapılan update sayısı
  • leaf_delete_count : Index leaf’inde toplam yapılan delete sayısı

AdventureWorks için örnek bir sorgu çekecek olursak;

SELECT OBJECT_NAME(os.object_id) as objectName, 
       i.name as indexName, 
       os.leaf_insert_count, 
       os.leaf_update_count, 
       os.leaf_delete_count 
FROM   sys.dm_db_index_operational_stats(NULL,NULL,NULL,NULL ) os
INNER JOIN sys.indexes i ON i.object_id=os.object_id
			AND i.index_id=os.index_id
WHERE  OBJECTPROPERTY(os.object_id,'IsUserTable') = 1

 

1
 

2 - Index üzerinde gerçekleşen latch ve lock durumları

Bu sorgulama altında aşağıdaki kolonları inceleyeceğiz;

  • page_latch_wait_count : index yada heap te gerçekleşen toplam latch wait sayısını verir.
  • page_latch_wait_in_ms : index yada heap te gerçekleşen toplam latch wait süresini milisaniye cinsinden verir.
  • row_lock_count : Database engine’in index üzerinde kaç defa row lock yaptığı bilgisini verir.
  • row_lock_wait_in_ms : Database engine’in index üzerinde yaptığı row lock ların toplam süresini milisaniye cinsinden verir.
  • page_lock_count : Database engine’in index üzerinde kaç defa page lock yaptığı bilgisini verir.
  • page_lock_wait_in_ms : Database engine’in index üzerinde yaptığı page lock ların toplam süresini milisaniye cinsinden verir.

AdventureWorks için örnek bir sorgu çekersek;

SELECT OBJECT_NAME(os.object_id) as objectName, 
       i.name as indexName, 
       os.page_latch_wait_count, 
       os.page_latch_wait_in_ms,
       os.row_lock_count,
       os.row_lock_wait_in_ms,
       os.page_lock_count,
       os.page_lock_wait_in_ms       
FROM   sys.dm_db_index_operational_stats(NULL,NULL,NULL,NULL ) os
INNER JOIN sys.indexes i ON i.object_id=os.object_id
			AND i.index_id=os.index_id
WHERE  OBJECTPROPERTY(os.object_id,'IsUserTable') = 1

 

2


3 - Index üzerinde IO latch beklemeleri

Bu sorgulama altında aşağıdaki kolonları inceleyeceğiz;

  • page_io_latch_wait_count : Index yada heap page leri memory getirirken yaşanan IO bekleme sayısı.
  • page_io_latch_wait_in_ms : Bu yaşanan beklemelerin milisaniye cinsinden toplam değeri.

AdventureWorks için örnek bir sorgu çekersek;

SELECT OBJECT_NAME(os.object_id) as objectName, 
       i.name as indexName, 
       os.page_io_latch_wait_count,
       os.page_io_latch_wait_in_ms
FROM   sys.dm_db_index_operational_stats(NULL,NULL,NULL,NULL ) os
INNER JOIN sys.indexes i ON i.object_id=os.object_id
			AND i.index_id=os.index_id
WHERE  OBJECTPROPERTY(os.object_id,'IsUserTable') = 1

 

3


sys.dm_db_index_operational_stats
DMF sini 3 farklı şekilde nasıl kullanacağımızı gördük. Sizde yukarıdaki sorguları kullanarak index’lerin operasyonel istatistiklerini sorgulayabilir bu sorgulara göre gerekli aksiyonları alabilirsiniz.

 

İ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 da query performansını arttırıcı ana etkenlerden biri index kullanımıdır. Index’ler vasıtasıyla datanın tamamı okunmaktansa kullanılan index’e göre daha az veri diskten yada memory den okunur. Bu da az IO ve CPU kullanımıyla beraber daha iyi bir performans getirir.

Index’lerin performans artışına sebebiyet verdiği bilindiği için development yada DBA ekibi tarafından sürekli yeni index’ler create edilir. Bir süre sonra ortalık kullanılmayan index’lerle dolup taşar. Gerekli Index’lerin olması ne kadar iyiyse gereksiz index’lerin sistemde durması performans açısından bir o kadar kötüdür.

Bu yüzden DBA olarak ana işlerimizden biri belirli aralıklarla index’lerin kullanımlarına bakmak ve kullanılmayan indexleri drop etmeyi göz önünde bulundurmaktadır.

Bugünkü yazımızda sys.dm_db_index_usage_stats DMV si ile index’lerin kullanım oranlarına(scan-seek-lookup) ve kullanılmayan index’lerin nasıl sorgulanabileceğini görüyor olacağız.

[more]

Index’lerin kullanım oranlarına sys.dm_db_index_usage_stats DMV si ile bakılabilir.

select * from sys.dm_db_index_usage_stats

 

Index üzerinde yapılabilecek 4 işlem vardır. Bunlardan seek, scan ve lookup okuma amacı ile yapılan işlemlerdir. Her daim seek in scan dan fazla olması istenen durumdur. Update ise bu index i etkileyecek herhangi bir kayıdın üstünde yapılacak insert-update-delete işlemini ifade eder. Dolayısıyla update i bir yazma işlemi sonucu olarak görebiliriz.

Sorgu sonucunda seek,scan,lookup ve update işlemleri için en son gerçekleşme tarihlerini görebiliriz.

Not olarak şunu düşmemizde fayda var. sys.dm_db_index_usage_stats DMV side diğer DMV ler gibi SQL Server son restart ından sonraki istatistikleri kümülatif olarak toplar. Ayrıca eğer DB detach edilirse gene bu DMV sıfırlanacaktır. Dolayısıyla herhangi bir operasyona girişmeden önce uzun süredir bilgi toplandığından emin olmakta fayda vardır.

Şimdi sorgumuzu biraz daha detaylandıralım. Seek ve scan oranlarını görelim. Daha önce bahsettiğim gibi seek in scan dan fazla olması arzu edilir. Eğer scan seek ten fazla ise bu index üzerinde bir operasyon yapmayı düşünebilirsiniz.

AdventureWorks’ta bulunan Index’lerin kullanım istatistikleri

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')

 

Şu şekilde bir sorgu sonucu dönecektir.

1


Bu DMV yi kullanarak kullanılmayan Index raporuda çekebiliriz. Bu şekilde kullanılmayan index’leri sorgulayıp drop edip etmeme konusunu gündeme getirebiliriz.

Kullanılmayan Index Raporu

Aşağıdaki sorgu AdventureWorks’ta bulunan index’lerden kullanılmayanları getirecektir.

Select object_name(i.object_id) as objectName,
	i.name as indexname, 
	i.index_id
from sys.indexes i 
join sys.objects o on i.object_id = o.object_id 
where objectproperty(o.object_id,'IsUserTable') = 1 
	and i.index_id NOT IN (select s.index_id 
				from sys.dm_db_index_usage_stats s 
				where s.object_id=i.object_id 
					and i.index_id=s.index_id and database_id = DB_ID('AdventureWorks') ) 
order by objectname,i.index_id,indexname asc

 

2


Çok Nadir Kullanılan Index’lerin Raporu

Bu sorgumuzda seek,scan,lookup ve update değerlerine bakarak çok az kullanılan index’leri raporlayacağız. Bu 4 değerin toplamına göre sorgumuzu ASC olarak dizip en az değeri olanlar çok nadir kullanılan index’lerdir diye değerlendireceğiz.

select object_name(iu.object_id) as objectname, 
	i.name as indexname, 
	i.index_id, 
	user_seeks, 
	user_scans, 
	user_lookups, 
	user_updates 
from sys.dm_db_index_usage_stats iu 
join sys.indexes i on i.object_id = iu.object_id and i.index_id = iu.index_id
where database_id = DB_ID('AdventureWorks') and objectproperty(iu.object_id,'IsUserTable') = 1 
order by (user_seeks + user_scans + user_lookups + user_updates) asc

 

3


Sizde sistemlerinizde bu sorguları kullanarak index kullanım istatistiklerine bakabilir, kullanılmayan index leri sorgulayarak drop edip etmeme konusunu gündeme getirebilirsiniz.

 

İ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 da kullanılan Heap, Clustered yada NonClustered Index’ler kullanıldıkça fragmante olurlar. Bu da Index’in performansını olumsuz etkiler. Belirli aralıklarla fragmante olan bu indexleri bulup drop-create, ReOrganize yada Rebuild işlemleriyle fragmante oranlarının düşürülmesi gerekmektedir. Bugünkü yazımızda Index’lerin fragmante oranlarını nasıl sorgulayacağımızı işliyor olacağız.

[more]

Index’lerin fragmante oranlarına sys.dm_db_index_physical_stats DMF’si ile bakılabilir.

select * from sys.dm_db_index_physical_stats(DB_ID('AdventureWorks'),null,null,null,null)

 

Bu DMF 5 parametre almaktadır.

  • database_id : İstenilen bir database üzerindeki index’leri sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün database lerde yapılır.
  • object_id : İstenilen bir table üzerindeki index’leri sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün table larda yapılır.
  • index_id : İstenilen bir index’i sorgulamak için bu parametre kullanılabilir. NULL verilirse kontrol bütün index lerde yapılır.
  • partition_id : İstenilen index’in istenilen bir partition’ı sorgulanmak istendiğinden bu parametre kullanılabilir. NULL verilirse kontrol bütün partition’larda yapılır.
  • mode : İşlem sonucunun detayı için kullanılabilir. DEFAULT, NULL, LIMITED, SAMPLED, or DETAILED değerlerini alabilir. NULL olarak geçilirse LIMITED mode kullanılır.

Sorgu sonucunda gelen result set te bizi ilgilendiren kolonlar ve açıklamaları şu şekildedir.

  • database_id : Index’in bulunduğu table yada view in bulunduğu database in ID si.
  • object_id : Index’in bulunduğu table yada view in ID si. OBJECT_NAME() function’ı ile bu değeri kullanarak obje’nin adını alacağız.
  • index_id : Index’in Id’si.Eğer heap ise bu değer 0’dır.
  • partition_number : Partition number’ı verir. 1 ise non-partitioned index’tir.
  • index_type_desc : Index’in tipi. Heap, Clustered Index vs.
  • avg_fragmentation_in_percent : Index’teki fragmante oranını verir. Best practise olarak bu değerin %10 dan fazla olduğu index’lerde operasyon yapılması önerilir.
  • page_count : Index’in toplam kaç page’ten oluştuğunu verir.
  • record_count : Index’teki toplam kayıt sayısını verir.

Bu teorik bilgilerden sonra ilk sorgumuzu çekelim.


AdventureWorks DB sinde bulunan index’lerin fragmante oranları

select * from sys.dm_db_index_physical_stats(DB_ID('AdventureWorks'),null,null,null,null)

 

Şimdi biraz daha detaylı bir rapor çekelim. Best practise olarak %10 dan fazla fragmante olmuş index’ler de operasyon yapılmasının önerildiğini söylemiştik. Şimdi bu sorguyu nasıl çekeceğinize bakalım.

AdventureWorks DB sinde bulunan %10 dan fazla fragmante olmuş index ler

select object_name(ps.object_id) as [name], 
	ps.index_id,
	ps.avg_fragmentation_in_percent, 
	ps.fragment_count,
	ps.avg_fragment_size_in_pages, 
	ps.page_count, 
from sys.dm_db_index_physical_stats(DB_ID(),null,null,null,null) ps
where ps.avg_fragmentation_in_percent > 10
order by ps.avg_fragmentation_in_percent desc

 

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

1


İlk paragrafta belirttiğim gibi bu index ler üzerinde fragmante yi azaltabilmek için drop-create,reorganize ve rebuild işlemleri yapılabilir. Bu konu için daha sonra çok detaylı bir yazı hazırlayacağım. Şu anda sadece belirli komutlar ile fragmante oranlarının düşürüldüğünü bilmemiz yeterli.

Sizde sistemlerinizde bulunan index’leri belirli maintenance job lar ile kontrol ettirip Fragmante oranları %10 dan fazla olanlar üzerinde operasyonlar yapabilirsiniz. Bu sorgulama için yukarıdaki script i kullanabilirsiniz.

 

İ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