21 Ağustos 2015 Cuma

Implicit Conversion neden "kaka"dır?

Selam arkadaşlar,

Şimdiye kadar bu ve buna benzer çok sorunu çözdüm, ama şimdi bir vakit aralığı buldum ve istatistikleriyle birlikte güzel bir örnek olacağını düşündüğüm için bunu özellikle sizlerle paylaşmak istedim. Lütfen bu örneği küçük bir ders gibi görün ve dikkatlice inceleyin. T-SQL'ı işinde kullanıyor olanlarınız için, kariyerinizin geri kalanında da çok işinize yarayacaktır.

Öncelikle "Implicit Conversion" nedir, kısaca önce bunu tanımlayayım. Diyelim ki aşağıdaki gibi bir tablonuz var:

CREATE TABLE dbo.Gonderilenler(
GonderiNo BIGINT,
)

CREATE TABLE dbo.Gonderilecekler(
GonderiNo VARCHAR(50),
)

Ve aşağıdaki gibi bir Stored Procedure (SP)'ünüz var:

CREATE PROCEDURE dbo.GonderiSorgula
@GonderiNo DECIMAL(11,0)
AS
SET NOCOUNT ON;

SELECT … FROM dbo.Gonderilenler WHERE GonderiNo = @GonderiNo;
SELECT … FROM dbo.Gonderilecekler WHERE GonderiNo = @GonderiNo;

Burada dikkatinizi çekmek istediğim şey, Gonderilenler ve Gonderilecekler tablolarının ikisinde de GonderiNo adında bir alanın bulunması, fakat bu iki alan için farklı veritiplerinin kullanılması. Maalesef birçok ortamda, aynı maksatla kullanılan ve farklı tablolarda bulunan alanlar için yazılımcıların farklı veritiplerini kullandığını görebiliyoruz. Yazılımcılar bu konuda gerçekten çok dikkatsiz olabiliyorlar. Örneğin bir ortamımda, aynı amaçla kullanılan alan için, farklı onlarca tabloda, farklı veritipleri kullanıldığını gördüm. Bu sorunu düzeltmek de ayrı bir sorun, bundan da belki başka bir yazımda bahsederim.

Nerede kalmıştık efendim? Evet, Implicit Conversion nedir, nasıl oluştur? Eğer yukarıdaki dbo.GonderiSorgula isimli SP'yi çalıştırırsanız, @GonderiNo değişkeninin veritipi ile dbo.Gonderilecekler tablosundaki GonderiNo alanının veritipleri uyuşmayacağı için nur gibi Implicit Conversion sorununuz olacaktır. SQL Server kendi içerisinde Numeric veritipi olan DECIMAL'ı, VARCHAR'a çevirecektir (Implicit Conversion tam olarak budur) ve bu nedenle de dbo.Gonderilecekler.GonderiNo alanı için güzel bir Index de olsa bu Index'ten faydalanılamayacak ve Index Scan işlemi yapılacaktır. Bu da, tüm tablonun taranması anlamına gelmektedir ve tablo ne kadar büyükse, o kadar büyük bir sorununuz var demektir. Implicit Conversion ile sadece IO değil, CPU sorunu da yaşarsınız. Örnekleri ve delilleriyle birlikte hepsini göstereceğim.

Bugün karşılaştığım sorunlu SP'nin bahsini ettiğim sorunlu kısmının önceki ve sonraki Execution Plan'larını da aşağıda görebilirsiniz. 

Aşağıdaki ekran görüntüsündeki INSERT operatörünün üstündeki üçgen içerisindeki ünlem işareti de Execution Planları incelerken her zaman dikkatinizi çekmeli. Eğer bu operatörün üstünde gelir ve beklerseniz bir Tooltip çıkacaktır (veya F4->Properties) ve orada size Implicit Conversion hakkında bir uyarı gösterilecektir. Tabii ki aşağıdaki senaryo için bu böyle. Başka senaryolarda ve operatörler için bu üçgen içerisindeki ünlem işareti "Spill to tempdb" gibi anlamlara da gelebilir.

SP'nin iyileştirmeden önceki Execution Plan'ı.

Aşağıda da, SP'nin yaptığım iyileştirme çalışmasından sonraki Execution Plan'ını görebilirsiniz.

SP'nin iyileştirmeden sonraki Execution Plan'ı.

Peki bu uyuşmazlığın, iyileştirme öncesindeki ve sonrasındaki etkisi nasıl? Lütfen aşağıda paylaştığım grafiğe bir gözatın. 

Bu Script kendi yazdığım bir Script. Bu Script sayesinde Cache'lenebilen sorguların ve nesnelerin eski istatistiklerine ve Execution Plan'larına ulaşabiliyorum. 
Grafiğin en üst kısmında, kırmızı ile işaretlediğim yerde SP'nin şu anki, yani iyileştirmeden sonraki masrafını göreceksiniz; alttaki kayıtlar ise, SP'nin iyileştirme öncesinde nasıl çalıştığına dair istatistikler. Gördüğünüz gibi kaynak kullanım masraflarındaki iyileşmeler binlerce kat.

Peki ne yaptımda Execution Plan Index Scan'i terk edip Index Seek yapmaya başladı? INSERT operatörünün üstündeki üçgen içerisindeki ünlem nereye gitti?

Benimki gibi gerçek hayat senaryolarında, her zaman her şey mükemmel olmuyor. Zaten öyle olsa, Implicit Conversion sorunu da yaşamıyor olurdum. Gerçek senaryomda, SP içerisinde sorgulanan tablonun ilgili alanının veritipini maalesef değiştiremiyorduk. Yani özellikle bir tablodaki ilgili alanın veritipi, diğer tablolarınkinden farklı kalmak durumundaydı. Nedenleri bana kalsın. Fakat benim her şeye rağmen bir çözüm üretmem gerekiyordu, çünkü ortada bir sorun vardı. Ben de en azından bu tablodaki ilgili alanın veritipini değiştirinceye kadar aşağıdaki çözümü ürettim. SP içerisine aşağıdaki gibi bir değişken tanımladım:

DECLARE @GonderiNo_str VARCHAR(50) = CAST(@GonderiNo AS VARCHAR(50));

Ve yukarıdaki senaryoya göre anlatacak olursak SP'min kodunu aşağıdaki gibi değiştirdim:

CREATE PROCEDURE dbo.GonderiSorgula
@GonderiNo DECIMAL(11,0)
AS
SET NOCOUNT ON;

DECLARE @GonderiNo_str VARCHAR(50) = CAST(@GonderiNo AS VARCHAR(50));

SELECT … FROM dbo.Gonderilenler WHERE GonderiNo = @GonderiNo;
SELECT … FROM dbo.Gonderilecekler WHERE GonderiNo = @GonderiNo_str;

Bu değişiklikten sonra bu SP'nin Execution Plan'ı yukarıda gösterdiğim gibi değişti.

SP'nin içerisindeki asıl @GonderiNo değişkeninin veritipi dbo.Gonderilenler tablosu için doğru olduğundan, bu için bu değişkeni kullanmaya devam ettim, ama dbo.email_gonderilenler tablosu için yukarıda tanımladığım yeni @GonderiNo_str veritipini kullandım. Böylece dbo.Gonderilecekler tablosu sorgulanırken Implicit Conversion yapılmamış ve varolan Index'ten en iyi şekilde faydalanılmış oldu. Evet, tek yaptığım buydu ve binlerce kat iyileşme elde ettim.

İyi kod yazmalar!

Ekrem Önsoy

Hiç yorum yok: