13 Ağustos 2024

SQL Hızlandırma İpuçları

 

SQL Hızlandırma İpuçları

Bu konuda birçok şey yazılabilir ve hala eksik kalabilir öncelikle onu belirtmek isterim.Ve DB’den DB’ye farkedebilir bazı şeyler ama biz genel olarak karşılaştığımız sorunları ve çözümleri üzerinden ilerleyelim umarım faydalı olur.

Bazı yöntemlerde sorgunuzu değiştirmeniz yeterli olabiliecekken bazılarında sizin veya DBA’nizin tablolara müdahele etmesi gerekebilir bunu unutmayın.

İpuçları yazmadan önce bahsetmek istediğim başka şeyler var.Bir SQL sorgusu nasıl çalışır bunu anlatmak istiyorum.

Bir sorguyu çalıştırdığınızda önce hata var mı diye kontrol edilir bu adımlar geçtikten sonra bu sorguyu çalıştıracak “motora” gönderilir.Fakat burada şöyle bir sıkıntı var sizin gönderdiğiniz sorgu mantıksal bir işlemdir bunun fiziksel olarak nasıl olacağına hangi yollar izlenerek bu sorgunun çalıştırıacağını “motor” birçok olası plan içinden seçer.Demekki biz bir sorguyu çalıştırdığımız anda bir çok plan oluşuyor en uygun ise en az maliyetli olan oluyor.Bu maliyet verdiğiniz hintlere,istatistiklere,indexlere bağlı olabiliyor.O yüzden her seferinde aynı sorguyu aynı plan ile çalıştırmayabilir bunu unutmayın.Biz hızlandırma derken aslında bu maliyeti düşürmeye çalışıyoruz 😊

 

 

·       Mümkün olduğunda ufak data setleri ile çalışın.

o   Bunu hemen biraz açmak istiyorum örneğin 50 sütunlu 100 milyon satırlı bir tablo ile 20 sütunlu 20 milyonluk iki tabloyu joinleyeceksiniz ama ilk tablodan sadece 1m diğerinden sadece 500k’lık bir kitleyi joinleyeceksek önce filtreleyip sonra joinlemek daha mantıklı olacaktır.

o   Bir diğer örnek ise örneğin bu iki tabloyu joinlediniz bir subqueryde üstte başka bir tablo ile joinleyeceksiniz sadece gerekli sütunları çağırın subquery’nin selectinde.Her sütun ek maliyet demektir.

·       Bir data setini bir kere kullanmaya dikkat edin.

o   Buna da hemen bir örnek verelim.Örneğin son 30 gün son 15 gün ve son 7 günün satış rakamlarına ihtiyacınız var.Bu durumda bu sorguyu 2 şekilde yazabilirsiniz.

Select

son30gun_satis,

son15gun_satis,

son7gun_satis

From

(

select count(*) son30gun_satis from satis_tablosu where satis_tarihi>=current_date-30

)   son30gun

Left join

(

select count(*) son15gun_satis from satis_tablosu where satis_tarihi>=current_date-15

)   son15gun on 1=1

Left join

(

select count(*) son7gun_satis from satis_tablosu where satis_tarihi>=current_date-15

)   son7gun on 1=1

Dikkat ederseniz satis_tablosunu 3 kere okudum bu x3 maliyet demektir.Peki ideal yöntem nedir?

select

count(*) son30gun_satis ,

count(case when satis_tarihi>=current_date-15 then id else null end ) son15gun_satis ,

count(case when satis_tarihi>=current_date-7 then id else null end ) son7gun_satis

from satis_tablosu

where satis_tarihi>=current_date-30

Gördüğünüz üzere tabloyu sadece 1 kere okudum.Unutmayın en çok maliyetlerden biri okuma sırasında meydana gelir.

 

o   Bir diğer önemli tek seferde okuma ise with’dir.Bir diğer ismi CTE(Common Table Expression).Eğer aynı subquery birden çok yerde kullanılacaksa bu durumda WITH ile hafızaya alıp tek bir kere okumak yine aynı okuma,join ve dönüşüm işlemlerini tekrar yapmakdan daha kısa sürecektir.Hemen bir örnek kod verelim.

Select

*

From table as aa

Left join (bir çok join ve dönüşüm içeren aynı subquery) as bb on aa.id=bb.id

Left join (bir çok join ve dönüşüm içeren aynı  subquery) as cc on aa.id=cc.id

Bunun yerine kod şöyle yazılmalıdır.Böylelikle tek bir kez hesaplama yapılır.

With subquery_hafizada as (bir çok join ve dönüşüm içeren aynı subquery)

Select

*

From table as aa

Left join subquery_hafizada as bb on aa.id=bb.id

Left join subquery_hafizada as cc on aa.id=cc.id

 

 

 

 

 

 

·       Where veya Joinde kullandığınız sütunları dönüşüm işlemlerine sokmayın.

Hemen bir örnek verelim.Aşağıdaki örnekte sütun1 indexli dahi olsa indexi kullanmayacaktır.Buda sorgunuzun performansını ciddi olarak azaltacaktır.Çünkü full scan yapacaktır.

Select

*

From table

Where CAST(sütun1 as DATE)=’2024-07-01’

Veya diğer bir örnek

Select

*

From table1

Left join table2 on table1.Ad=table2.Ad+table2.Soyad

·       ORDER BY kullanmayın!

o   İnanılmaz performans öldürücü methodtur zorunlu haller dışında kesinlikle kullanmayın yada çok ufak data setlerinde.

·       DISTINCT kullanmayın!

o   Çok ciddi performans düşürüdür kesinlikle kullanmayın dupliceliği çözebiliyorsanız çözün.

·       UNION kullanmayın

o   DISTINCT yaptığından yine performansı çok ciddi etkiler.

·       Indexli sütunlar kullanın.

o   Hem join hem wherede mümkün olduğunca sadece bu sütunları kullanmaya çalışın.Eğer yoksa yaratın çekinmeyin.

o   Fakat şunu unutmayın çok fazla index ciddi yer kaplar diskinizi doldurur herşeye index atılmamalıdır.

o   Eğer cadinatlity %30 üzerinde ise index mantıklı olabilir.Yani sadece 1 ve 0 olan bir sütuna index atmayın en az %30’u farklı değerlere sahip sütunlara index atın.

·       Partitionlı sütunlar kullanın.

o   Özellikle DWH yapılaırnda sıklıkla tercih ettiğimiz partitionlı tablolar partition sütunu kullanılmazsa yanıt bile vermeyecektir çoğu zaman muhakkak where ve joinlerinize ekleyin.

·       İstatistikleri güncellemeyi unutmayın.

o   İstatistikler en uygun execution planı seçmek için istatistiklere başvurur ne kadar güncel olursa o kadar uygun planı seçer.

·       IN yerine EXISTS kullanın.

o   IN yaptığınızda bir değeri bulduğunda dahi diğer satırları aramaya devam ederken EXISTS’de direk budum diyip sonraki değeri aramaya başlar o yüzden EXISTS her zaman daha hızlıdır.Hemen örnek bir kod yazalım.

Select

*

From table1

Where sutun1 IN (select sutun2 from table2)

Bunun yerine şöyle yazmak daha iyi olacaktır.

Select

*

From table1

Where exists (select 1 from table2 where table2.sutun2=table1.sutun1)

·       * kullanmayın.

o   Mümkünse hiç bir zaman selectlerinizde * kullanmayın bunun yerine sadece istediğiniz belirli sütunları isteyin.

·       Temp Tablolar kullanın

o   Temp tablolar veya geçici süreyle yarattığınız klasik tablolar kullanmaktan çekinmeyin.Özellikle ETL süreçlerinde sıklıkla kullandığımız bir methodtur inanılmaz performans arttırır işiniz bitince silersiniz.

o   Temp tablo olarak yarattığınızda session düştüğünde otomatik silinecektir ama uzun süren temp tablo yaratmalarında tavsiye etmem klasik olarak yaratın olurda session düşerse bir daha hesaplamak için dakikalar harcamazsınız kendiniz sonrasında silersiniz zaten.

·       DBLink Kullanmayın

o   Bazen ihtiyaçdan yapılabiliyor ama performans anlamında inanılmaz kötü mümkünse tabloyuda aynı veritabanınına alın network kaynaklı çok sorun yaşabilirsiniz.

·       View Kullanmayın

o   Viewler select veya joinlerde kullandığınızda her çağrılmada tekrar tekrar hesaplanıp geldiğinden önce view hesaplanıp sonrasında sizin query’iniz çalışır.Mümkünse tabloya basın viewlerinizi sonrasında tablodan çağırın bunun için elbette ETL süreci gerekir bunu yapamıyorsanız viewler üzerinde de index oluşturulabiliyor bu incelenebilir veya view’in performansı arttırılabilir.

·       Hintleri dikkatli olmak kaydı ile kullanın

o   Hintler hızlandıracağı kadar yavaşlatabilirde o yüzden çok dikkatli olun.Özellikle Oracleda PARALLEL,APPEND veya sql serverda NOLOCK,TABLOCKX gibi hintler çok yardımcı olurlar ama yerine göre tehlikelidir.

·       Table Scan yapıp yapmadığınıza dikkat edin

o   Eğer sorgunuz index veya partition kullanmıyorsa execution plana baktığınızda table scan görebilirsiniz bu ciddi bir sorundur.Eğer indexli bir tabloda indexli sütun üzerinden where yapmanıza rağmen table scan yapıyorsa bu durumda index rebuild edilebilir veya indexli sütunda dönüşüm yapılıp yapılmadığına bakılabilir.

·       Nested Loop Join kullanılıyor ise düzenleyin.

o   Çoğu DB’de 3 tip fiziksel join vardır.Bunlar İnner veya Left’den çok farklıdır.Inner veya Left mantıksaldır nasıl ilişkiye gireceklerini söylerken fiziksel join bunların nasıl okunup nasıl eşleştirileceğini gösterir.Nested loop iç içe iki for döngüsü gibi düşünebilir.bir tablodaki her bir kaydı diğer tabloda aratır.Hak vereceğiniz üzere bu 1 büyük 1 çok küçük tablo için mantıklıdır eğer sizin 2 büyük tablonuz nested loop yapıyorsa ya hintlerle bunu değiştirmesini zorlayabilirsiniz ya da index ve istatistisklerle başarabilirsiniz.Genelde DB’ler kolay kolay 2 büyük tablo için bu seçmez.

·       DML(DELETE,INSERT,UPDATE) log seviyesini en aza indirin.

o   Özellikle DWH’da log çok az önemlidir veri kaybı bizde kolay kolay söz  konusu değildir zaten kaynada vardır log minimum tutulaiblir bunun içinde Oracle’da APPEND,SQL Serverda TABLOCKX hintleri kullanılabilir tabi DB seviyesinde de minimum seçeneği seçilebilir.

·       DELETE yerine TRUNCATE edin.

o   Full tabloyu boşaltacaksanız TRUNCATE seçin log tutmaz.

·       Her zaman PL/SQL,T-SQL’lerinizde row by row değil bulk olarak işlemlerinizi yapın.

o   Örneğin bir tablonuz var 1m satır ve siz bir sütunu update edeceksiniz bu durumda 1m’lik bir loop’a sokup tek tek değil tek bir UPDATE sorgusunda bu işi yapın.

 

·       Like kullanmayın

o   Mümkünse like kullanmayın çünkü aranan string değerdeki tüm karakterileri incelediğinden çok yavaş olacaktır.Fakat içinde aratmayan sadece ‘XXX%’  bunun gibi yani XXX ile başlayanlar dediğimizde çok sorun yaşamayız çünkü tüm string aranmaz sadece ilk 3’e bakılır.

·       OR kullanmayın

o   Eğer OR ile yazmanız gereken bir ifade var ise bunu iki ayrı ifade ile yazıp UNION’lamak daha mantıklı olacaktır.Çünkü indexli olsalar dahi OR kullanımında indexler kullanılmayacaktır.Örnek verelim

Select

*

From table1 where sütun1=x or sütun2=y

Bunun yerine aşağıdaki daha mantıklı olacaktır.

Select

*

From table1 where sütun1=x 

Union

Select

*

From table1 where sütun2=y