İÇİNDEKİLER
- •Fonksiyonlar sorgularınızı yavaşlatıyor olabilir
- •Yerleşik ve Kullanıcı Tanımlı Fonksiyonlar
- •Scalar Fonksiyonlar: Klasik Performans Tuzağı
- •SQL Server 2019 ve sonrasında scalar UDF inlining
- •Scalar UDF’yi inline TVF olarak yeniden yazmak
- •Inline Table-Valued Fonksiyonlar: Hızlı Olan Tür
- •Multi-Statement Table-Valued Fonksiyonlar
- •WHERE Koşullarında Fonksiyonlar
- •Fonksiyonların Yapamadıkları
- •Fonksiyon mu, View mu, Stored Procedure mı?
- •Maliyetli Fonksiyonları Bulmak
- •Fonksiyon Kontrol Listesi
- •Sizi yavaşlatan fonksiyonları bulun
Kullanıcı tanımlı fonksiyonlar (UDF), T-SQL mantığını paketleyip sorgularda yeniden kullanmanızı sağlar; tıpkı herhangi bir programlama dilindeki fonksiyonlar gibi. Bu kolaylığın SQL Server’da iyi bilinen bir bedeli vardır: fonksiyon türüne ve sürüme bağlı olarak bir fonksiyon sorguyu seri çalışmaya zorlayabilir, gerçek maliyetini execution plan’dan gizleyebilir ve her satır için bir kez çalışabilir. Aynı mantık farklı türde bir fonksiyon olarak yazıldığında yüzlerce kat hızlı olabilir.
Bu rehberde üç tür kullanıcı tanımlı fonksiyonu, her birinin nasıl çalıştırıldığını, SQL Server 2019 ve sonrasındaki scalar UDF inlining’in neyi değiştirdiğini, maliyetli fonksiyonların nasıl bulunacağını ve ne zaman fonksiyon yerine view ya da stored procedure kullanılması gerektiğini anlatıyoruz.
Fonksiyonlar sorgularınızı yavaşlatıyor olabilir
Aryasoft, maliyetli scalar ve multi-statement fonksiyonları bulur, inline alternatiflerle yeniden yazar ve sorgu planlarını paralel ve ölçeklenebilir hale getirir.
Yerleşik ve Kullanıcı Tanımlı Fonksiyonlar
SQL Server’da yüzlerce yerleşik fonksiyon vardır: CONCAT ve REGEXP_LIKE gibi metin fonksiyonları, DATEADD gibi tarih fonksiyonları, SUM gibi aggregate’ler ve ROW_NUMBER gibi window fonksiyonları. Motorun içinde uygulanırlar ve genellikle ucuzdurlar.
Kullanıcı tanımlı fonksiyonlar ise sizin yazdığınız T-SQL (ya da CLR) kodudur. Üç türü vardır ve aralarındaki fark, sözdiziminin düşündürdüğünden çok daha önemlidir:
| Tür | Döndürdüğü | SQL Server nasıl çalıştırır | Performans |
|---|---|---|---|
| Scalar UDF | Tek bir değer | 2019 öncesi: ana planın dışında, her satır için bir kez. 2019+: inline edilebilir | Inline edilmediğinde çoğu zaman en yavaş seçenek |
| Inline table-valued function (iTVF) | Tek bir SELECT’ten gelen tablo | Parametreli bir view gibi çağıran sorgunun içine açılır | Genellikle sorguyu elle yazmak kadar hızlı |
| Multi-statement table-valued function (mTVF) | Birkaç ifadeyle doldurulan bir table variable | Ayrı çalışır, sonra sonucu join edilir | Zayıf satır tahminleri; büyük sonuçlarda yavaş |
Scalar Fonksiyonlar: Klasik Performans Tuzağı
CREATE OR ALTER FUNCTION dbo.fn_CustomerTotal (@CustomerID int)
RETURNS decimal(18,2)
AS
BEGIN
RETURN (SELECT SUM(Amount) FROM dbo.Orders WHERE CustomerID = @CustomerID);
END;
GO
SELECT CustomerID, Name, dbo.fn_CustomerTotal(CustomerID) AS total
FROM dbo.Customers;
Inlining olmadan bu sorgu, fonksiyonun içindeki SELECT’i her müşteri için ayrı bir çalıştırma olarak bir kez çalıştırır. Üstelik inline edilmemiş bir scalar UDF:
- Sorgunun geri kalanı paralellikten fayda görecek olsa bile tüm sorguyu seri plana zorlar.
- Maliyetini gizler: execution plan fonksiyonu ucuz bir Compute Scalar olarak gösterir; fonksiyonun içindeki I/O sorgunun istatistiklerinde görünmez.
- İçindeki sorgu için çağıran sorgunun bağlamında satır tahmini almaz.
SQL Server 2019 ve sonrasında scalar UDF inlining
Compatibility level 150 ve üzerinde SQL Server, uygun scalar UDF’leri çağıran sorgunun içinde ilişkisel ifadelere otomatik olarak dönüştürebilir. Fonksiyonun gövdesi sorguyla birlikte optimize edilir, paralellik mümkün hale gelir ve satır başına çağrı ortadan kalkar. On kat ve üzeri kazançlar sık görülür.
Her fonksiyon uygun değildir. Bir fonksiyon, diğer nedenlerin yanında, GETDATE() ya da NEWID() gibi zamana bağlı veya yan etkili fonksiyonları çağırıyorsa, table variable kullanıyorsa, bir computed column ya da CHECK constraint içinde kullanılıyorsa veya çağıran sorgu onu belirli ifadelerde kullanıyorsa inline edilmez. Tahmin etmek yerine uygunluğu kontrol edin:
SELECT OBJECT_SCHEMA_NAME(m.object_id) + '.' + OBJECT_NAME(m.object_id) AS function_name,
m.is_inlineable,
m.inline_type
FROM sys.sql_modules AS m
JOIN sys.objects AS o ON o.object_id = m.object_id
WHERE o.type = 'FN'; -- scalar fonksiyonlar
Uyarı
Inlining uç durumlarda davranışı değiştirebilir ve birçok cumulative update’te düzeltme almıştır; bu yüzden compatibility level’ı yükselttikten sonra kritik fonksiyonları test edin. Bir fonksiyon sorun çıkarırsa inlining’i tüm veritabanı için kapatmak yerine yalnızca o fonksiyon için (WITH INLINE = OFF) ya da tek bir sorgu için (USE HINT(‘DISABLE_TSQL_SCALAR_UDF_INLINING’)) kapatın. Güvenli yükseltme adımlarını compatibility level rehberimizde anlatıyoruz.
Scalar UDF’yi inline TVF olarak yeniden yazmak
Eski sürümlerde ya da inline edilemeyen fonksiyonlarda en güvenilir çözüm, scalar fonksiyonu inline TVF olarak yeniden yazıp CROSS APPLY (satır dönmeyebiliyorsa OUTER APPLY) ile çağırmaktır:
CREATE OR ALTER FUNCTION dbo.itvf_CustomerTotal (@CustomerID int)
RETURNS TABLE
AS
RETURN (SELECT SUM(Amount) AS total FROM dbo.Orders WHERE CustomerID = @CustomerID);
GO
SELECT c.CustomerID, c.Name, t.total
FROM dbo.Customers AS c
OUTER APPLY dbo.itvf_CustomerTotal(c.CustomerID) AS t;
Optimizer artık tek bir sorgu görür. Her müşteri için ayrı bir arama yerine Orders üzerinde tek bir scan ve hash aggregate seçebilir ve paralel çalışabilir.
Inline Table-Valued Fonksiyonlar: Hızlı Olan Tür
Inline TVF, BEGIN ... END bloğu olmayan tek bir RETURN (SELECT ...) ifadesidir. SQL Server onu parametre alan bir view gibi çağıran sorgunun içine açar; index’ler, istatistikler ve paralellik normal şekilde çalışır. Satır döndüren yeniden kullanılabilir bir mantığa ihtiyacınız varsa seçmeniz gereken tür budur.
Multi-Statement Table-Valued Fonksiyonlar
Multi-statement TVF bir table variable tanımlar, onu bir veya daha fazla ifadeyle doldurur ve döndürür. Sonuç çalışma anında oluşturulan bir table variable olduğu için optimizer kaç satır döneceğini tahmin etmek zorundadır: tarihsel olarak sabit 1 satır, SQL Server 2014’ün cardinality estimator’ıyla birlikte 100 satır. 100 satıra göre planlanan join’ler, fonksiyon bir milyon satır döndürdüğünde kötü davranır.
Compatibility level 140’tan itibaren interleaved execution, uygun multi-statement TVF’leri önce çalıştırır ve sorgunun geri kalanını gerçek satır sayısıyla optimize eder. Bu yardımcı olur; ancak mantık tek bir sorguya sığıyorsa inline TVF yine daha iyi bir tasarımdır.
WHERE Koşullarında Fonksiyonlar
WHERE koşulunda ya da JOIN koşulunda bir kolona uygulanan her fonksiyon, kullanıcı tanımlı ya da yerleşik olsun, genellikle o kolonda index seek’i engeller; çünkü SQL Server karşılaştırma yapmadan önce fonksiyonu her satır için hesaplamak zorundadır:
-- OrderDate üzerinde seek yapılamaz: fonksiyon her satır için çalışır
WHERE dbo.fn_FiscalYear(OrderDate) = 2026
-- Sargable: aralığı bir kez hesaplayın, kolonu doğrudan karşılaştırın
WHERE OrderDate >= '20250701' AND OrderDate < '20260701'
Fonksiyonları kolonlara değil, parametrelere ve sabitlere uygulayın. Diğer yaygın sargable olmayan kalıpları index rehberimizde listeliyoruz.
Fonksiyonların Yapamadıkları
Fonksiyonların yan etkisiz olması amaçlanır. Kullanıcı tanımlı bir fonksiyonun içinde şunları yapamazsınız:
- Fonksiyon içinde tanımlanan bir table variable dışındaki tabloları değiştirmek (gerçek tablolarda INSERT, UPDATE ya da DELETE yok).
- EXEC ile dinamik SQL çalıştırmak ya da stored procedure çağırmak (durum değiştirmeyen extended procedure’ler hariç).
- TRY…CATCH kullanmak, geçici tablo oluşturmak ya da SET seçeneklerini değiştirmek.
- NEWID() ya da RAND() gibi deterministik olmayan fonksiyonları doğrudan çağırmak.
Çoğu fonksiyona WITH SCHEMABINDING eklemeye değer. Fonksiyonu referans verdiği nesnelerdeki değişikliklere karşı korur; veriye erişmeyen scalar fonksiyonlarda ise SQL Server’ın onları deterministik ve veri erişimsiz olarak işaretlemesini sağlayarak update planlarındaki gereksiz koruyucu operatörleri kaldırır.
Fonksiyon mu, View mu, Stored Procedure mı?
| İhtiyaç | En uygun seçim |
|---|---|
| Başka sorguların içinde kullanılan, parametreli yeniden kullanılabilir SELECT mantığı | Inline table-valued function |
| Parametresiz yeniden kullanılabilir SELECT mantığı | View |
| Veri değiştiren, transaction ya da hata yönetimi kullanan iş mantığı | Stored procedure |
| Satırda zaten bulunan değerler üzerinde bir hesaplama | Sorgu içinde ifade ya da computed column; scalar UDF ancak inline edilebiliyorsa |
| Sonuç kümesi döndüren karmaşık, çok adımlı mantık | Stored procedure ya da adım adım oluşturulan geçici tablo; multi-statement TVF değil |
Maliyetli Fonksiyonları Bulmak
sys.dm_exec_function_stats (SQL Server 2016 ve sonrası), cache’teki her scalar fonksiyonun kaç kez çalıştığını ve ne kadar CPU ve I/O kullandığını gösterir. Bir fonksiyondaki çok yüksek çalıştırma sayısı, satır başına çağrının imzasıdır:
SELECT TOP (20)
DB_NAME(fs.database_id) AS database_name,
OBJECT_NAME(fs.object_id, fs.database_id) AS function_name,
fs.execution_count,
fs.total_worker_time / 1000 AS total_cpu_ms,
fs.total_logical_reads,
fs.total_worker_time / NULLIF(fs.execution_count, 0) AS avg_cpu_us
FROM sys.dm_exec_function_stats AS fs
ORDER BY fs.total_worker_time DESC;
İpucu
Execution plan’da ucuz görünen ama pratikte yavaş olan bir sorguda, SELECT listesinde ve WHERE koşulunda scalar UDF arayın. Gerçek maliyetleri planın tahmini maliyetlerinde görünmez; bu yüzden performans çalışmalarında bu kadar sık gözden kaçarlar.
Fonksiyon Kontrol Listesi
- Scalar UDF’ler compatibility level 150 veya üzerinde inline edilebilir durumda (is_inlineable = 1) ya da inline TVF olarak yeniden yazılmış.
- Mantığın izin verdiği yerlerde multi-statement TVF’lerin yerini inline TVF’ler almış.
- WHERE ve JOIN koşullarında index’li kolonlara fonksiyon uygulanmıyor.
- Fonksiyonlar WITH SCHEMABINDING kullanıyor.
- Çok yüksek çalıştırma sayılı fonksiyonlar için sys.dm_exec_function_stats kontrol ediliyor.
- Veri değiştiren mantık fonksiyonlarda değil, stored procedure’lerde.
İlgili Yazılar
SQL Server Performans Danışmanlığı
Sizi yavaşlatan fonksiyonları bulun
Aryasoft’un kıdemli DBA ekibi gizli maliyeti yüksek scalar ve multi-statement fonksiyonları tespit eder, inlining uygunluğunu kontrol eder, inline alternatiflerle yeniden yazar ve sonuçları doğrular; böylece sorgularınız yeniden paralel çalışır ve ölçeklenir.