SQL Server için Performans Parametreleri 1 |Performans Monitor Uygulaması Giriş

Merhaba,

Bu yazımızda SQL Server’ın sağlıklı çalışıp çalışmadığını görmek için baktığımız çeşitli performans parametreleri hakkında Türkçe olarak konuşacağız.

Öncelikle bilmeyenler için performans parametre ve sayarçlarına performance monitör (perfmon.exe) uygulaması ile bakıyoruz.

Yanlış anlaşılmasın bu uygulama windows’un içinde gelen bir uygulama yani sonradan kurulmuyor.

Başlat>Çalıştır’a perfmon yazarsak gelecektir.

image001.png

image002.png

Çalıştırdığımızda karşımıza böyle bir ekran çıkar.image003.png

Buradan istediğimiz sayaç değerini istediğimiz formatta görebiliyoruz. Örneğin yukarıda bu değerleri grafiksel olarak görürken aşağıdaki resimde Rapor işaretleyerek sayısal olarak görebiliyoruz.image004.jpg

image005.png

Şimdi bazı sayaçları ekleyerek değerler bakalım.

image006.png

Gördüğünüz gibi yüzlerce sayacımız var.

image007.png

Şimdi bunlardan en çok kullanılanlardan bir kaçını ekleyelim.

Örneğin aşağıdaki resimde diskte ortalama bekleyen işlem kuyruğu sayısı sayacını ekledim. (Ortalama disk sırası uzunluğu ya da Average Disk Que Length) değeri.image009.png

Burada tüm diskler için seçebildiğim gibi sadece bir veya birkaç diski de seçebiliyorum.

image010.jpg

Eklediğim sayaçları da bu şekilde izleyebiliyorum.

image012.pngBuradaki resimde ise başka sayaçlar da eklenmiş durumda.

image013.jpg

Bir sonraki yazıda bu parametreler nelerdir? Ne işe yarar ve olması gereken değerler nelerdir onları konuşuyor olacağız.

SQL Server ile Web Servise bağlanıp Json formatında hava durumunu çekme

SQL Server ile Web Servise bağlanıp Json formatında hava durumunu çekme

Son dönemde bildiğiniz üzere farklı platformlar arasında en çok kullanılan ortak dil JSON oldu. Öyle ki 2000’li yılların başında bu noktada bir devrim gibi çıkan web servislerinin formatı olan XML’in yerini sahip olduğu avantajlar ile almış durumda.

JSON bu kadar popüler iken data ile uğraşan insanlar olarak bizim de MSSQL tarafında bu konuya kayıtsız kalmamız doğru olmaz diye düşünüyorum ve JSON formatını SQL tablosuna  dönüştüren bir fonksiyonu sizinle burada paylaşıyorum. Her ne kadar 2017 versiyonunda MSSQL Json’a destek verse de önceki versiyonlarda kullanacağınız bir fonksiyonu paylaşıyorum.

Bu kodu bizimle paylaşan Scott Puleo’ya teşekkür ederiz.

Aşağıdaki linkte bulabilirsiniz.

Şimdi gelelim örneğimize internette hava durumunu api olarak json formatında bize veren bir site var.

https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22

Örneğin burası bize Londra ile alakalı JSON bilgi döndürüyor.

Buradaki Json bilgisini bizim fonksiyonumuzda çağırdığımızda ise aşağıdaki gibi sonuç görürüz.

Şimdi bu işlemi web sayfasından doğrudan çekecek şekilde kodumuzu yazalım.

Bunun için Ole Automation procedure leri kullanacağız. Bu procedure leri kullanmak için

exec sp_configure ‘show advanced options’,1

reconfigure with override

exec sp_configure ‘Ole Automation Procedures’,1

reconfigure with override

Diyerek aktif hale getiriyoruz.

Sonra kodumuzu çalıştırdığımızda sonuç aşağıdaki gibi geliyor. Burada bu site bir örnek ve normalde paralı olarak bu hizmeti veriyor. Başka uygulamalara da bakılabilir. Ben ilk karşıma çıkan sayfayı denedim açıkçası.

Kodları aşağıda paylaşıyorum.

Önce JSON dbo.parseJSON fonksiyonunu oluşturmanız gerekiyor. Onun scripti uzun olduğu için en aşağıda. Sonra hemen alttaki sorguyu çalıştırmanız yeterli.

Görüşmek üzere.

Declare @RESULT as varchar(MAX)
Declare @JSON as varchar(MAX)
Declare @Object as int;
Declare @ResponseText as varchar(8000);
Declare @HResult int
Declare @Source varchar(255), @Desc varchar(255)
Declare @Body as varchar(8000) =”

—————————Web Servise Bağlanıp hava durumunu çeken kod———————
Declare @URL as varchar(MAX)=’https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22′
Exec SP_OACreate ‘MSXML2.XMLHTTP’, @Object OUT;
Exec SP_OAMethod @Object, ‘Open’, NULL, ‘GET’,
@URL, ‘false’
Exec SP_OAMethod @Object, ‘setRequestHeader’, NULL, ‘Content-Type’, ‘application-json’
Declare @Len int
Set @Len = len(@Body)
Exec SP_OAMethod @Object, ‘send’, NULL
Exec SP_OAMethod @Object, ‘ResponseText’, @ResponseText OUTPUT
Set @JSON = @ResponseText

select * from dbo.parseJSON(@JSON)

——————————–json formatını parse eden fonksiyon————————
CREATE FUNCTION dbo.parseJSON( @JSON NVARCHAR(MAX))
RETURNS @hierarchy TABLE
(
element_id INT IDENTITY(1, 1) NOT NULL, /* internal surrogate primary key gives the order of parsing and the list order */
sequenceNo [int] NULL, /* the place in the sequence for the element */
parent_ID INT,/* if the element has a parent then it is in this column. The document is the ultimate parent, so you can get the structure from recursing from the document */
Object_ID INT,/* each list or object has an object id. This ties all elements to a parent. Lists are treated as objects here */
NAME NVARCHAR(2000),/* the name of the object */
StringValue NVARCHAR(MAX) NOT NULL,/*the string representation of the value of the element. */
ValueType VARCHAR(10) NOT null /* the declared type of the value represented as a string in StringValue*/
)
AS
BEGIN
DECLARE
@FirstObject INT, –the index of the first open bracket found in the JSON string
@OpenDelimiter INT,–the index of the next open bracket found in the JSON string
@NextOpenDelimiter INT,–the index of subsequent open bracket found in the JSON string
@NextCloseDelimiter INT,–the index of subsequent close bracket found in the JSON string
@Type NVARCHAR(10),–whether it denotes an object or an array
@NextCloseDelimiterChar CHAR(1),–either a ‘}’ or a ‘]’
@Contents NVARCHAR(MAX), –the unparsed contents of the bracketed expression
@Start INT, –index of the start of the token that you are parsing
@end INT,–index of the end of the token that you are parsing
@param INT,–the parameter at the end of the next Object/Array token
@EndOfName INT,–the index of the start of the parameter at end of Object/Array token
@token NVARCHAR(200),–either a string or object
@value NVARCHAR(MAX), — the value as a string
@SequenceNo int, — the sequence number within a list
@name NVARCHAR(200), –the name as a string
@parent_ID INT,–the next parent ID to allocate
@lenJSON INT,–the current length of the JSON String
@characters NCHAR(36),–used to convert hex to decimal
@result BIGINT,–the value of the hex symbol being parsed
@index SMALLINT,–used for parsing the hex value
@Escape INT –the index of the next escape character

DECLARE @Strings TABLE /* in this temporary table we keep all strings, even the names of the elements, since they are ‘escaped’ in a different way, and may contain, unescaped, brackets denoting objects or lists. These are replaced in the JSON string by tokens representing the string */
(
String_ID INT IDENTITY(1, 1),
StringValue NVARCHAR(MAX)
)
SELECT–initialise the characters to convert hex to ascii
@characters=’0123456789abcdefghijklmnopqrstuvwxyz’,
@SequenceNo=0, –set the sequence no. to something sensible.
/* firstly we process all strings. This is done because [{} and ] aren’t escaped in strings, which complicates an iterative parse. */
@parent_ID=0;
WHILE 1=1 –forever until there is nothing more to do
BEGIN
SELECT
@start=PATINDEX(‘%[^a-zA-Z][“]%’, @json collate SQL_Latin1_General_CP850_Bin);–next delimited string
IF @start=0 BREAK –no more so drop through the WHILE loop
IF SUBSTRING(@json, @start+1, 1)='”‘
BEGIN –Delimited Name
SET @start=@Start+1;
SET @end=PATINDEX(‘%[^\][“]%’, RIGHT(@json, LEN(@json+’|’)-@start) collate SQL_Latin1_General_CP850_Bin);
END
IF @end=0 –no end delimiter to last string
BREAK –no more
SELECT @token=SUBSTRING(@json, @start+1, @end-1)
–now put in the escaped control characters
SELECT @token=REPLACE(@token, FROMString, TOString)
FROM
(SELECT
‘\”‘ AS FromString, ‘”‘ AS ToString
UNION ALL SELECT ‘\\’, ‘\’
UNION ALL SELECT ‘\/’, ‘/’
UNION ALL SELECT ‘\b’, CHAR(08)
UNION ALL SELECT ‘\f’, CHAR(12)
UNION ALL SELECT ‘\n’, CHAR(10)
UNION ALL SELECT ‘\r’, CHAR(13)
UNION ALL SELECT ‘\t’, CHAR(09)
) substitutions
SELECT @result=0, @escape=1
–Begin to take out any hex escape codes
WHILE @escape>0
BEGIN
SELECT @index=0,
–find the next hex escape sequence
@escape=PATINDEX(‘%\x[0-9a-f][0-9a-f][0-9a-f][0-9a-f]%’, @token collate SQL_Latin1_General_CP850_Bin)
IF @escape>0 –if there is one
BEGIN
WHILE @index<4 –there are always four digits to a \x sequence
BEGIN
SELECT –determine its value
@result=@result+POWER(16, @index)
*(CHARINDEX(SUBSTRING(@token, @escape+2+3-@index, 1),
@characters)-1), @index=@index+1 ;

END
— and replace the hex sequence by its unicode value
SELECT @token=STUFF(@token, @escape, 6, NCHAR(@result))
END
END
–now store the string away
INSERT INTO @Strings (StringValue) SELECT @token
— and replace the string with a token
SELECT @JSON=STUFF(@json, @start, @end+1,
‘@string’+CONVERT(NVARCHAR(5), @@identity))
END
— all strings are now removed. Now we find the first leaf.
WHILE 1=1 –forever until there is nothing more to do
BEGIN

SELECT @parent_ID=@parent_ID+1
–find the first object or list by looking for the open bracket
SELECT @FirstObject=PATINDEX(‘%[{[[]%’, @json collate SQL_Latin1_General_CP850_Bin)–object or array
IF @FirstObject = 0 BREAK
IF (SUBSTRING(@json, @FirstObject, 1)='{‘)
SELECT @NextCloseDelimiterChar=’}’, @type=’object’
ELSE
SELECT @NextCloseDelimiterChar=’]’, @type=’array’
SELECT @OpenDelimiter=@firstObject

WHILE 1=1 –find the innermost object or list…
BEGIN
SELECT
@lenJSON=LEN(@JSON+’|’)-1
–find the matching close-delimiter proceeding after the open-delimiter
SELECT
@NextCloseDelimiter=CHARINDEX(@NextCloseDelimiterChar, @json,
@OpenDelimiter+1)
–is there an intervening open-delimiter of either type
SELECT @NextOpenDelimiter=PATINDEX(‘%[{[[]%’,
RIGHT(@json, @lenJSON-@OpenDelimiter)collate SQL_Latin1_General_CP850_Bin)–object
IF @NextOpenDelimiter=0
BREAK
SELECT @NextOpenDelimiter=@NextOpenDelimiter+@OpenDelimiter
IF @NextCloseDelimiter<@NextOpenDelimiter
BREAK
IF SUBSTRING(@json, @NextOpenDelimiter, 1)='{‘
SELECT @NextCloseDelimiterChar=’}’, @type=’object’
ELSE
SELECT @NextCloseDelimiterChar=’]’, @type=’array’
SELECT @OpenDelimiter=@NextOpenDelimiter
END
—and parse out the list or name/value pairs
SELECT
@contents=SUBSTRING(@json, @OpenDelimiter+1,
@NextCloseDelimiter-@OpenDelimiter-1)
SELECT
@JSON=STUFF(@json, @OpenDelimiter,
@NextCloseDelimiter-@OpenDelimiter+1,
‘@’+@type+CONVERT(NVARCHAR(5), @parent_ID))
WHILE (PATINDEX(‘%[A-Za-z0-9@+.e]%’, @contents collate SQL_Latin1_General_CP850_Bin))<>0
BEGIN
IF @Type=’Object’ –it will be a 0-n list containing a string followed by a string, number,boolean, or null
BEGIN
SELECT
@SequenceNo=0,@end=CHARINDEX(‘:’, ‘ ‘+@contents)–if there is anything, it will be a string-based name.
SELECT @start=PATINDEX(‘%[^A-Za-z@][@]%’, ‘ ‘+@contents collate SQL_Latin1_General_CP850_Bin)–AAAAAAAA
SELECT @token=SUBSTRING(‘ ‘+@contents, @start+1, @End-@Start-1),
@endofname=PATINDEX(‘%[0-9]%’, @token collate SQL_Latin1_General_CP850_Bin),
@param=RIGHT(@token, LEN(@token)-@endofname+1)
SELECT
@token=LEFT(@token, @endofname-1),
@Contents=RIGHT(‘ ‘+@contents, LEN(‘ ‘+@contents+’|’)-@end-1)
SELECT @name=stringvalue FROM @strings
WHERE string_ID=@param –fetch the name
END
ELSE
SELECT @Name=null,@SequenceNo=@SequenceNo+1
SELECT
@end=CHARINDEX(‘,’, @contents)– a string-token, object-token, list-token, number,boolean, or null
IF @end=0
SELECT @end=PATINDEX(‘%[A-Za-z0-9@+.e][^A-Za-z0-9@+.e]%’, @Contents+’ ‘ collate SQL_Latin1_General_CP850_Bin)
+1
SELECT
@start=PATINDEX(‘%[^A-Za-z0-9@+.e][A-Za-z0-9@+.e]%’, ‘ ‘+@contents collate SQL_Latin1_General_CP850_Bin)
–select @start,@end, LEN(@contents+’|’), @contents
SELECT
@Value=RTRIM(SUBSTRING(@contents, @start, @End-@Start)),
@Contents=RIGHT(@contents+’ ‘, LEN(@contents+’|’)-@end)
IF SUBSTRING(@value, 1, 7)=’@object’
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 8, 5),
SUBSTRING(@value, 8, 5), ‘object’
ELSE
IF SUBSTRING(@value, 1, 6)=’@array’
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 7, 5),
SUBSTRING(@value, 7, 5), ‘array’
ELSE
IF SUBSTRING(@value, 1, 7)=’@string’
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, ValueType)
SELECT @name, @SequenceNo, @parent_ID, stringvalue, ‘string’
FROM @strings
WHERE string_ID=SUBSTRING(@value, 8, 5)
ELSE
IF @value IN (‘true’, ‘false’)
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, ValueType)
SELECT @name, @SequenceNo, @parent_ID, @value, ‘boolean’
ELSE
IF @value=’null’
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, ValueType)
SELECT @name, @SequenceNo, @parent_ID, @value, ‘null’
ELSE
IF PATINDEX(‘%[^0-9]%’, @value collate SQL_Latin1_General_CP850_Bin)>0
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, ValueType)
SELECT @name, @SequenceNo, @parent_ID, @value, ‘real’
ELSE
INSERT INTO @hierarchy
(NAME, SequenceNo, parent_ID, StringValue, ValueType)
SELECT @name, @SequenceNo, @parent_ID, @value, ‘int’
if @Contents=’ ‘ Select @SequenceNo=0
END
END
INSERT INTO @hierarchy (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
SELECT ‘-‘,1, NULL, ”, @parent_ID-1, @type

RETURN
END
GO

SQL Server’da Ethernet Jumbo Paket Sorunu

Uzun zaman önce yaşadığım ve çözümünü çok zor bulduğum bir sorunu burada paylaşmak istiyorum. SQL deki yavaşlığın ethernetten kaynaklandığını tespit ettiğimiz çok ilginç bir deneyim.

2011 yılında başıma gelen bir olay.

Bir sistem upgrade’i yaptık. Server ımız değişti, Sistemde database olarak SQL 2005’ten SQL 2008’e, İşletim sistemi olarak Windows Server 2003’ten Windows Server 2008’e geçtik ve cluster yapısı kurduk. ERP Sistemimizde de versiyon geçişi yaptık ve yeni versiyonun çalışması için client bilgisayarlarda mdac versiyon upgrade’ine ihtiyaç duyduk.
Sonuç olarak çok ilginç bir sorunla karşılaştık. Kullanıcı tarafındaki çok basit bir işlem kimi bilgisayarda 1 sn sürerken kimi bilgisayarda 20 sn sürüyordu.
Bu durumda sorun server kaynaklı, İşletim sistemi kaynaklı, SQL 2008 kaynaklı, Erp programı kaynaklı, ya da mdac kaynaklı olabilirdi. Çünkü bunların hepsi de değişmişti. Epey bir inceleme yaptık sorun üzerinde.
Öncelikle şunu söyleyim bu ayarla alakalı sql server üzerinde bir makale bulamadım.
Ancak başka uygulamalarda benzer sıkıntılar yaşanmış onun üzerine bu konu üzerine gittik.
Burada problem şu;
Yeni ethernetler Jumbo frame denilen yapıyı destekliyor ve normalde 1500 byte lık olan network paketleri 9000 byte olarak tek seferde gönderiyor. Paketleri parçalama işini client ın etherneti yapıyor. Performans amaçlı yapılan bir iyileştirme.
Özellikle benzer işlem tekrarlarında bu durumu sistem otomatik olarak yapıyor yani kendince optimize etmeye çalışıyor. Client’ta özellikle döngüye takıp aynı sonucu döndüren tek satırlık ya da çoğunlukla sıfır satırlık select cümlelerinin kullanıldığı yerlerde bu özellik devreye giriyor.
Eğer karşıdaki ethernet jumbo paketi desteklemiyor ise paket tekrardan servera gönderiliyor bu kez server bu paketi tekrardan parçalayıp client a gönderiyor. Bu da her paket için yapıldığında yaklaşık 10 kat bazen daha fazla gecikmeye sebep oluyor.

Çözüm iki türlü.
Ya server’dan “Large Recive Offload Data” özelliğini disable etmek ya da client da jumbo paket size değerini arttırmak.
Ancak clientta işlem yapmanın iki dezavantajı var bunlardan biri ethernet ya da switchler desteklemiyor olabilir ikincisi de bu özellik enable yapıldığında networkte büyük paketler dolaşmaya başladığından networkü tıkayabilir.
Bu konuda en doğrusunun server üzerinde bu ayarı disable etmek olduğuna karar verdik.
Biz bu ayarı serverda disable ederek sorunu çözdük. Zaten eski serverda ethernet desteklemiyormuş ondan sorun olmamış.
Bu bahsettiğim sorundan kaynaklı sıkıntı olduğunu düşündüğün makinada sorun olup olmadığını anlamak için performance monitorden send packet/sec değerlerine bakılabilir.
Hızlı makina ile yavaş makina arasındaki fark en az 10 kat oluyor. Aşağıdaki resimde ise bu ayarın nasıl yapıldığını görüyorsunuz.

Sağlıcakla…

jumbo.jpg

Logon Trigger ile Sql Server’a İzin Verilmeyen Bilgisayarların Girmesini Engelleme

Bu yazımızda Logon trigger lar ile SQL Server’a bizim iznimiz dışında herhangi bir bilgisayardan kullanıcı adı şifre bilinse bile (SA kullanıcısı dahil) girilmesini engelleme uygulaması yapacağız.

Burada bir izin verilen bilgisayar listemiz var ve bunlar bir tabloda tutuluyor. Biz bu listenin dışında herhangi bi client dan login olma işlemi geldiğinde şunları yapabiliriz.

1.Kayıt altına alabiliriz.

2.Kayıt altına alıp anında sistem yöneticisine mail attırabiliriz.

3.Login olmasını engelleyebiliriz.

İşte bütün bu işlemleri yapmak için Logon Trigger yazmamız gerekiyor.

Trigger ları normalde tabloların altında insert,update,delete işlemleri için kullanırız. Ancak pek az bilinse de SQL Server 2008 sürümünden bu yana hem ddl trigger lar hem de logon trigger lar mevcut. Bu trigger lar sayesinde bir çok brut force atağın önüne kolaylıkla geçilebilir.

Böylece Kullanıcı adı, şifre, port önlemlerinin üstüne bir de client adı kontrolü ile ekstra bir koruma sağlamış oluyoruz.

Şimdi bu işlemi adım adım nasıl yaptığımızı anlatalım.

1.Öncelikle sisteme Audit isimli bir database oluşturalım. Bu database bizim loğları tutacağımız ve izin verilen bilgisayarları tanımlayacağımız yer.

 

2.Bu database in içine AuditLog isimli bir tablo oluşturalım.

CREATE TABLE [dbo].[AUDITLOG](

[SESIONID] [INT] NULL,

[LOGONTIME] [DATETIME] NOT NULL,

[NET_TRANSPORT] [VARCHAR](250) NOT NULL,

[PROTOCOL_TYPE] [VARCHAR](250) NULL,

[AUTH_TYPE] [VARCHAR](250) NOT NULL,

[NET_PACKETSIZE] [INT] NULL,

[CLIENT_NETADDRESS] [VARCHAR](250) NULL,

[PROGRAM_NAME] [VARCHAR](250) NOT NULL,

[HOSTNAME] [VARCHAR](250) NOT NULL,

[NT_USERNAME] [VARCHAR](250) NOT NULL,

[NET_LIBRARY] [VARCHAR](250) NOT NULL,

[NT_DOMAIN] [VARCHAR](250) NOT NULL

) ON [PRIMARY]

 

Burada gördüğümüz üzere

SESIONID: SQl Server a bağlanan kullanıcının id si.

LOGONTIME: Sisteme login olmaya çalıştığı zaman

NET_TRANSPORT: TCP , Shared Memory, Named Pipes gibi protokollerden hangisini kullandığı

PROTOCOL_TYPE: SQL Server ile konuşma dili (TSQL)

AUTH_TYPE: Authentication türü (NTLM,SQL)

NET_PACKETSIZE: Network paketi boyutu (4096,8000….)

CLIENT_NETADDRESS: Bağlanan kullanıcının Ethernet ya da IP Adresi

PROGRAM_NAME: SQL e bağlantı kurulmaya çalışılan uygulama (Management studio…)

HOSTNAME: Bağlanan kullanıcının bilgisayarının adı

NT_USERNAME: Bağlanan kullanıcının Windows kullanıcısının adı

NET_LIBRARY: Network kütüphanesi (TCP/IP, LPC…)

NT_DOMAIN:Kullanıcı domaini

3.Hangi bilgisayarlara izin vereceğimizi tutmak adına ALLOWEDHOST isimli bir tablo oluşturalım ve altına kendi bilgisayarımızı ekleyelim ki bağlanmamıza izin versin.

CREATE TABLE ALLOWEDHOST (HOSTNAME VARCHAR(200))

INSERT INTO ALLOWEDHOST (HOSTNAME) VALUES (‘OMERLENOVO’)

 

4.Şimdi trigger ımızı yazıyoruz. Trigger aşağıdaki gibi. Kodların ne işe yaradığını yorum satırlarında yazdım.

CREATE TRIGGER [connection_AUDIT] –TRIIGER ADI

ON ALL SERVER — BÜTÜN DATABASE LER İÇİN GEÇERLİ

FOR LOGON –LOGON İŞLEMİ SIRASINDA ÇALIŞACAK

AS

BEGIN

DECLARE @HOSTNAME AS VARCHAR(200) –KULLANICININ HOST BİLGİSİNİ ALMAK İÇİN DEĞİŞKEN TANIMLIYORUZ

SELECT @HOSTNAME=HOST_NAME() –HOST_NAME() FONKSİYONU CLIENT MAKİNENİN ADINI VERİR

 

IF NOT EXISTS (SELECT * FROM AUDIT.DBO.ALLOWEDHOST WHERE HOSTNAME=@HOSTNAME)

–EĞER KULLANICI BİLGİSAYARI BİZİM İZİN VERDİĞİMİZ LİSTEDE YOK İSE

–master.sys.dm_exec_connections VE master.dbo.sysprocesses SİSTEM VIEWLARINI KULLANARAK İHTİYACIMIZ OLAN BİLGİLERİ

–ALIYORUZ VE AUDITLOG TABLOSUNA INSERT EDIYORUZ.

BEGIN

INSERT INTO AUDIT.DBO.AUDITLOG (SESIONID, LOGONTIME, NET_TRANSPORT, PROTOCOL_TYPE, AUTH_TYPE, NET_PACKETSIZE, CLIENT_NETADDRESS,

PROGRAM_NAME, HOSTNAME, NT_USERNAME, NET_LIBRARY, NT_DOMAIN)

select

cnn.session_id SESIONID,cnn.connect_time LOGONTIME,cnn.net_transport NET_TRANSPORT,

cnn.protocol_type PROTOCOL_TYPE,

cnn.auth_scheme AUTH_TYPE,

cnn.net_packet_size NET_PACKETSIZE,

cnn.client_net_address CLIENT_NETADDRESS,

prc.program_name PROGRAM_NAME,

hostname HOSTNAME,

nt_username NT_USERNAME,

net_library NET_LIBRARY,nt_domain NT_DOMAIN

 

from master.sys.dm_exec_connections cnn

inner join master.dbo.sysprocesses prc

on prc.spid = cnn.session_id

WHERE PRC.spid=@@SPID

 

END

END

 

5.Management studioda Server Objects kısmında yazdığımız trigger ı görebiliriz.


6.Şimdi sisteme Login olalım.

Sistem girmeye izin verdi. Çünkü girdiğim makinenin adı OMERLENOVO ve bu isim ALLOWEDHOST tablosunda mevcut.

 

7.Kendi bilgisayarımızın adını izin verilen bilgisayarlar listesinden çıkaralım. Bunun için OMERLENOVO yazan yeri OMERLENOVO1 diye değiştiriyorum.

8.Şimdi yeni bir ekrandan bağlanmaya çalışıyorum.

Gördüğümüz gibi sisteme yine login olduk.

 

9.Log tablomuza bakalım.

OMERLENOVO makinesinden login olma işlemi kayıt altına alındı. Çünkü bu makine ALLOWEDHOST tablosunda yok ve yabancı bir makine. Burada otomatik olarak çalıştıracağımız bir job ile sistem yöneticisine bu durumu mail de attırabiliriz.

10.Eğer biz belirli bilgisayarlar dışında hiçbir bilgisayarın sisteme girememesini istiyorsak bu kez ROLLBACK yaparak bunu sağlayabiliriz.

ALTER TRIGGER [connection_AUDIT] –TRIIGER ADI

ON ALL SERVER — BÜTÜN DATABASE LER İÇİN GEÇERLİ

FOR LOGON –LOGON İŞLEMİ SIRASINDA ÇALIŞACAK

AS

BEGIN

DECLARE @HOSTNAME AS VARCHAR(200) –KULLANICININ HOST BİLGİSİNİ ALMAK İÇİN DEĞİŞKEN TANIMLIYORUZ

SELECT @HOSTNAME=HOST_NAME() –HOST_NAME() FONKSİYONU CLIENT MAKİNENİN ADINI VERİR

 

IF NOT EXISTS (SELECT * FROM AUDIT.DBO.ALLOWEDHOST WHERE HOSTNAME=@HOSTNAME)

–EĞER KULLANICI BİLGİSAYARI BİZİM İZİN VERDİĞİMİZ LİSTEDE YOK İSE DOĞRUDAN ROLLBACK YAPIYORUZ

 

ROLLBACK

 

END

 

11.Şimdi sisteme login olmaya çalışalım. (Bu kısım tehlikeli yeni bir management studio açalım çünkü bir daha hiç giremeyebiliriz.

Görüldüğü üzere sisteme girmeye izin vermedi.

12.Şimdi ALLOWEDHOST tablosunu OMERLENOVO1 yazan yeri OMERLENOVO olarak tekrar düzeltelim.

Tekrar girmeyi denediğimizde görüldüğü gibi giriş yapabiliyoruz.

 

Sonuç:

Eğer SQL Server sistemine bir saldırı olma ihtimali üzerine önlem alıyorsak,

Diyelim ki portları kontrol ettik,

Kullanıcı adı ve şifreleri kompleks yaptık

Ancak kullanıcı adı ve şifre birinin eline geçebilir ve bizim networkümüze başka bir bilgisayar da dahil olabilir.

Ya da yabancı bir bilgisayar brut force yaparak şifre denemesinde bulunabilir.

Bunun önüne geçmek adına LOGON triggerlar hem izleme hem de engelleme noktasında oldukça faydalı.

Son olarak bu denemeleri dikkatli yapmak gerekir. Ziraz kendi kendimizi engelleyebilirizJ. Siz siz olun bu işlemleri yapmadan önce master.mdf ve mastlog.ldf dosyalarını yedekleyin. Olur ki kendi kendinizi engellersiniz yapacağınız tek şey bu master database ine ait dosyaların eskisine dönmek.

SQL Server’da Change Data Capture ile Değişen ve Silinen Kayıtların Loglanması

SQL Server Change Data Capture ile Değişiklik Yapılan Kayıtların Loglanması

Bu makalemizde SQL Server tarafında yapılan maniplasyonların (Insert, Update, Delete) geri planda otomatik olarak kayıt altına alınmasını anlatıyor olacağız.
Şimdi bir senaryo düşünelim. Bir ticari yazılımımız var. Bu yazılımı dışarıdan satınaldık ve özelliklerine müdahele edemiyoruz kaynağı bizde olmadığı için.
Sistem üzerinde önemli bir fatura hareketinin değiştirildiğini ya da çıkarıldığını düşünelim. Son dönemlerdeki ticari yazılımlar bunların kayıt altına alınmasına izin veriyor ancak vermeyenler de var. Bu anlamda bizim database bazında bu kayıtların loglanmasına ihtiyacımız söz konusu.
Bu işlerle biraz uğraşanlar için ilk akla gelen tabiki trigger yazılması. Doğru bu bir çözümdür ancak sıkıntıları vardır.
Bu sıkıntılar genel olarak şöyledir;
  • Sizin yazdığınız trigger ticari programın kendisinin hata vermesine sebep olabilir ve kayıtların yapılmamasına sebep olabilir. Zira trigger lar transactionların bir parçasıdır ve trigger da gerçekleşen hata tüm transaction ı rollback  yapar.
  • Özellikle mevzuat değişimi gereği sıklıkla versiyon geçişi söz konusudur ve bu versiyon geçişlerinde database düzenlemesi yapıldığı için büyük ihtimal trigger larınız silinir ve her seferinde yeniden oluşturacak scriptler oluşturmanız gerekecektir.
  • Genel olarak Türkiye şartlarında dönem mali dönem bağımlı çalışmak tercih edildiği için her yıl başında fiziken yeni tablolar oluşturulmaktadır ve bunlar için de trigger lar yeniden yazılmalıdır.
Anlaşılacağı üzere trigger meselesi etkin bir çözümdür fakat biraz zahmetlidir.
Peki bizim yazımızın da konusu olan bu durum için bir çözüm yok mu? Birim fiyatı 5000 TL olan bir malzemenin satış faturasındaki fiyatını 50 TL olarak  değiştiren bir kişiyi tespit etmenin pratik bir yolu yok mudur?
Bu noktada imdadımıza SQL Server Change Data Capture (CDC) dediğimiz özellik yetişiyor. Bu arkadaş yetenekli bir arkadaş. SQL Server’da bildiğiniz üzere tüm manipülasyon işlemleri önce Log dosyasına sonra Data dosyasına yazılır.  Burada log dosyası diye bahsettiğim SQL server’ın sistem log dosyası değil database’in Log dosyasıdır (LDF).
İşte CDC sistem üzerinde Log dosyasını izler ve olan değişiklikleri hızlı bir şekilde kayıt altına alır.
Örnek olarak siz aşağıdaki gibi bir UPDATE cümlesi çalıştırdınız.
UPDATE CUSTOMERS SET ACTIVE=1
CUSTOMERS tablosunun 20 alandan oluştuğunu varsayalım oysa biz sadece bir alanı update ettik. Dolayısıyla SQL Server transaction log üzerinde sadece bir alanlık işlem hacmi söz konusu.
İşte Change Data Capture sadece bu bilgiyi okuyarak arka planda veriyi logluyor.
Siz  CDC yi configure ederken belli bir süreliğine dataları loglayıp belli bir tarihten öncesini sildirebiliyorsunuz. Burada yazacağınız bir script ile önce bu datalara herhangi bir warehouse ortamına alıp daha sonra sistemden temizleyebilirsiniz.
sql-data-capture-1.jpg
Öncelikle şunu başta belirtmek isterim ki bu özellik SQL Server 2008 den beri vardır ancak Enterprise edition üzerinde çalışır. Tabi test ortamları için developer edition da enterprise ın tüm özelliklerine sahiptir.
Şimdi bu CDC nasıl çalışıyor bir bakalım.
1. Önce bir tablo oluşturalım.
CREATE TABLE dbo.Customers(ID int Primary Key NOT NULL,Name varchar(100) NOT NULL,Address varchar(500) NOT NULL)
2.Database imizde CDC yi enable yapıyoruz.
EXEC sp_cdc_enable_db
3.Tablomuzda CDC yi enable yapalım.
EXEC sp_cdc_enable_table @source_schema = N’dbo’,@source_name = N’Customers’,@role_name = NULL,@filegroup_name =N”,@supports_net_changes = 1
CDC yi enable ettikten sonra system tables altında aşağıdaki tablolar oluşur.
sql-data-capture-2.jpg
  • cdc.captured_columns : Adından da anlaşılacağı üzere değişikliklerin takip edileceği kritik alanları tutar. Bu tablo manuel olarak edit edilebilir durumda olup içeriği değiştirilebilir.
  • cdc.change_tables :Hangi tabloların değişiminin takip edileceği bilgisini tutar.
  • cdc.ddl_history :Şema bilgilerindeki değişiklikleri tutar.
  • cdc.lsn_time_mapping: Asıl Tablo üzerinde yapılan her transaction işlemi bu tablo içerisinde tutulur ve içerisindeki lsn bilgisine göre hangi sırada yapıldığı bilgisi tutulur.
4.Şimdi bir kayıt ekleyelim.
       INSERT INTO CUSTOMERS (Id,Name,Address) VALUES  (1,’ÖMER’,’TÜRKİYE’);
       INSERT INTO CUSTOMERS (Id,Name,Address) VALUES  (1,’AHMET’,’İSTANBUL’)
5.UPDATE yapalım.
               UPDATE CUSTOMERS SET ADDRESS=’ANKARA’ WHERE ID=1
6.DELETE Yapalım
       DELETE FROM CUSTOMERS WHERE ID=1
Görüldüğü gibi tablo üzerinde 2 insert,1 update ve 1 delete işlemi yaptık. Burada sistemde 4 satır kaydın logunun tutulması gerekiyor. Bakalım görebilecek miyiz?
Şimdi tablolarımıza bir bakalım

 

LbLmvo.png

 

CDC.ddl_history tablosu
sql-data-capture-6.jpg
CDC.index_column tablosu
sql-data-capture-7.jpg
CDC.lsn_time_mapping tablosu
Sistemde loglanan kayıtları ya doğrudan ya da tarih parametresi alan table valued function lar ile görebiliyoruz. Bu tablodaki kayıtlar ise log sequence number (lsn) ile tutuluyor. Bu fonksiyonlar da yine tablo bazlı olarak otomatik oluşuyor. Aşağıdaki resimde bu fonksiyonları görebilirsiniz.
sql-data-capture-8.jpg
Log kayıtlarını ulaşmak  istediğimizde  eğer tablonun tamamına ulaşmak istiyor isek
select
* from cdc.dbo_customers_CT şeklinde kullanıyoruz.
burada tablo formatı cdc.<schema>_<tablename>_CT şeklinde.
Bunun kullanımını sonucu aşağıdaki gibi.
sql-data-capture-9.jpg
table valued functionlar  ise aşağıdaki gibi kullanılıyor.
DECLARE @from_lsn binary(10), @to_lsn binary(10);
–minimum lsn numarasını buluyoruz.
SET @from_lsn = sys.fn_cdc_get_min_lsn(‘dbo_customers’);
–maximum lsn numarasını buluyoruz.
SET @to_lsn = sys.fn_cdc_get_max_lsn();
— CDC ile ilgili işlemlerde tablo bazlı oluşan cdc function larını kullanıyoruz.
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_customers(@from_lsn, @to_lsn, ‘all’);
sql-data-capture-10.jpg
Görüldüğü üzere sistem 4 adet fazladan alan ve sistemde yapılan değişiklik üzerine loglanan kayıtları getirdi.
Burada
__$start_lsn log: sequence number bilgisini içeriyor. Buradan kayıt tarihine erişebiliyoruz.
__$seqval: Sequnce değeri yani işlemin hangi sırada gerçekleştiği bilgisine erişmek için bu alan kullanılıyorç
__$operation:2 Insert, 4 Update ve 1 Delete için kullanılıyor.
__$operation:1 Insert,Delete 0 Update
anlamına gelmektedir.
Burada kayıt zamanını elde etmek istediğimizde
sys.fn_cdc_map_lsn_to_time function ını kullanıyoruz.
select sys.fn_cdc_map_lsn_to_time(__$start_lsn) as KayitZamani,
* from cdc.dbo_customers_CT
sql-data-capture-11.jpg
Burada oluşan log kayıtlarını temizlemek için ise
sp_cdc_cleanup_change_table
komutunu kullanıyoruz.
Kullanımı aşağıdaki gibi.
— aşağıdaki kod 3 gün öncesine ait logları temizliyor.
declare @lsn binary(10);
set @lsn = sys.fn_cdc_map_time_to_lsn(‘largest less than or equal’,getdate()-3);
exec sys.sp_cdc_cleanup_change_table @capture_instance = ‘dbo_Customers’, @low_water_mark=@lsn

–CDC yi disable etmek için ise
sp_cdc_disable_db,
sp_cdc_disable_table
komutları kullanlr

EXECUTE sp_cdc_disable_table@source_schema = N’dbo’,@source_name = N’Customers’,@capture_instance=N’dbo_Customers’
CDC çalıştırabilmek için SQL Server agent a ihtiyacımız söz konusu.  Sistem 2 adet job ı otomatik olarak oluşturmaktadır. Bunlardan birisi değişen datanın capture edilmesini sağlarken diğeri de logları temizlemektedir.
sql-data-capture-12.jpg
Sonuç:
  1. CDC gerçekten çok ihtiyaç duyulan ve çok kullanışlı bir araç.
  2. Sistemdeki insert, update ve delete leri loglayabiliyor.
  3. Eğer update cümlesinde kayıt değişmiyor ise gereksiz yer teşkil etmiyor.
  4. Örneğin: UPDATE CUSTOMERS SET NAME=NAME cümlesini çalıştırdığımızda herhangi bir loglama yapmıyor çünkü değişen bir şey yok.
  5. Sistemin çalışıyor olması için SQL Server Agent’ın mutlaka çalışması gerekir. Çünkü logları okuyan bir job bu işleri yerine getirmektedir.
  6. Yazacağımız bir script ile istediğimiz tablolarda çalıştırıp istemediklerimizde çalıştırmayabiliriz. Hatta çok fazla kolon olan bir tabloda istediğimiz kolonlar için aktif hale getirirken istemediklerimizi es geçebiliriz.

Donanımların SQL Server Performansı üzerine etkisi

Her şey yavaş çalışan bir kaç raporla başladı aslında. Bu raporlar çalıştığı zaman normal işlemlerin de kilitlenmesi ile devam etti sorunlar silsilesi.
Soluğu bilişim danışmanı firmanın yanında aldınız.
Cevap: “Abi makine %99 ram kullanıyor ram alman lazım.” Ram’i uygun fiyatlı arttırmak adına Bus hızı düşük olan ram ler aldınız.
Sonrasında sistem yine yavaş. Hatta daha yavaş. Tekrar sordunuz, tekrar cevap aldınız.
“Abi makine eskidi upgrade edelim.”
Sonuç:Yine hüsran.
Benzer senaryoları yaşayanlar bilir. Bir daha yaşanmaması adına Donanımlar SQL Server Performansını nasıl etkiler? Nasıl ölçülür? Hangi durumlarda hangi donanım upgrade i yapılmalıdır? Gibi senaryoları anlattığım bir sohbet videosu.

İyi seyirler. Smile

SQL Server’da Suspect Mode’a düşen bir database’i kurtarma

Bir database’in suspect moda düşmesi demek database dosyalarından (mdf,ldf,ndf) en az birini okurken bir sorunla karşılaşmış olması anlamına gelir.

image001.png
Genel olarak bu sorun şu sebeblerden kaynaklanıyor olabilir.

  1. Database dosyaları bozulmuş olabilir.
  2. Sistemde yeterli disk alanı kalmamış olabilir.
  3. Yeterli memory kalmamış olabilir.
  4. Database dosyaları silinmiş ya da işletim sistemi dosyaların kullanılmasına izin vermiyor olabilir.
  5. Server düzgün kapatılmadığı için ya da bir takım donanımsal sorunlar yüzünden dosyalar okunamıyor olabilir.

Bu moda düşen bir database’i normale çevirmek için aşağıdaki komutları kullanırız.

–Database’in statüsünü resetleme komutu. Böylece manuel müdaheleye izin verir.
EXEC sp_resetstatus ‘dbName’;
–Database’i emergency moda çekiyoruz.

image003.png

ALTER DATABASE dbName SET EMERGENCY ;
–Database tablolarında ya da dosyalarında bir bozukluk var mı onu kontrol eden komut.
DBCC checkdb(‘dbNAme’)
–Database’i single moda çekiyoruz.
ALTER DATABASE SET dbName SINGLE_USER WITH ROLLBACK IMMEDIATE
–Database’de bir bozukluk var ise logdaki henüz commit edilmemiş veri varsa bunun kaybına izin verecek şekilde düzenleme yapıyoruz.

–Log dosyası silinir ve yeni bir yeni boş bir log dosyası oluşturulur. Bu sırada log dosyasındaki –kaydedilmeyen veriler silinir.
DBCC CheckDB (‘dbName’, REPAIR_ALLOW_DATA_LOSS)
–Database’i kullanıma açıyoruz.
ALTER DATABASE dbName SET MULTI_USER
Database’imizi artık gönül rahatlığıyla selectleyebilirizSmile

SQL Server’da bilinçli indexleme

SQL Server’da hatta tüm veritabanlarında Index’in ne kadar önemli olduğunu bilmeyen yoktur sanırım.
Index performansı ile alakalı şöyle bir örnek vereyim.
128 milyon satırlı bir tabloda index yoksa aradığınız bir satırı bulmak için 128 milyon işlem yaparsınız.
Oysa index varsa binary search mantığı ile sistem sürekli ikiye bölme mantığı ile gittiği için aşağıda gördüğünüz gibi 27 işlemde işi bitirir.
1)     128,000,000       /2
2)     64,000,000          /2
3)     32,000,000          /2
4)     16,000,000          /2
5)     8,000,000            /2
6)     4,000,000            /2
7)     2,000,000            /2
8)     1,000,000            /2
9)     500,000                /2
10)  250,000                /2
11)  125,000                /2
12)  62,500                  /2
13)  31,250                  /2
14)  15,625                  /2
15)  7,813                    /2
16)  3,906                    /2
17)  1,953                    /2
18)  977                        /2
19)  488                        /2
20)  244                        /2
21)  122                        /2
22)  61                          /2
23)  31                          /2
24)  15                          /2
25)  8                             /2
26)  4                             /2
27)  2                             /2

Satır sayısı iki katına çıktığında yani 256 milyon olduğunda ise index olmadan arama işlemi 256 milyona çıkarken index li arama işlemi 28 adımda gerçekleşir.
1)     256,000,000       /2
2)     128,000,000       /2
3)     64,000,000          /2
4)     32,000,000          /2
5)     16,000,000          /2
6)     8,000,000            /2
7)     4,000,000            /2
8)     2,000,000            /2
9)     1,000,000            /2
10)  500,000                /2
11)  250,000                /2
12)  125,000                /2
13)  62,500                  /2
14)  31,250                  /2
15)  15,625                  /2
16)  7,813                    /2
17)  3,906                    /2
18)  1,953                    /2
19)  977                        /2
20)  488                        /2
21)  244                        /2
22)  122                        /2
23)  61                          /2
24)  31                          /2
25)  15                          /2
26)  8                             /2
27)  4                             /2
28)  2                             /2

Dediğim gibi indexi bilen bilir de bu hesabı bilmeyenler vardır belki diye bu açıklamayı yaptım.
Indexler yaşayan varlıklar. Yeni kayıtlar eklendiğinde, kayıtlar silindiğinde ya da güncelleme işlemi yapıldığında haliyle bu indexler bozuluyor ve belli zaman aralıklarında bunları güncellemek gerekiyor. Bunun yolu da çok basit. 2 dakikada bu işi yapacak bir planı kod yazmadan oluşturabilirsiniz.
Bu düzeltme işlemi bazen uzun sürüyor ve sistemi kilitliyor ve durdurmak zorunda kalıyorusunuz. Siz hangi tablonun indexlerinin yapıldığını hangi tablonun yapılmadığını bilemiyorsunuz.
Ayrıca hangi tablo indexten önce ne kadar bozuktu, indexten sonra ne kadar düzeldi onu da bilmiyorsunuz.
Yine hangi tablodaki index düzeltme işleminin ne kadar sürdüğünü de bilmiyorsunuz.
İşte bu sorunlara çözüm olması amacı ile bir stored procedure yazdım ve onu burada paylaşmak istiyorum.
Bunun için index işlemini loglamak amacı ile bir veritabanı ve bir tablo oluşturuyoruz.
Örneğin ben burada database i BT diye oluşturdum.
Siz de aşağıdaki script ile oluşturabilirsiniz.

use BT
CREATE TABLE [dbo].[TBLINDEXLOG](
       [ID] [int] IDENTITY(1,1) NOT NULL,
       [SEQID] [int] NULL,
       [DBNAME] [varchar](50) NULL,
       [TABLENAME] [varchar](250) NULL,
       [BEGDATE] [datetime] NOT NULL,
       [ENDDATE] [datetime] NULL,
       [DURATION] [int] NULL,
       [FRAGMANTATIONBEFORE] [float] NULL,
       [FRAGMANTATIONAFTER] [nchar](10) NULL,
       [ROWCOUNT_] [int] NULL,
       [DATASIZEBEFORE] [int] NULL,
       [INDEXSIZEBEFORE] [int] NULL,
       [TABLESIZEBEFORE] [int] NULL,
       [DATASIZEAFTER] [int] NULL,
       [INDEXSIZEAFTER] [int] NULL,
       [TABLESIZEAFTER] [int] NULL,
 CONSTRAINT [PK_TBLINDEXLOG] PRIMARY KEY CLUSTERED 
(
       [ID] ASC
 
)  )

Burada alanları şu şekilde açıklayabiliriz.
ID:Otomatik artan alan
SEQID:Toplu olarak yapılan index işleminde her tablo loglanacağı için bu toplu işlemi takip etme adına koyduğumuz SEQUENCEID değeri.
DBNAME:Hangi database’de index düzeltme yapıyorsak onun bilgisi
TABLENAME:Index düzeltme yaptğımız tablonun adı.
BEGDATE:Index başlama zamanı
ENDDATE:Index bitme zamanı
DURATION:Index düzeltme süresi (ENDDATE-BEGDATE)
FRAGMANTATIONBEFORE:Index yapmadan önceki indexlerin bozukluk oranı.
FRAGMANTATIONAFTER: Index yaptıktan sonraki indexlerin bozukluk oranı.
ROWCOUNT_:Tablodaki satır sayısı
DATASIZEBEFORE:Index yapmadan önce tabloda datanın kapladığı alan
INDEXSIZEBEFORE:Index yapmadan önce tabloda indexlerin kapladığı alan
TABLESIZEBEFORE: DATASIZEBEFORE+ INDEXSIZEBEFORE
DATASIZEAFTER:Index yaptıktan sonra tabloda datanın kapladığı alan
INDEXSIZEAFTER:Index yaptıktan sonra tabloda indexlerin kapladığı alan
TABLESIZEAFTER: DATASIZEAFTER+ INDEXSIZEAFTER

Procedure ümüz ize aşağıdaki gibi. Gördüğümüz gibi içerisine iki parametre alıyor biri tablo adı. Yani ben sadece bir tabloyu ya da adı “LG_“ ile başlayan tabloları index yap diyebilirim.
İkincisi ise fill factor. Bunu da bilen bilir. İdeali 70-80 gibi değerler vermektir ama duruma göre farklı değerler de verebilirsiniz.
CREATE PROC [dbo].[SPREINDEX]    
@TABLENAME AS VARCHAR(1000)=’%’,
@FILLFACTOR AS INT=70
AS
DECLARE @ID AS INT 
DECLARE @SEQID AS INT
DECLARE @BEGDATE AS DATETIME
DECLARE @ENDDATE AS DATETIME
DECLARE @DURATION AS INT
DECLARE @ROWCOUNT AS INT
DECLARE @DATASIZEBEFORE INT
DECLARE @INDEXSIZEBEFORE AS INT
DECLARE @TABLESIZEBEFORE AS INT
DECLARE @DATASIZEAFTER INT
DECLARE @INDEXSIZEAFTER AS INT
DECLARE @TABLESIZEAFTER AS INT 
DECLARE @FRAGMANTATIONBEFORE AS FLOAT
DECLARE @FRAGMANTATIONAFTER AS FLOAT
 
SELECT @SEQID=MAX(SEQID)+1 FROM BT.DBO.TBLINDEXLOG 
SET @SEQID=ISNULL(@SEQID,1)
 
CREATE TABLE #T (NAME VARCHAR(200),ROWS INT,RESERVED VARCHAR(100),DATA VARCHAR(100),INDEX_SIZE VARCHAR(100),UNUSED VARCHAR(100))
 
DECLARE @TABLENAME2 AS VARCHAR(1000)
DECLARE CRS CURSOR FOR SELECT NAME FROM SYSOBJECTS WHERE XTYPE=’U’ AND NAME LIKE @TABLENAME
OPEN CRS
FETCH NEXT FROM CRS INTO @TABLENAME2
WHILE @@FETCH_STATUS=0
BEGIN
             SET @BEGDATE=GETDATE()
             TRUNCATE TABLE #T 
             INSERT INTO #T 
             EXEC SP_SPACEUSED @TABLENAME2
 
             SELECT @ROWCOUNT=ROWS 
             ,@TABLESIZEBEFORE=CONVERT(INT,REPLACE(RESERVED,’ KB’,”))
             ,@DATASIZEBEFORE=CONVERT(INT,REPLACE(DATA,’ KB’,”))
             ,@INDEXSIZEBEFORE=CONVERT(INT,REPLACE(INDEX_SIZE,’ KB’,”))
             FROM #T
 
             SELECT @FRAGMANTATIONBEFORE=avg(avg_fragmentation_in_percent) FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(@TABLENAME2), NULL, NULL , ‘LIMITED’) 
       —     WHERE index_type_desc <>’CLUSTERED INDEX’
 
 
             INSERT INTO BT.DBO.TBLINDEXLOG 
             (SEQID, TABLENAME,DBNAME, BEGDATE,ROWCOUNT_,DATASIZEBEFORE,INDEXSIZEBEFORE,TABLESIZEBEFORE,FRAGMANTATIONBEFORE)
             VALUES
             (@SEQID, @TABLENAME2,DB_NAME(), @BEGDATE,@ROWCOUNT,@DATASIZEBEFORE,@INDEXSIZEBEFORE,@TABLESIZEBEFORE,@FRAGMANTATIONBEFORE)
 
             SET @ID=@@IDENTITY 
 
             DECLARE @SQL AS NVARCHAR(MAX)
             SET @SQL=’ALTER INDEX ALL ON ‘+@TABLENAME2+’ REBUILD WITH (FILLFACTOR=’+CONVERT(VARCHAR,@FILLFACTOR)+’)’
             EXEC SP_EXECUTESQL @SQL 
 
             TRUNCATE TABLE #T 
             INSERT INTO #T 
             EXEC SP_SPACEUSED @TABLENAME2
 
             SELECT @ROWCOUNT=ROWS 
             ,@TABLESIZEAFTER=CONVERT(INT,REPLACE(RESERVED,’ KB’,”))
             ,@DATASIZEAFTER=CONVERT(INT,REPLACE(DATA,’ KB’,”))
             ,@INDEXSIZEAFTER=CONVERT(INT,REPLACE(INDEX_SIZE,’ KB’,”))
             FROM #T
 
             SET @ENDDATE=GETDATE()
             SET @DURATION=DATEDIFF(SECOND,@BEGDATE,@ENDDATE)
 
 
             SELECT @FRAGMANTATIONAFTER=avg(avg_fragmentation_in_percent) FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(@TABLENAME2), NULL, NULL , ‘LIMITED’) 
             WHERE index_type_desc <>’CLUSTERED INDEX’
 
             SET @SQL=’UPDATE STATISTICS ‘+@TABLENAME2 
             EXEC SP_EXECUTESQL @SQL  
 
             UPDATE BT.DBO.TBLINDEXLOG SET 
             ENDDATE=@ENDDATE, DURATION=@DURATION, DATASIZEAFTER=@DATASIZEAFTER, INDEXSIZEAFTER=@INDEXSIZEAFTER,
             TABLESIZEAFTER=@TABLESIZEAFTER,
             FRAGMANTATIONAFTER=@FRAGMANTATIONAFTER
             WHERE ID=@ID 
 
 
FETCH NEXT FROM CRS INTO @TABLENAME2
END
CLOSE CRS
DEALLOCATE CRS
 
DROP TABLE #T

Ben bu procedure ü kendi test ortamımda çalıştırdım ve 58 sn sürdü.

vPgY0p.png

Şimdi sonuçlara bakalım.

Aşağıdaki resim en çok bozuk index olan tabloları gösteriyor1EYj9G.png

Bu resim de en çok satır sayıları olan tabloları incelememizi sağlıyor. Örneğin burada LG_217_01_STLINE tablosunu inceleyelim
%31 fragmante olmuş yani indexler %31 oranında bozuk.
159.469 satır var
Index süresi 42 sn sürmüş.
Tablo boyutu indexten sonra 409.368 KB’tan  492.056’a çıkmış. Bu büyüme Fill factor değerinden kaynaklanıyor.

Bu sorgu da işlemin toplam ne kadar sürdüğünü söylüyor. Burada saniyeler yuvarlandığı için 53 olarak getirmiş.
r1gYq1.png

Bu procedure ü de örneğin haftada bir çalışacak bir job haline getirirsek alın size Index maintanance plan.Smile
Umarım anlaşılabilir bir makale olmuştur. Çünkü gerçekten veritabanı ile uğraşanlar için büyük kolaylık sağlıyor.

Başka bir yazıda görüşmek üzere.

SQL Server’ı restart etmek doğru bir davranış mı?

Sql server her yavaşladığında restart etmek gerek diye düşünenler için ezber bozan çok güzel bir yazı. Eğer gerçekten önemli bir sebebi yoksa sistemi restart etmek sql server a yapılacak en büyük kötülüklerden biri.

Orijinal link
https://www.linkedin.com/pulse/frequent-…y-bruk-1e/

Frequent SQL Server restart, is it a bad practice?
In theory, Microsoft SQL Server never requires a reboot, just like a Windows server, and most of the configuration can take effect immediately.

However, in a few cases, we are obliged to do so:

[*]Windows maintenances;

[*]Enabling/disabling, or changing the configuration of network protocols (using SQL Server Configuration Manager);

[*]Applying of Hotfixes/Patches/Service packs;

[*]tempdb issues which are fixed by the recreation of the file;

[*]Applying startup parameters such as trace flags;

[*]Server authentication mode changes.

Except for the above reasons, a good maintained and healthy SQL Server could happily stay online forever.

In my opinion, restart of the server is probably one of the most damaging things for performance! When we restart SQL server, we get all the memory back to the server OS, completely clean plan cache and wipe out all tempdb. That mean:

  • To take back the memory from Windows OS will take a while. During that period, SQL server will read a lot of data from the disc and upload data back into the memory.
  • A Plan Cache of SQL Server stores the precompiled execution plan for frequently executed of queries. It improves the query performance by reducing a cost of creating and repeatedly compiling of execution plans.
  • All cached query plans are lost and SQL server will need to compile it again. Depending on the workload of the server, it will take a while for SQL server to generate and load back to the cache all execution plans. “New Plan Cache” probably will not contain rarely executed procedures, because all plans will be generated “on the fly” since service is up. Additionally, we risking “to get” bad execution plan.
  • All collected information used to output the Dynamic Management Views (DMV’s) and Functions, about the state of SQL Server, will be lost and cannot be used to monitor the health of a server instance, diagnose problems, and tune performance.
  • Part of SQL Server crash recovery it’s a rollback of uncommitted transactions. SQL Server rollbacks all uncommitted transactions, which degrade the performance and restart process, might take more time to complete the action.
  • In case of a busy server with the high workload, SQL Server restart will perform rollbacks for many queries are start their running before the restart and this process may take a lot of time.
  • Let’s be honest, not every Windows restart finished as we want – happy and fixed all our issues.

[*]

Seems, we have many things to think about, before we will push on the reboot button.
I am not saying that reboot of SQL Server is a bad practice, but often reboot or reboot without good reason, it’s a bad practice.

For the summary, I will use Paul Randal’s quote:
“if you’re regularly rebooting Windows/SQL Server, make sure it’s for a good reason and not just because someone thinks it’s a good thing to do or it’s the chosen way to fix a problem that should be fixed in some other way.”

SQL Server’da Satırları Gruplayıp Yanyana Yazdırma

Veritabanında sorgulama yaparken bazen dikey olarak satır satır gelen verileri yatay olarak virgül ile birleştirerek yazdırmak isteriz.

Basit bir örnek verecek olur isek örneğin iller tablomuz var 81 satır.

1

Bir de ilçeler tablomuz var o da 911 satır.

2

Standart sql bilgimiz ile biz bu tabloları alt alta birleştirebiliriz. Yani aşağıdaki gibi.

3

Oysa biz şöyle bir sonuç almak isteyebiliriz bazı durumlarda.

4

İşte bu yazıda bunun nasıl yapıldığından bahsediyor olacağım.

Bir select sorgusunun sonucunu xml olarak görebiliriz. Bunun için for xml path komutunu kullanırız.

5

Burada sonuç xml olarak gelir. Yani aşağıdaki gibi.

6

SELECT Convert(varchar,isim+’,’) FROM ilceler for xml path(”)

Dediğimizde aşağıdaki gibi virgül ile yanyana yazdırılan değerler görürüz.

7

select *,

(select convert(varchar,isim+’,’) from ilceler where il_no=iller.il_no FOR XML PATH(”)) as ILCELER

from iller

 

dediğimizde ise aşağıdaki gibi istediğimiz sonucu elde ederiz.

8