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

Recently one of my customer needs to query all tables’ sizes and row counts in order to track these information in a table periodically and determine the growth trend.

[more]

Here is the script;

create table #tmp1 (
      Servername SysName,
      DBName SysName,
      object_id int,
      ScheamaName sysName,
      TableName sysName,
      TableSize_KB int,
      row_count int
)

create table #tmp2 (
      Servername SysName,
      DBName SysName,
      object_id int,
      ScheamaName sysName,
      TableName sysName,
      row_count int
)

exec sp_msforeachdb 'use ?;
if DB_ID()>4 begin
      insert #tmp1
      select @@ServerName,DB_Name(),ps.object_id,
                  schema_name(o.schema_id) as SchemaName,
                  o.name as TableName,
                  SUM(ps.reserved_page_count)*8 as TableSize_KB,
                  0 as row_count
      from sys.dm_db_partition_stats ps
      join sys.objects o on o.object_id = ps.object_id
      where o.type=''U''
      group by ps.object_id,o.schema_id, o.name

      insert #tmp2
      select @@ServerName,DB_Name(),ps.object_id,
                  schema_name(o.schema_id) as SchemaName,
                  o.name as TableName,    
                   sum(ps.row_count) as 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)
      group by ps.object_id,o.schema_id, o.name
end
';


update t1
set t1.row_count=t2.row_count
from #tmp1 t1
left join #tmp2 t2 on 
      t2.ServerName=t1.ServerName and
      t2.DBName=t1.DBName and
      t2.object_id=t1.object_id


select * from #tmp1
order by TableSize_KB desc

drop table #tmp1
drop table #tmp2

And a sample result like that;

image

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


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


SQL Server 2011 Denali ile beraber gelen yeni objeler sayesinde bir T-SQL komutunun ya da SP,view gibi objelerin çalıştırıldığı zaman döndüreceği kolonların listesini ve veri tiplerini almak mümkün. Ayrıca gene bu objeleri, bir TSQL script içerisindeki parametrelerin tiplerini öğrenmek için de kullanabiliriz.

[more]

Bu objelerin tamamının en büyük özelliği kodu çalıştırmadan bu bilgileri veriyor olmalarıdır. Örneğin şöyle bir senaryo düşünelim.

Uzun çalışan bir TSQL sorgumuz olduğunu düşünelim. Bu sorgu sonucunu da çalıştırdıktan sonra bir temporary tabloya atmak istiyoruz ama tablonun tanımını yani hangi kolon ve kolon tipleri şeklinde oluşturmamız gerektiğini bilmiyoruz.

Klasik yöntemde “into” syntax’ı ile sorguyu çalıştırıp tabloyu create edebiliriz. Ya da artık SQL Server 2011 Denali ile gelen sp_describe_first_result_set ve sys.dm_exec_describe_first_result_set objelerini kullanabiliriz.

sp_describe_first_result_set

Parametre olarak verilen TSQL komutunun döndüreceği ilk result set’in metadata bilgilerini verir.

Örneğin aşağıdaki komut sys.tables system view’ine çekilecek select işlemi sonucunda dönecek kolonların bilgisini döndürür.

exec sp_describe_first_result_set N'SELECT * FROM sys.indexes'

 

1

İlk result set’in metadata bilgilerinin döneceği yukarıda belirtmiştim. Yani aşağıdaki gibi önce sys.tables’a daha sonra sys.columns’a select çeken bir sorgu için gene sys.tables’ın metadata bilgileri dönecektir.

exec sp_describe_first_result_set N'SELECT * FROM sys.indexes; SELECT * FROM sys.columns'

 

sys.dm_exec_describe_first_result

sp_describe_first_result_set ile aynı işi yapan bu DMV, ilk result set’in metadata bilgilerini almak için kullanılabilir.

Kullanımı aşağıdaki gibidir.

select * from sys.dm_exec_describe_first_result_set ('SELECT * FROM sys.indexes', null, 0)

 

2

Bu DMV’nin sp_describe_first_result_set system procedure’ünden en büyük farkı bir tablo gibi sorgulanabilmesidir. Örneğin aşağıdaki gibi bir kullanımımız olabilir.

select dfr.name,dfr.system_type_id,dfr.system_type_name,max_length,precision,scale
from sys.dm_exec_describe_first_result_set ('SELECT * FROM sys.indexes', null, 0) dfr

 

3

 

sys.dm_exec_describe_first_result_set_for_object

Yukarıda anlattığım 2 obje, parametre olarak TSQL ifadesi alıyordu. Bu objelerle aynı işi yapan sys.dm_exec_describe_first_result_set_for_object DMV’si ile parametre olarak object_id alıp bu object_id’den dönen ilk result set’in metadata bilgisini dönmektedir.

Kullanımı aşağıdaki gibidir.

CREATE PROC dbo.sp_mdSample 
AS
	SELECT * FROM sys.indexes

GO

select *
from sys.dm_exec_describe_first_result_set_for_object (object_id('sp_mdSample'), 0)
GO

4

sp_describe_undeclared_parameters

Yukarıda anlattığım objeler ile parametre olarak verilen TSQL komutu ya da Obje çalıştırıldığında dönecek sonucun metadata bilgilerinin nasıl alınacağına bakmıştık. sp_describe_undeclared_parameters sistem SP’si ise TSQL komutunda geçen parametrelerin metadata bilgilerini öğrenmek için kullanılabilir.

Örneğin aşağıdaki sorguda @param1 ve @param2 adında 2 parametre kullanılmakta. Bu parametrelerin tiplerini öğrenmek için şöyle bir komut kullanabiliriz.

exec sp_describe_undeclared_parameters 
   @tsql = N'select * from sys.columns where name = @Param1 and max_length = @Param2'

 

5

 

İ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 2011 Denali ile gelen yeni DMV’lerden biri sys.dm_server_services DMV’si. Bu DMV ile SQL Server Agent, Database Service gibi servislerin startup seçenekleri, process id’leri, en son ne zaman başlatıldıkları gibi bilgilerin öğrenilmesi mümkün.

[more]

sys.dm_server_services DMV’si ile alınabilecek bilgilerin bir kısmı şu şekilde;

  • servicename : Kurulu olan servisin adı.
  • startup_desc : Servisin otomatik başlama tipi.
  • status_desc : Servisin şu anki durumu.
  • process_id : Servisin Windows Process ID’si
  • last_startup_time : Servisin en son çalıştırıldığı zaman.
  • service_account : Servisi çalıştırıan kullanıcının adı.
  • filename : Servis exe’sinin bulunduğu dizin ve dosya adı.

Sorguyu şu şekilde çekebiliriz.

select servicename, startup_type_desc, status_desc
	,process_id, last_startup_time, service_account
	,filename
from sys.dm_server_services

 

Yukarıdaki sorgu benim client makinamda şu sonuçları vermekte.

1

Bu DMV’yi özellikle service account’unu ve servisin process ID’sini öğrenmek için kullanabiliriz. Denali’den önceki sürümlerde bu bilgiyi almak için configuration manager’dan bağlantı kurmamız gerekmekteydi.

 

İ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 2011 Denali ile gelen yeni DMV’lerden biri sys.dm_os_windows_info DMV’si. Bu DMV ile veritabanı sunucusunun, release, service pack seviyesi gibi işletim sistemi (Operatin System) bilgilerinin öğrenilmesi mümkün.

[more]

sys.dm_os_windows_info DMV’si ile alınabilecek bilgiler şu şekildedir;

  • windows_release : İşletim sisteminin versiyon numarasını verir. Tüm versiyon numarası listesine şu adresten bakabilirsiniz.
  • windows_service_pack_level : İşletim sisteminin service pack seviyesini verir.
  • windows_sku : İşletim Sistemi Stock Keeping Unit (SKU) ID’sini verir.
  • os_language_version : İşletim sistemi dil numarasını verir. Tüm dil numarası listesine şu adresten bakabilirsiniz.

Sorguyu şu şekilde çekebiliriz.

select * from sys.dm_os_windows_info 

 

Yukarıdaki sorgu benim client makinamda şu sonuçları vermekte.

1

windows_release kolonunda 6.1 değeri Windows7 işletim sistemini, os_language_version kısmındaki 1033 değeri ise English - United States dil seçeneğini ifade etmektedir.

 

İ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 sorgu performans analizi yapılırken en çok kullanılan DMV’lerden biri olan sys.dm_exec_query_statsDMV’sine SQL Server 2011 Denali ile beraber 4 yeni kolon eklendi.

[more]

Yeni eklenen bu kolonlar ile sorgu sonucu dönen satır sayısı istatistiklerini öğrenmek mümkün.

Eklenen kolonlar ve açıklamaları şu şekilde;

  • total_rows : Sorgu sonucunda dönen toplam satır sayısı. Bu değer kümülatif olarak hesaplanmaktadır. Yani sorgu her çalıştığında 5 satır döndürüyor ve toplamda 10 kez çalıştırıldıysa bu kolonda 50 değeri yazacaktır.
  • last_rows : Sorgunun en son çalışmasında kaç satır döndürdüğünü gösterir. Yukarıdaki örnek için bu kolonda 5 değeri yazacaktır.
  • min_rows : Sorgunun şu ana kadarki çalıştırılmalarında en az kaç kayıt döndürdüğü bilgisini verir.
  • max_rows : Sorgunun şu ana kadarki çalıştırılmalarında en fazla kaç kayıt döndürdüğü bilgisini verir.

Aşağıdaki sorgu ile SELECT sorgularının döndürdüğü satır sayılarının istatistiklerini analiz edebiliriz.

SELECT qs.execution_count,
    SUBSTRING(qt.text,qs.statement_start_offset/2 +1, 
                 (CASE WHEN qs.statement_end_offset = -1 
                       THEN LEN(CONVERT(nvarchar(max), qt.text)) * 2 
                       ELSE qs.statement_end_offset end -
                            qs.statement_start_offset
                 )/2
             ) AS query_text, 
     qt.dbid, dbname= DB_NAME (qt.dbid), qt.objectid, 
     qs.total_rows, qs.last_rows, qs.min_rows, qs.max_rows
FROM sys.dm_exec_query_stats AS qs 
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt 
WHERE qt.text like '%SELECT%' 
ORDER BY qs.execution_count DESC

 

1

 

İ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 2011 Denali ile gelen yeni DMV’lerden biri sys.dm_os_volume_stats DMV’si. Bu DMV ile data ve log file’larının bulunduğu sürücülerin toplam alan, boş alan gibi bilgilerinin öğrenilmesi mümkün.

[more]

SQL Server 2011 Denali’den önceki sürümlerde de sürücülerin doluluk-kullanım oranlarını öğrenmemiz mümkündü. Hatta bu konu ile ilgili şu makaleyi yazmıştım. Makaleyi incelerseniz eğer doluluk oranları için uzun bir kod kullanmış ve Ole Automation Procedures özelliğini enable etmiştik.

2011 Denali ile beraber bu isteğimizi tek bir DMV’e sorgu çekerek gerçekleştirebiliriz.

Örneğin sunucumuzda bulunan sürücülerden içinde DB olan sürücülerin doluluk-kullanım oranları için şu sorguyu kullanabiliriz.

SELECT volume_mount_point
	   , total_bytes/1024/1024 as ToplamMB_MB
	   , (total_bytes-available_bytes)/1024/1024 as KullanilanAlan_MB
	   , available_bytes/1024/1024 as BosAlan_MB
FROM sys.master_files AS f
CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id)
GROUP BY volume_mount_point, total_bytes, available_bytes

1

Data ve Log dosyalarının bulunduğu sürücülerde yer kalmadığı zaman veritabanının hizmet veremez duruma gelme ihtimalinden dolayı sürücülerdeki boş alanların sürekli monitor edilmesinde fayda vardır.

 

İ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


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

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

[more]

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

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

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

 

İyi Çalışmalar

Turgay Sahtiyan

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


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

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

[more]

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

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

 

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

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

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

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

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

EXEC sp_recompile N'dbo.sp_1'

 

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

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

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

ReCompilation Olaylarının İzlenmesi

SQL Server Profiler - SQL:StmtRecompile - SP:Recompile

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

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

Windows Performance Counter - SQL Re-Compilation/sec

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

Activity Monitor

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

1

Sys.dm_exec_query_stats – plan_generation_num

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

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

 

2

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

Sonuç

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

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


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

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

[more]

Forwarded Record Nedir?

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

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

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

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

Forwarded Record Nasıl Oluşur?

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

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

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

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

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

 

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

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

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

 

1

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

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

 

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

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

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

 

Tekrar tablonun page’lerine bakıyorum.

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

 

2

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

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

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

 

3

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

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

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

 

4

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

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

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

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

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

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

NonClustered Index İçeren Heap Tablolarda Forwarded Record

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

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

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

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

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

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

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

create nonClustered Index IX_1 on ForwardedRecordNCIndex (IntCol1)

 

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

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

 

5

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

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

 

6

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

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

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

 

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

7

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

NonClustered Index İçermeyen Heap Tablolarda Forwarded Record

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

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

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

Forwarded Record’un Performansa Etkisi Nedir?

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

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

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

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

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

Hangi Tablolarımda Forwarded Record Var?

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

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

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

 

8

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

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

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO

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

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

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

DECLARE @IndexID int

OPEN HEAP_CURS
FETCH NEXT FROM HEAP_CURS INTO @IndexID

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

    FETCH NEXT FROM HEAP_CURS INTO @IndexID
END

CLOSE HEAP_CURS
DEALLOCATE HEAP_CURS

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

 

Sonuç şuna benzer olacaktır.

9

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

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

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

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

Forwarded Record Nasıl Düzeltilir?

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

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

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

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

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

 

10

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

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

 

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

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

 

11

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

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

12

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

Sonuç

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

 

İyi Çalışmalar

Turgay Sahtiyan

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


Geçenlerde SQL Server 2005 Performance Dashboard raporlarını incelerken ana ekranda olan bir rapor dikkatimi çekti. CPU utilization’ı görebildiğimiz bu rapor da dakika dakika CPU kullanımını görebilmekteyiz.

Hemen ilk aklıma gelen Performance Counter ile bu bilginin alındığıydı. Fakat daha sonra düşündüğümde bu raporu almadan herhangi bir performance counter toplamadığımdı. Bu durumda bu rapor bir şekilde geriye dönük olarak CPU bilgilerini getirebilmekte, çok büyük ihtimal de DMV üzerinden bu bilgiye erişmekte ki bu beni oldukça şaşırttı ve heyecanlandırdı.

[more]

Daha sonra konu üzerinde inceleme yapınca ve MS’ten Işıl’ın da yardımlarıyla CPU utilization bilgilerinin sys.dm_os_ring_buffers DMV’sinden alındığını gördük.

sys.dm_os_ring_buffers DMV’si CPU utilization bilgilerini dakika dakika olarak tutmakta. Bu DMV vasıtasıyla son 256 veriye erişebilmekteyiz. Yani bu DMV’den yaklaşık olarak son 4 saatin dakika dakika CPU utilization bilgilerini alabiliriz.

Sorgumuz aşağıdaki gibi. Sorgu hem 2005 hem de 2008 sunucularda kullanılabilmekte. Tek farklılık 2 ve 3. satırlarda. Zaten sorguda ilgili notuda düşmüş durumdayım.

declare @ts_now bigint
select @ts_now =  cpu_ticks / (cpu_ticks/ms_ticks) from sys.dm_os_sys_info --SQL Server 2008
--select @ts_now = cpu_ticks / convert(float, cpu_ticks_in_ms) from sys.dm_os_sys_info --SQL Server 2005
select top 50 record_id,
    dateadd(ms, -1 * ((@ts_now - [timestamp])), GetDate()) as EventTime, 
    SQLProcessUtilization,
    SystemIdle,
    100 - SystemIdle - SQLProcessUtilization as OtherProcessUtilization
from (
    select 
          record.value('(./Record/@id)[1]', 'int') as record_id,
          record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') as SystemIdle,
          record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') as SQLProcessUtilization,
          timestamp
    from (
          select timestamp, convert(xml, record) as record 
          from sys.dm_os_ring_buffers 
          where ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
          and record like '%%') as x
    ) as y 
order by record_id desc

Örneğin bir sunucumdaki son 60 dakikalık CPU utilization raporu aşağıdaki gibi.

cu1

Sorgu sonucunda gelen kolonlar için kısa bilgiler vermemiz gerekirse;

  • SQLProcessUtilization : SQL Server Exe’nin kullandığı CPU miktarı
  • SystemIdle : Boşta olan CPU miktarı
  • OtherProcessUtilization : Diğer exe’lerin kullandığı CPU miktarı.

 

İ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 Database Mirroring’e Auto Page Repair özelliği eklendi. Bu özellik ile beraber partner’ler (Mirroring yapılan sunucular birbirinin partner’idir) bozuk(corrupted) olan data page’leri birbirlerinden otomatik olarak recover etmeye çalışmaktadır. Herhangi bir page’i okuyamayan partner sunucu diğer sunucudan bu page’in düzgün olan halini istemekte, ve bu istek başarıyla sonuçlanırsa düzgün olan page corrupted yani bozuk olan page ile replace edilmektedir.

[more]

Auto Page Repair Yapılabilecek Page Tipleri

Auto Page Repair ile aşağıdaki page tipleri recover edilemez.

  • File header page (page ID 0).
  • Page 9 (the database boot page).
  • Allocation pages: Global Allocation Map (GAM) pages, Shared Global Allocation Map (SGAM) pages, and Page Free Space (PFS) pages.

Bunun haricinde kalan page’ler Auto Page Repair ile partner’ler arasında otomatik olarak recover edilebilmektedir.

Auto Page Repair İşlem Adımları

Auto Page Repair işlemi aşağıdaki adımlardan oluşmaktadır.

  1. Principal database üzerinde bir data page’de okuma hatası gerçekleştiğinde, bu page’in ID bilgileri ve error mesajı suspect_pages table’ına yazılır. Daha sonra principal server bu page’in bir kopyasını secondary yani mirror sunucudan ister.Bu isteğin içinde page ID ve LSN bilgisi bulunur. Bu istekle beraber ilgili page restore pending statu’sunu çekilir ve bu sayede auto page repair süresi boyunca ilgili page’e erişimler engellenir. Eğer page’e bir istek gelirse 829 (restore pending) hata mesajı döndürülür.
  2. İstek mirror server’a eriştiğinde , mirror server ilgili page’e erişmeye çalışır. Eğer page’e erişimde bir sorun olmazsa mirror server ilgili page’i principal server’a gönderir. Eğer erişimde sıkıntı çıkarsa mirror server principal server’a hata mesajı döndürür.
  3. Principal server page’i alır ve işlemeye başlar. Yani mirror server’dan aldığı page’i kendi üstünde bulunan corrupted (bozuk) page ile replace eder.
  4. Auto page Repair işlemi tamamlandığında daha önce 1 no’lu adımda suspect_pages table’ına alınan ilgili page’in kaydı bu table’dan kaldırılır ve page erişime açılır.

Yapılan Auto Page Repair İşlemlerini Monitor Etmek

Auto Page Repair işleminin otomatik olarak gerçekleştiğini söyledik. Peki ne zaman hangi page auto repair oldu nasıl kontrol edebiliriz.

Bunun için SQL Server 2008 ile beraber yeni bir DMV gelmiş durumda.

image

sys.dm_db_mirroring_auto_page_repair DMV’si ile geçmişe dönük olarak gerçekleşmiş Auto Page Repair işlemleri sorgulanabilir.

Bu DMV her bir Auto Page Repair işlemi bir sonuç döndürür. Her bir database için maksimum 100 adet kayıt saklanır. Bu rakama ulaşıldığında yeni gelen kayıt eski kayıtlardan birinin üzerine yazılır.

Sorgu sonucunda gelen bilgilere bakacak olursak;

  • database_id = Repair işleminin yapıldığı page’in bulunduğu database’in ID’si
  • file_id = Page’in bulunduğu file’in ID’si
  • page_id = Page’in ID’si
  • error_type = Principal server’ın hatalı olan page üzerinde aldığı hata
  • page_status = Repair işleminin şu an için bulunduğu status
  • modification_time = Status’ta yapılan en son değişikliğin zamanı

 

Bugünkü yazımda Mirroring yapısına 2008 ile beraber eklenen Auto Page Repair işleminin ne olduğu, ne şekilde çalıştığı ve nasıl monitor edilebileceği üzerinde durduk. High Avaibility açısından önemli bir çözüm olan DB Mirroring’e eklenen bu güzel özelliği mirror sunucularınızda periyodik olarak monitor etmenizin gerekliliğini vurgulayarak yazımı 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


Sys.dm_exec_query_stats gibi DMV’leri anlatırken ilgili çalışan kod parçacığının sys.dm_exec_sql_text ve start-end offset’ler kullanılarak nasıl okunacağını görmüştük. Bilgilerinizi tazelemek için aşağıdaki makaleleri kullanabilirsiniz.

SQL Server – Her Gün 1 DMV - Gün 2 - sys.dm_exec_sql_text - sys.dm_exec_query_plan

SQL Server – Her Gün 1 DMV - Gün 4 - sys.dm_exec_query_stats ile Query İstatistikleri

Bugünkü makalemde daha yeni yazdığım, dumanı üzerinde tüten, statement text’i döndüren function’ımı paylaşıyor olacağım.:)

[more]

Function şu şekilde;

USE master
GO
-- =============================================
-- Author:		Turgay Sahtiyan
-- Create date: 29.12.2010
-- Description:	Get Statement Text from Given Sql_Handle
-- =============================================
CREATE FUNCTION [dbo].[GetStatementText] 
(
	@sqlHandle varbinary(64), @stmtStart int, @stmtEnd int
)
RETURNS varchar(max)
AS
BEGIN
	DECLARE @Result varchar(max)
    select @stmtStart=isnull(@stmtStart,0)
    select @stmtEnd=isnull(@stmtEnd,-1)
    SELECT @Result = SUBSTRING(text, (@stmtStart/2)+1, 
        ((CASE @stmtEnd
          WHEN -1 THEN DATALENGTH(text)
         ELSE @stmtEnd
         END - @stmtStart)/2) + 1)
    from sys.dm_exec_sql_text(@sqlHandle)
    
	RETURN isnull(@Result,'')

END

 

Parametreler;

  • @sqlHandle : İlgili sql_handle
  • @stmtStart : Statement’ın başlangıç offset’i
  • @stmtStart : Statement’ın bitiş offset’i

Kullanımı;

select master.dbo.GetStatementText( 0x03000500462D95441BA30B01A69D00000100000000000000, 290, 856) as StatementText

 

1

sys.dm_exec_query_stats ile kullanımı;

2

 

İ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


28 Aralık Salı günü yapmış olduğum webcast’te kullandığım powerpoint dosyasını ve scriptleri sizinle paylaşmak istiyorum.

İlerleyen günlerde webcast’i video formatında da paylaşıyor olacağım.

[more]

PowerPoint Dosyası

http://www.turgaysahtiyan.com/file.axd?file=2011%2f01%2fWebcast_DMV_DMF.pptx

Sunumda Kullanılan Scriptler

--Tüm DMV-DMF Listesi
select name, type_desc 
from sys.all_objects 
where name like 'dm_%'
order by name

-------------------------------------------------------------
-------------------------------------------------------------

--DMW’ler ile Session(Process) Kontrolleri
select DB_NAME(er.database_id) as DBName,
	es.login_name,
	es.host_name,
	st.text,
    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
from sys.dm_exec_requests er
left join sys.dm_exec_sessions es on es.session_id=er.session_id
cross apply sys.dm_exec_sql_text(er.sql_handle) st
cross apply sys.dm_exec_query_plan(er.plan_handle) qp
where er.session_id>50 and er.session_id!=@@SPID

-------------------------------------------------------------
-------------------------------------------------------------

--En Çok CPU Tüketen Ilk 50 Sorgu
--select * from sales.Individual
--select * from sales.SalesOrderDetail

select 
     q.[text],
     SUBSTRING(q.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(q.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2) + 1) AS statement_text,        
     qs.last_execution_time,
     qs.execution_count,
     qs.total_worker_time/1000000 as total_cpu_time_sn,
     qs.total_worker_time/qs.execution_count/1000 as avg_cpu_time_ms,
     qp.query_plan,
     DB_NAME(q.dbid) as database_name,
     q.objectid,
     q.number,
     q.encrypted
from 
    (select top 50 
          qs.last_execution_time,
          qs.execution_count,
		  qs.plan_handle, 
          qs.total_worker_time,
          qs.statement_start_offset,
          qs.statement_end_offset
    from sys.dm_exec_query_stats qs
    order by qs.total_worker_time desc) qs
cross apply sys.dm_exec_sql_text(plan_handle) q
cross apply sys.dm_exec_query_plan(plan_handle) qp
order by qs.total_worker_time desc

-------------------------------------------------------------
-------------------------------------------------------------

--IO tüketimi için table scan yapalIm.
--select * from Sales.Individual

--En Çok IO Tüketen Ilk 50 Sorgu
select 
     q.[text],
     SUBSTRING(q.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(q.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2) + 1) AS statement_text,        
     qs.last_execution_time,
     qs.execution_count,
     qs.total_logical_reads as total_logical_read,
     qs.total_logical_reads/execution_count as avg_logical_read,
     qs.total_worker_time/1000000 as total_cpu_time_sn,
     qs.total_worker_time/qs.execution_count/1000 as avg_cpu_time_ms,
     qp.query_plan,
     DB_NAME(q.dbid) as database_name,
     q.objectid,
     q.number,
     q.encrypted
from 
    (select top 50 
          qs.last_execution_time,
          qs.execution_count,
		  qs.plan_handle, 
          qs.total_worker_time,
          qs.total_logical_reads,
          qs.statement_start_offset,
          qs.statement_end_offset
    from sys.dm_exec_query_stats qs
    order by qs.total_worker_time desc) qs
cross apply sys.dm_exec_sql_text(plan_handle) q
cross apply sys.dm_exec_query_plan(plan_handle) qp
order by qs.total_logical_reads desc

-------------------------------------------------------------
-------------------------------------------------------------

--Prosess Beklemelerinin Analizi
SELECT getdate() as 'Run_Time' --script in çalIþma zamanI
    , wait_type --wait type
	, CAST(wait_time_ms / 1000. AS DECIMAL(12, 2)) AS wait_time_s --saniye cinsinden bekleme zamanI
	, CAST(100. * wait_time_ms / SUM(wait_time_ms) OVER() AS DECIMAL(12, 2)) AS pct --toplam beklemeye oranI
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN ('BROKER_TASK_STOP','Total','SLEEP','BROKER_EVENTHANDLER',
	  'BROKER_RECEIVE_WAITFOR','BROKER_TRANSMITTER','CHECKPOINT_QUEUE',
	  'CHKPT,CLR_AUTO_EVENT','CLR_MANUAL_EVENT','KSOURCE_WAKEUP','LAZYWRITER_SLEEP',
      'LOGMGR_QUEUE','ONDEMAND_TASK_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','RESOURCE_QUEUE',
      'SERVER_IDLE_CHECK','SLEEP_BPOOL_FLUSH','SLEEP_DBSTARTUP','SLEEP_DCOMSTARTUP',
      'SLEEP_MSDBSTARTUP','SLEEP_SYSTEMTASK','SLEEP_TASK','SLEEP_TEMPDBSTARTUP',
      'SNI_HTTP_ACCEPT','SQLTRACE_BUFFER_FLUSH','TRACEWRITE','WAIT_FOR_RESULTS',
      'WAITFOR_TASKSHUTDOWN','XE_DISPATCHER_WAIT','XE_TIMER_EVENT','WAITFOR')
ORDER BY 4 DESC,3 DESC

-------------------------------------------------------------
-------------------------------------------------------------

--Disklerin IO Response Time’larI
select db_name(mf.database_id) as DatabaseName, mf.name as FileLogicalName,
	   io_stall_read_ms/num_of_Reads as ReadResponseTime,
	   io_stall_write_ms/num_of_writes as WriteResponseTime,
	   io_stall/(num_of_reads+num_of_writes) as ResponseTime,
       num_of_reads,
       num_of_bytes_read, 
       io_stall_read_ms,        
	   num_of_writes, 
	   num_of_bytes_written, 
	   io_stall_write_ms, 
	   io_stall, 
	   size_on_disk_bytes
from sys.dm_io_virtual_file_stats(DB_ID('AdventureWorks'),null) as divfs
join sys.master_files as mf 
	on mf.database_id = divfs.database_id 
		and mf.file_id = divfs.file_id
		
-------------------------------------------------------------
-------------------------------------------------------------

--Þu an Diskte Bekleyen IO Var mI?
select
   fs.database_id as database_id,
   db_name(fs.database_id) as database_name,
   mf.name as logical_file_name,
   ip.io_type,
   ip.io_pending_ms_ticks,
   ip.io_pending
from sys.dm_io_pending_io_requests ip
join sys.dm_io_virtual_file_stats(null, null) fs on fs.file_handle = ip.io_handle
left join sys.master_files mf on mf.database_id = fs.database_id
	and mf.file_id = fs.file_id		


-------------------------------------------------------------
-------------------------------------------------------------

--Index’lerin Fragmante OranlarI	
select object_name(ps.object_id) as [name], 
	ps.index_id,
	i.name as IndexName,
	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
left join sys.indexes i on i.object_id=ps.object_id and i.index_id=ps.index_id
where ps.avg_fragmentation_in_percent > 10
order by ps.avg_fragmentation_in_percent desc

-------------------------------------------------------------
-------------------------------------------------------------

--Index’lerin KullanIm 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')

--KullanIlmayan Index Raporu
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

-------------------------------------------------------------
-------------------------------------------------------------

--Eksik Index’lerin SorgulanmasI
--select * from person.address where PostalCode='98011'
--select * from person.address where PostalCode='98011' and City='Bothell'

select DB_NAME(id.database_id) as databaseName,
	id.statement as TableName,
	id.equality_columns,
	id.inequality_columns,
	id.included_columns,
	gs.last_user_seek,
	gs.user_seeks,
	gs.last_user_scan,
	gs.user_scans,
	gs.avg_total_user_cost * gs.avg_user_impact * (gs.user_seeks + gs.user_scans) 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

-------------------------------------------------------------
-------------------------------------------------------------

--sys.dm_db_partition_stats ile Tablo Bilgileri
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

--Table'larIn kayIt sayIlar
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

--Table'larIn boyutlarI
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


-------------------------------------------------------------
-------------------------------------------------------------

--sys.dm_os_sys_info ile Server Bilgileri
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

--SQL Server UpTime - 1.Yöntem
select DATEDIFF(MINUTE, sqlserver_start_time, CURRENT_TIMESTAMP) as SQLServerUpTime_minute
from sys.dm_os_sys_info

--SQL Server UpTime - 2.Yöntem
select (ms_ticks-sqlserver_start_time_ms_ticks)/1000/60 as SQLServerUpTime_minute
from sys.dm_os_sys_info

-------------------------------------------------------------
-------------------------------------------------------------

--Cached Plan KaydI oluþturmak için aþaðIdaki SP'leri çalIþtIralIm
--exec dbo.uspGetBillOfMaterials 15,'01.01.2010'
--exec dbo.uspGetEmployeeManagers 11 

--Cache’lenen Stored Procedure’lerin Query PlanlarI
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'

 

İ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


Geçenlerde bana sorulan güzel bir soru üzerine bu makaleyi yazmaya karar verdim. Soru şu şekildeydi;

“En çok CPU tüketen sorgularımı kontrol etmek için sys.dm_exec_query_stats DMV’sine sql_handle kolonu üzerinden sys.dm_exec_sql_text’i join’lediğim zaman bazı satırların database bilgisi null gelmekte. Bunun sebebi nedir?”

[more]

Soruda ne demek istendiğine bir bakalım.

Daha önce şu makalemde anlattığım “en çok CPU tüketen ilk 50 sorgu” sorgusunu instance’ımda çalıştırdığımda

1

Bazı satırların database_name kolonu boş gelmekte. Bunun sebebi nedir?

Yaptığım araştırmalar neticesinde bunun sebebinin sql_handle’dan kaynaklandığı sonuca vardım.

SQL Server Database Engine farklı database’lerde çalıştırılmış aynı Ad-Hoc query’ler için aynı sql_handle’ı üretmekte. Yani sql_handle içinde hangi DB üzerinde çalıştırıldığı bilgisi tutulmamakta. Dolayısıyla sql_handle’ı kullanarak database_name bilgisini almaya çalıştığımızda bu bilgi tutulmadığı için null değer dönmekte.

SP kullanımında ise bir sıkıntı gözükmemekte. SP’ler için oluşturulmuş sql_handle’lardan database adına erişebilmekteyiz.

Bir örnek yaparak ne demek istediğimizi daha net hale getirelim.

Üzerinde çalışmak için 2 tane DB create ediyoruz.

--2 Test DB'si create edelim.
create database Deneme1
GO
create database Deneme2
GO

 

Her DB üzerinde 1 table ve 2 SP create ediyoruz. Bu SP’lerden ilkini normal şekilde execute edeceğiz, diğerini ise dynamic olarak execute edeceğiz ve dynamic execute’de herhangi bir farklılık olup olmadığını analiz etmeye çalışacağız.

--1. DB'de 1 tablo ve bu tabloya select çeken 2 tane SP
use Deneme1
GO
Create table TblDnm1 (Col1 varchar(10))
GO
Create Proc spDeneme1 @Col1 varchar(10)
as
  Select * from TblDnm1 where Col1=@Col1
GO
Create Proc spDeneme2 @Col1 varchar(10)
as
  Select * from TblDnm1 where Col1=@Col1
GO
--2. DB'de ayni islemler
use Deneme2
GO
Create table TblDnm1 (Col1 varchar(10))
GO
Create Proc spDeneme1 @Col1 varchar(10)
as
  Select * from TblDnm1 where Col1=@Col1
GO
Create Proc spDeneme2 @Col1 varchar(10)
as
  Select * from TblDnm1 where Col1=@Col1
GO

 

Her 2 DB’de de tabloya select, 1.SP’ye execute ve 2.SP’ye dynamic execute yapıyoruz.

--1.DB
Use Deneme1
GO
Select * from TblDnm1
GO
exec spDeneme1 'a'
GO
sp_executesql N'exec spDeneme2 ''a'''
GO

--2.DB
Use Deneme2
GO
Select * from TblDnm1
GO
exec spDeneme1 'a'
GO
sp_executesql N'exec spDeneme2 ''c'''
GO

 

Ve şimdi sys.dm_exec_query_stats DMV’sine bakıyoruz. DBName’i hem sys.dm_exec_sql_text’ten hem de sys.dm_exec_query_plan’dan almaya çalışıyoruz.

--sys.dm_exec_query_stats'i sorgulayalim.
select st.text,qs.sql_handle,qs.plan_handle 
   ,DB_NAME(st.dbid) as DBName_sql_text
   ,DB_NAME(qp.dbid) as DBName_query_plan
from sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) st
cross apply sys.dm_exec_query_plan(qs.plan_handle) qp
where (st.text not like '%select * from sys.dm_exec_query_stats%')
  and (st.text like '%Select * from TblDnm1%'
  or st.text like '%exec spDenem1%'
  or st.text like '%exec spDenem2%')

 

2

Gördüğünüz gibi farklı DB’lerde çalışan “Select * from TblDnm1” Ad-Hoc sorgusu için bire bir aynı sql_handle create edildi. Ve bu sorgular için ne sys.dm_exec_sql_text’ten ne de sys.dm_exec_query_plan’dan database adını ulaşamadık.

SP’lerde ise normal çalıştırılsa da dynamic olarak çalıştırılsa da bir sıkıntı olmadığını görüyoruz.

Peki Ad-Hoc query’ler için database adını öğrenebilmekten feragat mı edeceğiz. İşte burada yardımımıza sys.dm_exec_plan_attributes DMF’si yetişmekte.

Bu DMF’yi kullanarak istediğimiz sonuca erişebiliriz. Üstteki sorgunun sys.dm_exec_plan_attributes eklenmiş hali aşağıdadır.

select st.text,qs.sql_handle,qs.plan_handle 
   ,DB_NAME(st.dbid) as DBName_sql_text
   ,DB_NAME(qp.dbid) as DBName_query_plan
   ,DB_NAME(cast(isnull(pa.value,0) as int)) as DBName_plan_attributes
from sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) st
cross apply sys.dm_exec_query_plan(qs.plan_handle) qp
outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa
where (pa.attribute='dbid') 
  and (st.text not like '%select * from sys.dm_exec_query_stats%')
  and (st.text like '%Select * from TblDnm1%'
  or st.text like '%exec spDenem1%'
  or st.text like '%exec spDenem2%')

 

3

Gördüğünüz gibi Ad-Hoc query’ler için de database adı bilgilerine erişebildik.

Dolayısıyla daha önce şu makalemde yazdığım “En çok CPU tüketen ilk 50 sorgu” sorgusuda şu şekilde değişmiş oldu.

select
     q.[text],
     SUBSTRING(q.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(q.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2) + 1) AS statement_text,       
     qs.last_execution_time,
     qs.execution_count,
     qs.total_logical_reads as total_logical_read,
     qs.total_logical_reads/execution_count as avg_logical_read,
     qs.total_worker_time/1000000 as total_cpu_time_sn,
     qs.total_worker_time/qs.execution_count/1000 as avg_cpu_time_ms,
     qp.query_plan,
     COALESCE(DB_NAME(q.dbid),DB_NAME(CAST(pa.value as int))+'*') AS database_name,
     q.objectid,
     q.number,
     q.encrypted
from
    (select top 50
          qs.last_execution_time,
          qs.execution_count,
          qs.plan_handle,
          qs.total_worker_time,
          qs.total_logical_reads,
          qs.statement_start_offset,
          qs.statement_end_offset
    from sys.dm_exec_query_stats qs
    order by qs.total_worker_time desc) qs
cross apply sys.dm_exec_sql_text(plan_handle) q
cross apply sys.dm_exec_query_plan(plan_handle) qp
outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa
where (pa.attribute='dbid')
order by qs.total_logical_reads desc

 

4

Yanlarında * olan database adları sys.dm_exec_plan_attributes vasıtasıyla bulunan değerlerdir.

Son olarak yazımın başında create ettiğim DB’leri drop ederek yazımı noktalıyorum.

--create ettigimiz DB'leri drop ediyoruz.
drop database Deneme1
GO
drop database Deneme2
GO

 

İ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


28 Aralık Salı günü yapacağım webcast’in duyurusunu yapmak istiyorum.

DMV(Dynamic Management View)’ler ile SQL Server Database Sunucularını İzleme başlıklı webcast’im 28.12.2010 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://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032468645&EventCategory=4&culture=tr-TR&CountryCode=TR

Hepinizi bekliyorum.

[more]

DMV(Dynamic Management View)’ler ile SQL Server Database Sunucularını İzleme

Ajanda

  • DMV-DMF Nedir?
  • Ne Amaçla Kullanılır?
  • DMV-DMF Farkı Nedir?
  • Nasıl Bir Yetki Gereklidir?
  • Tüm DMV – DMF Listesi
  • Örnekler
  • Soru – Cevap

DMV – DMF Nedir?

SQL Server da Dynamic Management View(DMV) ve Dynamic Management Function’lar (DMF), sistemi monitor etmek, herhangi bir problemi teşhis etmek veya sistemi daha performanslı çalışması için tune etmek için kullanılan, sistem durumu hakkında bilgi dönen view ve function’lardır.

Ne Amaçla Kullanılır?

  • Şu an devam eden işler ve çalışan query’ler nedir?
  • Genel olarak SQL Server’ın işi yapmak için beklediği noktalar nereler?
  • Network IO’larında bekleme var mı?
  • Disklerin response time’ları nasıl?
  • Şu an diskte bekleyen herhangi bir IO işlemi var mı?
  • Eksik index’lerimden dolayı mı performans sıkıntım var?
  • Bu eksik index’ler nelerdir?
  • Index’lerimin scan,seek yani kısaca kullanım oranları nelerdir?
  • En çok CPU ya da IO tüketen sorgularım hangileri?

Geri kalan kısmı webcast’te :)

 

İ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 15 – DMV’ler ile Eksik Index Sorgulama makalemde sistemde olmayan ama olması tavsiye edilen indexleri nasıl sorgulayabileceğimizi incelemiştik.

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

[more]

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

  1. Missing Index’lerin sorgulanması ve create edilecek index’e karar verilmesi.
  2. Create edilmesi düşünülen index’in ve bulunduğu tablonun analizi
    1. Tablo kayıt sayısı ve boyut analizi
    2. Tablonun üzerinde bulunan diğer index’lerin kullanım istatistikleri
    3. Tablonun üzerinde bulunan index’lerin hangi kolonlar üzerinde bulunduklarının kontrolü
    4. Tablonun üzerinde bulunan index’lerin Include Column içerip içermediklerinin kontrolü
  3. Tablonun query stats’larına bakılıp ilgili missing index’e sebep olan script’in bulunması.
  4. Bulunan script’in şu anki IO değerlerinin sorgulanması.
  5. Index’in create edilmesi
  6. Sorgunun IO değerine tekrar bakılması ve ne kadar düştüğünün incelenmesi.
  7. Create edilen index’in kullanım istatistiklerinin monitor edilmesi

 

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

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

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

--Aşağıdaki select'i birden fazla kez çalıştırınız
select * from Person.Address where City='Duvall' and PostalCode='98019'

 

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

select DB_NAME(id.database_id) as databaseName,
	id.statement as TableName,
	id.equality_columns,
	id.inequality_columns,
	id.included_columns,
	gs.last_user_seek,
	gs.user_seeks,
	gs.last_user_scan,
	gs.user_scans,
	gs.avg_total_user_cost * gs.avg_user_impact * (gs.user_seeks + gs.user_scans)/100 as ImprovementValue		
from sys.dm_db_missing_index_group_stats gs
INNER JOIN sys.dm_db_missing_index_groups ig on gs.group_handle = ig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details id on id.index_handle = ig.index_handle
where DB_NAME(id.database_id) = 'AdventureWorks'
order by avg_total_user_cost * avg_user_impact * (user_seeks + user_scans)/100 desc

 

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

1

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

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

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

 

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

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

  • Tablo kayıt ve boyut analizi = Tablonun kayıt sayısına bakarak index’in gerekliliğinin ilk sorgulamasını yapıyoruz. Eğer ufak bir tablo ise index’in create edilmesinin çok gerekli olmadığını düşünebiliriz. Ya da boyutu çok büyükse oluşturacağımız index’in de iyi bir yer kaplayacağını düşünerek create edip etmemeyi tekrar değerlendirebiliriz. Ama ben kendi çalışmalarımda bu adıma sadece kontrol için bakıyorum. Çoğu durumda bu adım index create edip etmeme kararıma çok fazla etki etmiyor.
  • Tablonun üzerinde bulunan diğer index’lerin kullanım istatistikleri = Tabloda bulunan diğer index’lerin kullanım istatistiklerine bakıyoruz. Bu kontrol sırasında diğer index’lerin kullanım rakamlarına göre bu yeni oluşturacağımız index’in cluster index ya da non-cluster index olup olmayacağına karar verebiliriz.
  • Tablonun üzerinde bulunan index’lerin hangi kolonlar üzerinde bulunduklarının kontrolü = Tablonun hali hazırda sahip olduğu index’lerin hangi kolonlar üzerine tanımlandıklarına bakıyoruz. Belkide bizim oluşturmak istediğimiz index’i başka bir index farklı kolon sıralaması ile cover etmekte. Eğer böyle bir durum var ise index create etmemeyi düşünebiliriz.
  • Tablonun üzerinde bulunan index’lerin Include Column içerip içermediklerinin kontrolü = sp_helpindex sistem procedure’ü included column’ları vermemekte. Dolayısıyla son adım olarak bu kontrolüde yapıyoruz.

 

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

declare @tablename varchar(100)='Person.Address'

--1. Tablonun kayıt sayısına ve size ına bakılır.
exec('sp_spaceused '''+@tablename+''' ')

--2. Tablonun diğer index lerinin Index Usage Statistics lerine bakılır.
select OBJECT_SCHEMA_NAME(us.object_id),OBJECT_NAME(us.object_id) as tableName,
    i.index_id,
	i.name as indexName,
	us.last_user_seek,
	us.user_seeks,
	CASE us.user_seeks WHEN 0 THEN 0
		ELSE us.user_seeks*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS SeekPercentage,
	us.last_user_scan,
	us.user_scans,
	CASE us.user_scans WHEN 0 THEN 0
		ELSE us.user_scans*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS ScanPercentage,
	us.last_user_lookup,
	us.user_lookups,
	us.last_user_update,
	us.user_updates,
	CASE us.user_scans + us.user_seeks WHEN 0 THEN 0
		ELSE us.user_updates*1.0/(us.user_scans + us.user_seeks)*100.0 END as UpdatesPercentage	
FROM sys.dm_db_index_usage_stats us
INNER JOIN sys.indexes i ON i.object_id=us.object_id and i.index_id = us.index_id 
WHERE us.database_id = DB_ID() and OBJECT_SCHEMA_NAME(us.object_id)+'.'+OBJECT_NAME(us.object_id) = @tablename

--3. Diğer index'lerin hangi kolonlar üzerinde olduğuna bakılır.
exec('sp_helpindex '''+@tablename+''' ')

--4. Diğer index lerden included column içeren var mı yok mu diye bakılır.
select * from sys.index_columns where OBJECT_ID(@tablename)=object_id and is_included_column!=0

 

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

2

 

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

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

select 
     st.[text],
     SUBSTRING(st.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2) + 1) AS statement_text,        
     qs.last_execution_time,
     qs.execution_count,
     qs.total_logical_reads as total_logical_read,
     qs.total_logical_reads/execution_count as avg_logical_read,
     qs.total_worker_time/1000000 as total_cpu_time_sn,
     qs.total_worker_time/qs.execution_count/1000 as avg_cpu_time_ms,
     qp.query_plan,
     qs.last_logical_reads,
     qs.plan_generation_num
from sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) st
cross apply sys.dm_exec_query_plan(qs.plan_handle) qp
where SUBSTRING(st.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2) + 1) like '%Person.Address%'

 

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

3

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

 

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

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

set statistics IO ON
set statistics profile ON
select * from Person.Address where City='Duvall' and PostalCode='98019'

 

4

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

 

5. Index’in create edilmesi

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

USE [AdventureWorks]
GO
CREATE NONCLUSTERED INDEX [IX_Address_City_PostalCode]
ON [Person].[Address] ([City],[PostalCode])

 

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

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

set statistics IO ON
set statistics profile ON
select * from Person.Address where City='Duvall' and PostalCode='98019'

 

5

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

 

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

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

select OBJECT_NAME(us.object_id) as tableName,
	i.name as indexName,
	us.last_user_seek,
	us.user_seeks,
	CASE us.user_seeks WHEN 0 THEN 0
		ELSE us.user_seeks*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS SeekPercentage,
	us.last_user_scan,
	us.user_scans,
	CASE us.user_scans WHEN 0 THEN 0
		ELSE us.user_scans*1.0 /(us.user_scans + us.user_seeks) * 100.0 END AS ScanPercentage,
	us.last_user_lookup,
	us.user_lookups,
	us.last_user_update,
	us.user_updates
FROM sys.dm_db_index_usage_stats us
INNER JOIN sys.indexes i ON i.object_id=us.object_id and i.index_id = us.index_id 
WHERE us.database_id = DB_ID('AdventureWorks') and OBJECT_NAME(us.object_id) = 'Address'

 

6

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

 

ÖZET

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

 

İ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 bana gelen bir url’i sizinle paylaşmak istiyorum. Aşağıdaki süper manyak über :) şekilde hazırlanmış PDF dosyasıyla SQL Server 2008 ve R2 ile gelen system view’lerinin MAP’ine ulaşabilir, birbirleriyle olan relationlarına erişebilirsiniz.

Kesinlikle incelemenizi tavsiye ediyorum.

Overview

The Microsoft SQL Server 2008 R2 System Views Map shows the key system views included in Microsoft SQL Server 2008 and SQL Server 2008 R2, and the relationships between them. The map is similar to the Microsoft SQL Server 2008 version and includes updates which are also applicable for the Microsoft SQL Server 2008.

http://download.microsoft.com/download/0/6/9/06952611-7528-4CEE-BBD5-29DC44315DC7/SQL_Server_2008R2_System_Views_Map.pdf

map1

 

İ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