Function block with inputs and one output and a gear, representing SQL Server user-defined functions and their performance

SQL Server Fonksiyonları: Scalar ve Table-Valued Fonksiyonlar, Inlining ve Performans

SQL Server’da üç tür kullanıcı tanımlı fonksiyon, scalar UDF’lerin sorguları neden yavaşlattığı, scalar UDF inlining’in neyi değiştirdiği, inline TVF’ye dönüştürme ve maliyetli fonksiyonları bulma.

Mart 31, 2023

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.

SQL Server Performans Danışmanlığı

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.

Destek Alın

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.
Üç tür: tek bir değer döndüren scalar fonksiyonlar, tek bir SELECT’in sonucunu döndüren inline table-valued fonksiyonlar ve bir table variable’ı doldurup döndüren multi-statement table-valued fonksiyonlar.
Inline edilmemiş bir scalar UDF her satır için ayrı bir çalıştırma olarak bir kez çalışır, tüm sorguyu seri çalışmaya zorlar ve maliyetini execution plan’da gizler. SQL Server 2019 ve sonrası, compatibility level 150 veya üzerinde uygun scalar UDF’leri inline ederek bu yükün çoğunu ortadan kaldırabilir.
Inline TVF, SQL Server’ın çağıran sorgunun içine parametreli bir view gibi açtığı tek bir SELECT’tir. Multi-statement TVF ise bir table variable’ı birkaç adımda oluşturur ve ayrı çalışır; sabit satır tahmini çoğu zaman kötü planlara yol açar.
Hayır. Kullanıcı tanımlı fonksiyonlar gerçek tablolarda satır ekleyemez, güncelleyemez ya da silemez ve dinamik SQL çalıştıramaz. Veri değiştiren mantık için stored procedure kullanın.
Başka sorguların join edebileceği yeniden kullanılabilir sorgu mantığı gerektiğinde inline table-valued function kullanın. Veri değiştiren, transaction ya da hata yönetimi gerektiren veya bir uygulamaya sonuç döndüren iş mantığı için stored procedure kullanın.

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.

4.7/5