SQL Server Query Performans İpuçları

Bugün sizlerle Query Performans İpuçları üzerine konuşacağız ve Demolar yapacağız.

SELECT * kullanımının performansa etkisi ile başlıyoruz ;

Genelde en basit ama hep gözden kaçırıp dikkat etmediğiniz bir durumdur SELECT * kullanımı. Bunun üzerine seneler önce yazıp paylaştığım makalemin linkinide burada veriyorum ve ek olarak da bu makalemi okumanızı tavsiye ederim.

Makale : SELECT * Kullananların Başına Gelenler

SELECT ifadesini yazarken klasik hızlıca * sembolünü ekleyerek tablomuzda gerekli gereksiz yani işimize yarayan kolonlar dışında tüm kolonları çekip işlemlerimize bu şekilde devam ediyoruz. Fakat her zaman dile getirdiğim gibi biz bize gerekli kolonları çekmeliyiz. SELECT ile * şeklinde tüm kolonları çekmeye çalıştığımızda çoğu zaman tablo üzerinde yer alan Index kullanılmamakta ve tablo daki tüm veriler taranmaktadır. Bu durum performans açısından bizim hiç istemediğimiz bir durumdur. Table Scan veya Clustered Index Scan gibi durumlar performansımı aşağı çekecektir.

Örnek ile bu konuyu daha net halde ele alacak olursak ;

I. Query miz bize gerekli kolonları getirmekte ,

USE AdventureWorks2016
GO
SET STATISTICS IO ON
SELECT SalesOrderID,SalesOrderDetailID,CarrierTrackingNumber 
FROM Sales.SalesOrderDetail
WHERE ProductID = 707
GO

II.Query miz de ise * ile tüm kolonları çekiyoruz.

USE AdventureWorks2016
GO
SET STATISTICS IO ON
SELECT *
FROM Sales.SalesOrderDetail
WHERE ProductID = 707
GO

Şimdi ise query lerimizin istatistik değerlerini ve execution planlarını inceleyelim ,

Execution Plan ,

Gördüğünüz üzere SELECT ifademiz de kolon bazlı çektiğimizde {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}48 Cost , * ile çektiğimizde de {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}52 Cost u bulunmakta.

Statistics ,

IO değerleri ise aynıdır.

Şimdi ise query mizi Cover edecek bir Composite Index Create ediyoruz ve daha sonra query lerimizi tekrar EXECUTE edip hem Execution planına hemde IO değerlerine göz atacağız.

CREATE INDEX sqltr_NCIX_SalesOrderDetail_ProductID_SalesOrderID_VD ON Sales.SalesOrderDetail(ProductID, SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber)

Querylerimizi EXEC ediyoruz.

USE AdventureWorks2016
GO
SET STATISTICS IO ON
SELECT SalesOrderID,SalesOrderDetailID,CarrierTrackingNumber 
FROM Sales.SalesOrderDetail
WHERE ProductID = 707
GO
SET STATISTICS IO ON
SELECT *
FROM Sales.SalesOrderDetail
WHERE ProductID = 707
GO

Execution Plan ;

İki query de Cover etmesini beklediğimiz Index imiz sadece ilk query miz yani SELECT ile gerekli kolonları çektiğimiz query miz Index i kullandı ve Index seek operasyonunu gerçekleştirdi. SELECT * lı olan query miz Index i mizi kullanmadı ve Clustered Index Scan yapmaya devam etti ki biz yukarıda SELECT * ın çoğu zaman var olan Index i kullanmadığını bildirmiştik.

Birinci query Cost umuz bir anda {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}1 e düşerken, ikinci query Cost umuz ise {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}99 a çıkmıştır.

Statsitics ;

IO değerlerimizi incelediğimiz de de aradaki fark bariz gözükmektedir. Birinci query miz 13 Logical Read yaparken ikinci query miz 1266 Logical Read yapmaktadır.

 

Bu demodan çıkartacağımız en önemli konu , yazdığımız tüm query lerimiz de gerekli kolonlarımızı SELECT etmeliyiz…

EXISTS , IN , JOIN ile NOT NUUL kolonlar ile performans karşılaştırması ;

Aşağıda SQLTURKIYE veritabanı altında demo tablolarımızı oluşturup içerisine data atıyoruz.

USE SQLTURKIYE
GO
SET NOCOUNT ON
GO
CREATE TABLE sqltr_DemoTable
(
    ID INT NOT NULL,
    FirstName VARCHAR(100),
    LastName VARCHAR(100),
    City VARCHAR(100)
)
GO
INSERT INTO sqltr_DemoTable
    (ID,FirstName,LastName,City)
SELECT TOP 100000
    ROW_NUMBER() OVER (ORDER BY a.name) RowID, 'Bob', CASE WHEN  ROW_NUMBER() OVER (ORDER BY a.name){6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}2 = 1 THEN 'Smith' 
	   ELSE 'Brown' END, CASE WHEN ROW_NUMBER() OVER (ORDER BY a.name){6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}10 = 1 THEN 'New York' 
		 WHEN  ROW_NUMBER() OVER (ORDER BY a.name){6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}10 = 5 THEN 'San Marino' 
		 WHEN  ROW_NUMBER() OVER (ORDER BY a.name){6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}10 = 3 THEN 'Los Angeles' 
		 WHEN ROW_NUMBER() OVER (ORDER BY a.name){6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}427 = 1 THEN 'Hyderabad'
	   ELSE 'Houston' END
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
GO
CREATE TABLE sqltr_DemoTable2
(
    ID INT NOT NULL,
    FirstName VARCHAR(100),
    LastName VARCHAR(100),
    City VARCHAR(100)
)
GO
INSERT INTO SmallTable
    (ID,FirstName,LastName,City)
SELECT TOP(1000)
    *
FROM sqltr_DemoTable

Yukarıda oluşturduğumuz Demo tablolarımıza Clustered yani fiziksel index çakıyoruz.

USE SQLTURKIYE
GO
CREATE CLUSTERED INDEX sqltr_CIX__sqltr_DemoTable_ID
ON sqltr_DemoTable(ID)
GO
CREATE CLUSTERED INDEX sqltr_CIX_sqltr_DemoTable2_ID
ON sqltr_DemoTable(ID)
GO

Daha sonra sqltr_DemoTable2 tablosuna duplice kendinden 1 row daha ekleyelim ,

USE SQLTURKIYE
GO
INSERT INTO sqltr_DemoTable2 (ID,FirstName,LastName,City)
SELECT TOP(1) * FROM sqltr_DemoTable2

Şimdi ise query mizi 3 farklı operatör ile çalıştırıp query planları ve istatistik değerlerini inceleyelim ,

IN Kullanıyoruz :

-- Q1
USE SQLTURKIYE
GO
print'IN Kullanıyoruz'
SELECT ID, City
FROM sqltr_DemoTable 
WHERE ID IN 
(SELECT ID
FROM sqltr_DemoTable2)
GO

EXISTS Kullanıyoruz :

--Q2
USE SQLTURKIYE
GO
print 'EXISTS Kullanıyoruz'
SELECT ID, City
FROM sqltr_DemoTable 
WHERE EXISTS
(SELECT ID
FROM sqltr_DemoTable2
WHERE sqltr_DemoTable2.ID = sqltr_DemoTable.ID)
GO

JOIN Kullanıyoruz :

--Q3
USE SQLTURKIYE
GO
Print 'JOIN Kullanıyoruz'
SELECT dt.ID, dt.City
FROM sqltr_DemoTable dt
INNER JOIN sqltr_DemoTable2 dtt ON dt.ID = dtt.ID
GO

 

Execution Planları inceleyelim ;

Üç query ninde execution plan Cost ları aynı görülmektedir . ({6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}33)

Scan Count değerlerine baktığımızda ise , son query yani JOIN query miz dışındakiler aynı değere sahipken JOIN işleminde 1001 olarak görülmektedir.Bunun sebebi ise eklediğimiz son satırdır.

Bu durumda JOIN sorgumuzun istatistik değerinin biraz farklı ama Execution Planının diğer queryler ile aynı olduğu görülmektedir.

 

NOT EXISTS , NOT IN , JOIN ile NOT NULL kolonların performans karşılaştırılması ;

NOT IN Kullanarak :

-- Q1
USE SQLTURKIYE
GO
print'NOT IN Kullanıyoruz'
SELECT ID, City
FROM sqltr_DemoTable 
WHERE ID NOT IN 
(SELECT ID
FROM sqltr_DemoTable2)
GO

NOT EXISTS Kullanarak :

--Q2
USE SQLTURKIYE
GO
print 'NOT EXISTS Kullanıyoruz'
SELECT ID, City
FROM sqltr_DemoTable 
WHERE NOT EXISTS
(SELECT ID
FROM sqltr_DemoTable2
WHERE sqltr_DemoTable2.ID = sqltr_DemoTable.ID)
GO

LEFT JOIN Kullanarak :

--Q3
USE SQLTURKIYE
GO
Print 'LEFT JOIN Kullanıyoruz'
SELECT dt.ID, dt.City
FROM sqltr_DemoTable dt
LEFT JOIN sqltr_DemoTable2 dtt ON dt.ID = dtt.ID
WHERE dtt.ID IS NULL
GO

 

Execution Planı İnceliyoruz ,

Üç sorgumuzda aynı değerde sonuç döndürdü ama execution plana baktığımızda farklılığı görmekteyiz.

NOT IN ve NOT EXISTS query lerinin Costları {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}28 iken JOIN işleminin cost u {6189a1005e4f6dc613b8beb03d694ce50f0902974162d6a7dea944a970422cc9}43 tür.Bu gerçekten ilginç bir durum. Query lerinizi yazarken bu süreçleri performans açısından ön planda tutmalısınız.

 

NOT : Bu sayfa durmadan güncellenecek ….

 

2 Replies to “SQL Server Query Performans İpuçları”

  1. Harika bir makale olmuş. Konuyu ele bu kadar net alışınız ve anlatışınız için teşekkürler Hocam.

    Makalenin devamını beklemekteyiz.

Leave a Reply

Your email address will not be published. Required fields are marked *