Execution Plan Nasıl İncelenmelidir Part-3

Berk Emre Çabuk
6 min readApr 5, 2020

--

Serinin üçüncü yazısı ile karşınızdayım arkadaşlar. Bir önceki yazımda operatörlere giriş yapmıştık zaten bu yazımda da tekrardan operatörlerden devam edeceğim. Tabi bütün operatörleri anlatamayacağım hem sizi çok bunaltmamak hem de çoğunlukla deneyimlediğim bilgileri aktarmak istiyorum. Hızlıca devam edelim;

7)Sort

Sorgumuzda “Order by” yer alıyor ise Sort operatörünü görmemiz yüksek ihtimal diyebiliriz. Execution Plan üzerinde incelediğinizde seek/scan işleminden sonra gelen veri kümesi üzerinde Sort işlemi gerçekleştirilir sebebi ise verilerin istenilen parametreye göre sıralı olmamasıdır. Bunun dışında Distinct işlemi gibi öncesinde verilerin sıralanması gerektiği durumlarda da Sort işlemini görürüz. Çoğunlukla maliyeti çok olmuyor ama koşul olarak belirtilen parametre ile sıralama olarak belirtilen parametreler farklı ise ayrıca sıralama için belirtilen parametreye yönelik index’imiz yoksa maliyeti fazla olabilir.

Çözüm olarak ise bu sıralamanın gerçekten gerekli olup olmadığına karar verilmeli, ardından bunu backend tarafta çözebilir miyiz ve hangisi performans olarak daha iyi olur bunu denemeliyiz. Yok ben bu sorguda Order by kullanmak zorundayım diyorsak eğer başka bir çözüm yöntemi parametre değiştirilip Clustered Index’deki sütun verilebilir(Primary Key) veya index’imizde order by için kullandığı sütun eklenebilir tabi eklerken sıralama türüne(asc,desc) dikkat edilmesi gerekmektedir.

8)Compute Scalar

Sorgumuzda alan birleştirme, farklı bir tanım yapma veya bir hesaplama işlemi(sum vb.) var ise Compute Scalar kullanılır. Maliyeti çok çok azdır. bundan dolayı üzerine çok durmayacağım sadece gördüğünüzde bu ne dememeniz için değindim.

9)Concatenation

Sık sık karşılaştığımız Concatenation operatörü birden fazla farklı veri setini kopyalayıp ardından birleştirip tek bir veri seti haline getirir. Tabi bazı şartları sağlaması gerekiyor, veriler farklı tablolardan getirilebilir ama bu verilerin tipi(bigint,nvarchar vb.) aynı olmak zorundadır. Çoğunlukla Union All kullanıldığı durumlarda karşılaşırız. Maliyeti azdır.

10)Top

Sorgu sonucunda elde edilen veri üzerinde sınırlama yapmaktadır. Sıralanmış veri üzerinden belirtilen sayı kadarını filtreleyip getirir. Kullanılmasını tavsiye ediyorum bütün veriyi getirmek yerine kısıtlı bir data işinize yarıyor ise kesinlikle kullanın derim. Maliyeti çok azdır.

Şimdi bu bahsettiğim 4 operatörü aşağıdaki örnekte uygulayacağım. Sorgu mantıklı bir iş için olmadığı belli ama 4 işlemi de görebilmek için hazırladım.

( SELECT TOP(100) (Title + ‘ ‘ + Description) as FullDescription,UserId FROM [Product] WHERE CategoryId=2999

UNION ALL

SELECT TOP(100) (Nickname + ‘ ‘ + Name) as FullName,Id FROM [User] ) ORDER BY UserId

Execution planımızı incelemek gerekir ise önce sağ taraftan başlayarak ilerleyeceğiz. İlk sorgumuz koşul olarak CategoryId kullanıyordu Seek işlemi yapıp hızlı bir şekilde getirmesi için CategoryId için indeks attık(included olarak getireceği sütunları yazarak key lookup yapmasını da engelledik). diğer sorgumuzda herhangi bir koşul olmadığından PK üzerinden clustered indexi kullanıyor.

Bir tık sola kaydığımızda Compute Scalar işlemini görüyoruz. her iki sorguda da iki sütunu birleştirip başka bir sütun üzerinde çıktı olarak verdiğinden dolayı Compute Scalar yapıyoruz ve maliyeti neredeyse yok diyebiliriz.

Ardından sadece ilk 100 kaydı almak için Top işlemini gerçekleştiriyor. ardından bu iki parça halindeki veri setlerini Concatenation ile birleştirecek. Burada önemli olan ilgili eşleşen sütunların aynı veri tipinde olmasıdır bunun dışında hangi tablodan geldiği sütun adının ne olduğu vesaire önemli değildir.

Son olarak birleştirdiğimiz veriler içerisinde UserId bazlı sıralama işlemini yapmak için Sort kullanıyoruz. Sorgumuzdaki en büyük maliyet Sort işleminde sebebi ise çok maliyetli bir işlem olduğundan değil diğer işlemlerin maliyetinin neredeyse yok denilecek kadar az olmasındandır.

Ve sıra geldi Join operatörlerine. Bildiğiniz üzere bir çok join türü mevcuttur(right,left,inner,outer vb.). Bu Join işlemleri sayesinde farklı tabloları aralarındaki bir bağlantı üzerinden birleştirmemize olanak sağlar. Daha demin bahsettiğim mantıksal işlemlerin gerçekleştirilmesi için ise 3 adet fiziksel işlemimiz bulunmaktadır(Hash Match, Merge Join, Nested Join). Hatırlarsanız serinin ilk yazısında tooltip üzerindeki Physical ve logical operation kavramlarını açıklarken join örneğini vermiştim. Şimdi bu üç fiziksel join örneğine yavaştan giriş yapalım.

11)Hash Match

Yine en kötü senaryo ile başlangıç yapalım. Execution planda eğer hash match görüyorsanız iyice bir incelemenizi öneririm sebebi ise diğer join türlerine göre hem maliyeti yüksek hem de bellek kullanımı. Hash Match çoğunlukla sıralı olmayan büyük tablolar üzerinde birleştirme işlemi gerçekleşeceği zaman kullanılır. Birleştireceği tablolar arasında öncelikli olarak küçük olan tabloyu alır ve bellekte hash function kullanarak bir hash tablo oluşturur. Ardından diğer tablo üzerinde teker teker gezer ve her veriyi aynı hash function’a sokar çıkan değer oluşturulan hash tablosunda mevcut ise bu kayıtları birleştirir. Bu sayede join işlemini gerçekleştirir ama kaynak kullanımı(cpu ve bellek) çok fazla olduğundan dolayı maliyeti yüksektir.

Peki ne yapabiliriz, öncelikle eğer bu iki tablo scan işlemleri sonucunda gelip birleştirilecek ise bu tablolar için index atılabilir bu sayede hem veri seti küçültülür hem de veri sıralanmış olur.

bertwagner.com

Örnek ile devam edelim, öncelikle tablolarımızdaki bütün index’lerimizi siliyoruz(clustered hariç). Product ve Category tabloları üzerinden Status’leri 1 olacak koşulu ile bir sorgu oluşturalım, bize çıktı olarak Category tablosundan Name sütununun ve Product tablosundan Title sütununun ilk 100 kaydını getirsin.

SELECT top(100) c.Name, p.Title FROM [Product] as p

INNER JOIN [Category] as c on c.Id=p.CategoryId

Where p.Status=1 and c.Status=1

Maliyetimizin yarısı Hash Match için harcanıyor bunu yok etmek için Category tablosuna Status üzerinden Product tablosuna ise Status ve Category üzerinden index atıyorum. Ayrıca bu indexleri kullanıp key lookup yapmaması için included olarak Select sorgusunda belirttikleri alanları eklemem lazım ama ben tüm geri kalan sütunları ekliyorum. Sebebi ise bir çok sorgumda Status koşulunu kullanacağımı biliyorum ve Status üzerinden included bölümleri farklı bir çok farklı index oluşturmamak için bu kullanımı tercih ediyorum. Şimdi bir önceki sorgumuzu tekrar çalıştıralım.

Execution Planda join maliyeti %50den %0 oranına düşmüş oldu peki bu Nested Loops Nedir?

12)Nested Loops

Nested Loops, diğer bir join türümüz olup performans açısından Hash Match’e göre daha başarılı diyebiliriz. Burada iki tablodan birini Inner diğerini ise Outher olarak isimlendiriyor ve Outher tablosundaki her bir satırı tek tek alıp Inner tablosundaki tüm satırlar ile karşılaştırıyor. Eşleşen kayıtları ise birleştiriyor. Kısacası iç içe for kullanımını düşünebilirsiniz. Tabi bu gerçekleştirdiğimiz işlem Hash Match’e göre iyi olsa da CPU kullanımı yine de yüksektir.

bertwagner.com

Nested Loops kullanımını çoğunlukla key lookup kullanımından sonra da kullanılır bundan dolayı tekrar belirtiyorum doğru index kullanımı önemli. Ayrıca Nested Loops her zaman Hash Matchden iyidir diyemeyiz verinin çok büyük olduğu niş senaryolar bulunmaktadır.

13)Merge Join

Ve sıra geldi en az maliyetli olan sonuncu Join türümüze. Merge Join’in en büyük farkı eğer her iki tarafta da veri seti eşleşmeyi sağlayacak olan sütuna göre sıralı olmasıdır. Çünkü çalışma mantığı iki tablolada da sıra sıra gezip eşleşenleri ayırıyor yani Nested Loopstaki gibi bir tablodaki her satır diğer tablodaki her satırı gezmek zorunda kalmıyor. Big-O Notation olarak bakılırsa eğer en hızlısı Merge Join’dir.

bertwagner.com

Bunların dışında daha anlatılacak çok fazla operatör bulunmaktadır. Seriye belli bir süre ara verip başka konulardaki yazılar ile devam etmeyi planlıyorum. Ama son bölümde sorgularda kullanılan gruplama işlemleri ve system logları üzerine işinize yarayacak bir kaç queryden bahsedeceğim.

--

--