Skip to content

Bölüm 11 / 19

View, stored procedure, function

Veritabanı nesneleri ve kullanım yerleri.

19 dakikalık okuma

Bu üç nesne, sorguyu veritabanının içine yerleştirmenin üç ayrı biçimidir; üçünün de "sadece kod saklama" sanılması en yaygın yanlış anlamadır. Görünüm genelde hiçbir şey saklamaz — ama SQL Server'da indeksli görünüm yaptığında gerçekten diske yazılır ve karşılığında sert bir yasak listesi kabul edersin. Skaler fonksiyon okunabilirlik gibi görünür, oysa sorgunu seri plana düşürür. Bu ders bu üç nesnenin ne zaman doğru, ne zaman tuzak olduğunu iki motor üzerinden ayırıyor.

01Ne öğreneceksin

  • Görünümün gerçekte ne olduğu: sanal tablo, sorgu metnine gömülen tanım
  • SELECT * ile tanımlanan görünümün donması ve sp_refreshview
  • Güncellenebilir görünüm, WITH CHECK OPTION, PostgreSQL'in otomatik güncellenebilir görünüm kuralları ve INSTEAD OF tetikleyici
  • SQL Server indeksli görünümü: WITH SCHEMABINDING, benzersiz clustered indeks ve kabul etmediği her şey — OUTER JOIN, alt sorgu, DISTINCT, UNION, COUNT(*)
  • PostgreSQL materyalize görünümü ve REFRESH ... CONCURRENTLY şartı
  • Görünümün güvenlik katmanı olarak kullanımı ve satır düzeyi güvenlik
  • Stored procedure: plan yeniden kullanımı, SET NOCOUNT ON, sp_executesql ile dinamik SQL ve enjeksiyon
  • Fonksiyon türleri: skaler UDF (neden tehlikeli), satır içi TVF (neden iyi), çok deyimli TVF (neden yanlış tahmin üretir)
  • PostgreSQL'de FUNCTION/PROCEDURE ayrımı, volatility etiketleri ve SECURITY DEFINER + search_path açığı
  • Tetikleyicilerin küme mantığı ve tetikleyicinin OUTPUT'u kırması
  • "İş mantığı veritabanında mı, uygulamada mı" kararının gerçek ölçütleri

02Görünüm (view): adlandırılmış sorgu

Görünüm, bir SELECT deyimine ad vermektir. Kendi verisi yoktur; sorguladığında motor görünümün tanımını sorgunun içine yerleştirir ve tek bir plan üretir.

CREATE VIEW dbo.vw_AktifMusteri AS
SELECT m.Id, m.Ad, m.Eposta, m.Sehir
FROM   dbo.Musteri m
WHERE  m.SilindiMi = 0;

SELECT * FROM dbo.vw_AktifMusteri WHERE Sehir = 'Ankara';
-- Motorun gördüğü: SELECT ... FROM Musteri WHERE SilindiMi = 0 AND Sehir = 'Ankara'

Meşru kullanım alanları nettir: (1) karmaşıklığı gizlemek — beş tablolu bir join'i tek ad altında toplamak; (2) tutarlı iş kuralı — 8. derste konuştuğumuz soft delete filtresini tek yerde tutmak, böylece hiçbir geliştirici WHERE SilindiMi = 0 yazmayı unutamaz; (3) güvenlik — tabloya erişim vermeden yalnızca belirli sütun ve satırlara erişim vermek; (4) uyumluluk katmanı — tabloyu yeniden adlandırdığında eski adı bir görünümle yaşatmak.

Tuzak: SELECT * ile tanımlanan görünüm donar

2. dersin senedi: görünümü SELECT * ile tanımlarsan, SQL Server sütun listesini oluşturma anında çözer ve saklar. Sonradan tabloya sütun eklersen görünüm bunu görmez; daha kötüsü, tablodaki sütun sırası değişirse görünüm yanlış sütun adlarıyla doğru olmayan veri döndürebilir. Düzeltme EXEC sp_refreshview 'dbo.vw_...' çalıştırmaktır; kalıcı çözüm görünümlerde sütunları açıkça yazmaktır. PostgreSQL bu konuda daha katıdır: görünümün bağlı olduğu sütunu düşürmene ya da tipini değiştirmene doğrudan izin vermez (CASCADE demeden), yani sessiz bozulma yaşamazsın ama bu kez şema değişiklikleri zorlaşır.

Güncellenebilir görünüm ve WITH CHECK OPTION

Basit görünümler üzerinden INSERT/UPDATE/DELETE yapılabilir. SQL Server tek bir tabloya dokunulması ve toplama/DISTINCT/ GROUP BY içermemesi şartını arar; PostgreSQL'in "otomatik güncellenebilir görünüm" kuralları da benzerdir (tek FROM öğesi, pencere fonksiyonu ve toplama yok). Daha karmaşık görünümlerde iki motor da INSTEAD OF tetikleyici yazmanı ister (PostgreSQL'de ayrıca RULE mekanizması vardır ama önerilmez).

WITH CHECK OPTION, görünümün WHERE koşuluna uymayan satır yazılmasını engeller — yoksa görünüm üzerinden ekleyip görünümde göremediğin satırlar üretirsin:

CREATE VIEW vw_AnkaraMusteri AS
SELECT Id, Ad, Sehir FROM Musteri WHERE Sehir = 'Ankara'
WITH CHECK OPTION;

INSERT INTO vw_AnkaraMusteri (Ad, Sehir) VALUES ('Ali', 'İzmir');  -- reddedilir

03Görünüm performansı değildir — indeksli görünüm hariç

Sık rastlanan yanlış inanış, "görünüm sonucu saklar, o yüzden hızlıdır"dır. Sıradan bir görünüm hiçbir şey saklamaz; her sorguda alttaki sorgu çalışır. Üstelik iç içe görünümler (görünüm içinde görünüm içinde görünüm) iyileştiriciyi zorlar ve gereksiz tabloların plana girmesine yol açar.

Sonucu gerçekten saklamak istiyorsan iki motorun iki farklı aracı vardır ve ikisi de tam olarak birbirinin karşılığı değildir.

SQL Server: indeksli görünüm (materyalize görünüm)

Görünüme benzersiz bir clustered indeks eklersen görünüm gerçekten diske yazılır ve motor onu otomatik olarak günceller — alttaki tabloya yapılan her yazma işlemi görünümü de senkron olarak günceller. Karşılığında çok sert bir kural listesi kabul edersin:

CREATE VIEW dbo.vw_SehirToplam
WITH SCHEMABINDING                    -- zorunlu: iki parçalı ad + şema bağlama
AS
SELECT  s.Sehir,
        SUM(s.Tutar)   AS Toplam,     -- toplanan sütun NOT NULL olmalı
        COUNT_BIG(*)   AS Adet        -- COUNT(*) DEĞİL, COUNT_BIG(*)
FROM    dbo.Siparis s                 -- tek şemadan, iki parçalı ad
GROUP BY s.Sehir;
GO
CREATE UNIQUE CLUSTERED INDEX IX_vw_SehirToplam ON dbo.vw_SehirToplam (Sehir);

Kabul edilmeyenler — bu liste mülakatta doğrudan sorulur:

YasakSebepNe yaparsın
LEFT/RIGHT/FULL OUTER JOINEşleşmeyen satırların artımlı bakımı yapılamazYalnızca INNER JOIN; dış join'i uygulama katmanına taşı
Alt sorgu, türetilmiş tablo, CTEArtımlı bakım tanımsızGörünümü düzleştir
DISTINCTSatır kaybının izlenememesiGROUP BY ile yaz
UNION / UNION ALLAynıAyrı görünümler kur
COUNT(*)Bakım için satır sayacı gerekirCOUNT_BIG(*) yaz — zorunlu, GROUP BY varsa mutlaka bulunmalı
MIN, MAX, AVG, STDEVArtımlı güncellenemez (silmede yeniden hesap gerekir)SUM + COUNT_BIG tut, ortalamayı dışarıda böl
SUM nullable sütundaBakım belirsizleşirSütunu NOT NULL yap ya da ISNULL ile sar
Skaler UDF, deterministik olmayan işlev (GETDATE())Sonuç zamana bağlı olamazTarihi parametre olarak dışarıda uygula
Başka bir görünüme başvurma, OUTER APPLY, TOP—Düzleştir

Ek iki ayrıntı: (1) Görünümü oluştururken ve alttaki tabloları güncellerken yedi ANSI SET seçeneğinin doğru değerde olması gerekir (ANSI_NULLS, QUOTED_IDENTIFIER, ARITHABORT vb.); aksi hâlde tabloya yazma işlemi hata verir — eski ODBC/OLE DB istemcilerinden gelen bir uygulamanın "sadece bazı makinelerde INSERT patlıyor" hatasının klasik sebebidir. (2) Enterprise Edition, sorguda görünüm adı geçmese bile uygun sorgulara indeksli görünümü otomatik eşleştirir; Standard Edition bunu yapmaz, görünümü açıkça WITH (NOEXPAND) ipucuyla çağırmak gerekir.

Bedeli: indeksli görünüm senkron güncellenir; yani alttaki tabloya yapılan her INSERT aynı transaction içinde görünümü de günceller. Çok yazılan bir tabloda toplama görünümü kurmak, yazma işlemlerinde bir sıcak nokta (aynı Sehir satırına yönelen kilit) yaratır. Okuma ağırlıklı raporlarda muhteşem, yazma ağırlıklı OLTP'de tehlikelidir.

PostgreSQL: materyalize görünüm

PostgreSQL'de karşılığı MATERIALIZED VIEW'dur ve temel farkı otomatik güncellenmemesidir:

CREATE MATERIALIZED VIEW mv_sehir_toplam AS
SELECT sehir, sum(tutar) AS toplam, count(*) AS adet
FROM   siparis
GROUP  BY sehir
WITH DATA;

-- Tazeleme: tabloyu ACCESS EXCLUSIVE kilitler, süresince okunamaz
REFRESH MATERIALIZED VIEW mv_sehir_toplam;

-- Kilitsiz tazeleme için BENZERSİZ indeks ŞART:
CREATE UNIQUE INDEX ux_mv_sehir ON mv_sehir_toplam (sehir);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sehir_toplam;

Kıyaslama, iki motoru bilen birinin ayırt edici cevabıdır:

KonuSQL Server indeksli görünümPostgreSQL materyalize görünüm
GüncellikHer zaman güncel (senkron)Elle tazelenir — arada veri eskidir
Sorgu kısıtıÇok sert yasak listesiNeredeyse yok: dış join, alt sorgu, DISTINCT, UNION serbest
Yazma maliyetiHer DML'de ödenirYalnızca tazeleme anında
KilitYazan transaction'daREFRESH tam kilit; CONCURRENTLY için benzersiz indeks şart (ve daha yavaş)
Otomatik eşleşmeEnterprise'da var (NOEXPAND ile zorlanır)Yok — adıyla sorgulamalısın

Pratik sonuç: gerçek zamanlı doğruluk gerekiyorsa SQL Server indeksli görünümü, "5 dakikalık gecikme kabul" diyen bir gösterge paneli için PostgreSQL materyalize görünümü doğru araçtır. PostgreSQL'de gecikmeyi azaltmanın yolu REFRESH ... CONCURRENTLY işini zamanlanmış bir görevle (pg_cron ya da uygulamanın arka plan servisi) çalıştırmaktır.

Güvenlik katmanı olarak görünüm

Kullanıcıya tabloya değil görünüme yetki verirsen, hangi sütun ve satırları göreceğini denetlemiş olursun (Maas sütunu görünümde yoktur). Bunun daha gelişmiş hâli satır düzeyi güvenliktir: SQL Server 2016+ CREATE SECURITY POLICY ile, PostgreSQL ALTER TABLE ... ENABLE ROW LEVEL SECURITY + CREATE POLICY ile satır bazında filtre uygular ve bunu tüm sorgulara otomatik ekler. Çok kiracılı (multi-tenant) uygulamalarda "kiracı filtresini unutma" hatasını yapısal olarak imkânsız kılar.

04Stored procedure

Saklı yordam, veritabanında saklanan; parametre alan, birden çok deyim çalıştırabilen, transaction ve hata yönetimi yapabilen bir programdır.

CREATE OR ALTER PROCEDURE dbo.usp_SiparisOlustur
    @MusteriId int,
    @Tutar     decimal(19,4),
    @YeniId    int OUTPUT
AS
BEGIN
    SET NOCOUNT ON;          -- "n rows affected" mesajlarını kapat
    SET XACT_ABORT ON;       -- hata olursa transaction'ı tümüyle geri al

    BEGIN TRAN;
        INSERT INTO dbo.Siparis (MusteriId, Tutar, Durum)
        VALUES (@MusteriId, @Tutar, 'Bekliyor');

        SET @YeniId = SCOPE_IDENTITY();   -- @@IDENTITY DEĞİL (5. ders)

        UPDATE dbo.Musteri SET SonSiparis = SYSUTCDATETIME() WHERE Id = @MusteriId;
    COMMIT;
END;

SET NOCOUNT ON neden önemli? Her deyimden sonra sunucu istemciye "etkilenen satır sayısı" bilgi mesajı gönderir. Döngü içinde çalışan bir prosedürde bu, ağ gidiş-gelişini ve bazı istemci kütüphanelerinde beklenmeyen davranışı üretir. Alışkanlık olarak her prosedürün ilk satırıdır.

Plan yeniden kullanımı: prosedürün planı önbelleğe alınır — ama bunun bedeli 9. derste gördüğün parametre koklamasıdır. Prosedür "bazen hızlı bazen çok yavaş" çalışıyorsa ilk şüphelenilecek şey budur; müdahale araçları OPTION (RECOMPILE), OPTIMIZE FOR ve gerekiyorsa WITH RECOMPILE'dır.

Dinamik SQL ve enjeksiyon

Sütun adı ya da sıralama yönü gibi şeyler parametreleştirilemediği için bazen dinamik SQL gerekir. Yanlış ve doğru yol arasındaki fark hem güvenlik hem performanstır:

-- YANLIŞ: metin birleştirme → SQL enjeksiyonu + her değer için ayrı plan
SET @sql = N'SELECT * FROM Musteri WHERE Sehir = ''' + @sehir + '''';
EXEC (@sql);

-- DOĞRU: parametreli çalıştırma → enjeksiyon yok, plan yeniden kullanılır
SET @sql = N'SELECT Id, Ad FROM dbo.Musteri WHERE Sehir = @p_sehir';
EXEC sp_executesql @sql, N'@p_sehir nvarchar(100)', @p_sehir = @sehir;

-- Sütun/tablo adı parametre olamaz: BEYAZ LİSTE ile doğrula
IF @siralama NOT IN ('Ad', 'Olusturma') THROW 50000, 'Geçersiz sıralama', 1;
SET @sql = N'SELECT ... ORDER BY ' + QUOTENAME(@siralama);

QUOTENAME() tanımlayıcıyı köşeli parantezle güvenli hâle getirir; PostgreSQL karşılığı format('%I', ...) (tanımlayıcı) ve format('%L', ...) (değer) belirteçleridir. 6. hattaki "izin listesi" kuralının veritabanı tarafındaki karşılığıdır: kullanıcı girdisi asla tanımlayıcı olarak doğrudan kullanılmaz.

PostgreSQL'de fonksiyon ve prosedür

PostgreSQL uzun süre yalnızca FUNCTION'a sahipti; PG11 ile gerçek PROCEDURE geldi. Fark önemlidir: fonksiyon çağıran sorgunun transaction'ı içinde çalışır ve COMMIT yapamaz; prosedür CALL ile çağrılır ve içinde COMMIT/ROLLBACK yapabilir — parti parti işleyen bakım betikleri için tek doğru araç budur.

CREATE OR REPLACE PROCEDURE arsivle(p_gun int)
LANGUAGE plpgsql AS $$
DECLARE etkilenen int;
BEGIN
    LOOP
        DELETE FROM olay WHERE id IN (
            SELECT id FROM olay WHERE olusturma < now() - make_interval(days => p_gun) LIMIT 5000
        );
        GET DIAGNOSTICS etkilenen = ROW_COUNT;
        EXIT WHEN etkilenen = 0;
        COMMIT;                      -- yalnızca PROCEDURE içinde mümkün
    END LOOP;
END $$;

05Fonksiyonlar: hangisi güvenli, hangisi tuzak

SQL Server'da üç fonksiyon türü vardır ve performans açısından aralarında uçurum vardır:

TürYazımıDavranış
Skaler UDFRETURNS int AS BEGIN ... RETURN ... ENDTehlikeli: satır satır çalışır, iyileştirici içeriye bakamaz, sorguyu seri plana zorlar (2019 öncesi)
Satır içi TVFRETURNS TABLE AS RETURN (SELECT ...)İyi: parametreli görünüm gibidir, sorguya gömülür, tek plan
Çok deyimli TVFRETURNS @t TABLE(...) AS BEGIN ... ENDRiskli: tablo değişkeni gibi davranır, kardinalite tahmini sabittir (2014 öncesi 1, sonrası 100)

7. dersin senedini burada tamamlayalım: skaler UDF'nin sorunu yalnızca yavaşlık değildir, paralelliği tamamen kapatmasıdır. Milyon satırlık bir sorguda SELECT listesindeki tek bir skaler UDF, süreyi saniyelerden dakikalara çıkarır. SQL Server 2019'un "scalar UDF inlining" özelliği uygun fonksiyonları otomatik olarak sorguya gömer, ama WHILE döngüsü, tablo değişkeni ya da özyineleme içeren fonksiyonlar uygun değildir. Genel kural: skaler UDF yerine satır içi TVF yaz ve CROSS APPLY ile bağla.

-- Kötü: satır satır çalışır
CREATE FUNCTION dbo.fn_SonSiparisTutari(@musteriId int) RETURNS decimal(19,4)
AS BEGIN
    RETURN (SELECT TOP 1 Tutar FROM Siparis WHERE MusteriId = @musteriId ORDER BY Id DESC);
END;
SELECT m.Ad, dbo.fn_SonSiparisTutari(m.Id) FROM Musteri m;    -- seri plan, satır satır

-- İyi: satır içi TVF + APPLY — tek plan, paralelleşebilir
CREATE FUNCTION dbo.fn_SonSiparis(@musteriId int) RETURNS TABLE
AS RETURN (SELECT TOP 1 Tutar FROM Siparis WHERE MusteriId = @musteriId ORDER BY Id DESC);

SELECT m.Ad, s.Tutar FROM Musteri m CROSS APPLY dbo.fn_SonSiparis(m.Id) s;

PostgreSQL: volatility etiketleri ve güvenlik

PostgreSQL'de her fonksiyon üç kararlılık düzeyinden biriyle işaretlenir ve bu etiket iyileştiricinin davranışını doğrudan belirler:

EtiketAnlamıİyileştirici ne yapar
IMMUTABLEAynı girdi hep aynı çıktı, veritabanına bakmazSabit gibi önceden hesaplar; ifade indeksinde kullanılabilir
STABLETek deyim boyunca aynı sonuç (veri okuyabilir)Deyim içinde tekrar çağırmayabilir; indeks taramasında kullanılabilir
VOLATILEVarsayılan. Her çağrıda değişebilirHer satırda çağırır, hiçbir varsayım yapmaz

Yanlış etiket sessizce yanlış sonuç üretir: aslında tabloya bakan bir fonksiyonu IMMUTABLE işaretleyip onunla ifade indeksi kurarsan, tablodaki veri değiştiğinde indeks eski sonucu göstermeye devam eder ve sorgular yanlış satır döndürür. Bu, üretim hatası olarak bulunması en zor sınıflardandır.

SECURITY DEFINER açığı — güvenlik dersi ile kesişen klasik zafiyet: fonksiyonu SECURITY DEFINER ile tanımlarsan, çağıran kullanıcının değil fonksiyon sahibinin yetkileriyle çalışır. Fonksiyon içinde şema adı yazılmamış bir tablo/işlev çağrısı varsa, saldırgan kendi şemasını search_path'ın başına koyup aynı adlı sahte bir işlev tanımlayarak kodu sahip yetkisiyle çalıştırabilir. Korunma tektir ve mutlaka uygulanır:

CREATE FUNCTION yonetim.kullanici_sil(p_id int) RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog, yonetim      -- ZORUNLU savunma
AS $$ BEGIN DELETE FROM yonetim.kullanici WHERE id = p_id; END $$;

06Tetikleyiciler: küme mantığını unutma

Tetikleyici (trigger), bir tabloda DML olduğunda otomatik çalışan koddur. Güçlüdür ve tam da bu yüzden tehlikelidir: kodun görünmez bir yerinde çalışır, hata ayıklaması zordur ve transaction süresini uzatır.

SQL Server'da en sık yapılan hata: tetikleyici satır başına değil, deyim başına bir kez çalışır. inserted ve deleted sanal tabloları birden çok satır içerebilir. Şu kod 1 satırlık testte çalışır, 500 satırlık bir INSERT'te sessizce yanlış davranır:

-- YANLIŞ: tek satır varsayımı
CREATE TRIGGER trg_Siparis ON Siparis AFTER INSERT AS
BEGIN
    DECLARE @id int = (SELECT Id FROM inserted);        -- çok satırda hata/rastgele
    UPDATE Musteri SET SonSiparis = SYSUTCDATETIME() WHERE Id = @id;
END;

-- DOĞRU: küme mantığı
CREATE TRIGGER trg_Siparis ON Siparis AFTER INSERT AS
BEGIN
    SET NOCOUNT ON;
    UPDATE m SET m.SonSiparis = SYSUTCDATETIME()
    FROM Musteri m JOIN inserted i ON i.MusteriId = m.Id;
END;

PostgreSQL'de bu ayrım sözdiziminde yapılır: FOR EACH ROW (satır başına, NEW/OLD kayıtları) ya da FOR EACH STATEMENT (deyim başına, PG10+ geçiş tablolarıyla). Satır tetikleyicisi doğal olduğu için yukarıdaki hata orada oluşmaz, ama 500 bin satırlık bir yüklemede 500 bin kez çalışması performans sorunudur.

5. dersin senedi: tetikleyicisi olan bir tabloda OUTPUT yan tümcesini doğrudan istemciye döndüremezsin — SQL Server hata verir; OUTPUT ... INTO @tablo ile bir tabloya yazman gerekir. Kod bir gün tetikleyici eklendiğinde "sebepsizce" bozulur; bu yüzden OUTPUT kullanan üretim kodunda INTO biçimi tercih edilir.

07İş mantığı veritabanında mı olmalı?

Bu, ekiplerin en çok tartıştığı sorulardan biridir ve doğru cevap "duruma göre"dir — ama ölçütler nettir:

ÖlçütVeritabanı lehineUygulama katmanı lehine
Veri hacmiMilyonlarca satırı işleyip özet döndürmek — ağdan geçirmeAz satır, çok mantık
Gidiş-gelişTek çağrıda 10 adım (chatty API yerine)—
Yetki sınırıKullanıcıya tabloya değil yalnızca prosedüre yetki vermek—
Test edilebilirlik—Birim testi, hata ayıklama, sahte nesneler
Sürüm kontrolü—Kod deposu, kod incelemesi, dal yönetimi
Taşınabilirlik—Motor değişince T-SQL/PL-pgSQL yeniden yazılmaz
Ekip becerisiGüçlü DBA ekibiYazılım ağırlıklı ekip

Bugünkü yaygın pratik: iş kuralları uygulamada, veriye yakın toplu işlemler veritabanında. Ama veritabanı nesnelerini yazacaksan bunları da kaynak kontrolüne al — prosedür ve görünüm tanımları uygulama koduyla aynı depoda, betikler CREATE OR ALTER (SQL Server) / CREATE OR REPLACE (PostgreSQL) biçiminde idempotent yazılır ve göç aracıyla dağıtılır (16. derste EF Core migration'larıyla birlikte göreceğiz).

08Mini alıştırma — Üç nesneyi de sınırında dene

Yaklaşık 20 dakika

  1. Görünüm kur: vw_AktifMusteri'yi SELECT * ile tanımla. Sonra tabloya yeni bir sütun ekle ve görünümü sorgula — sütun göründü mü? sp_refreshview çalıştırıp tekrar bak.
  2. Güncellenebilirlik: tek tablolu görünüm üzerinden INSERT yap. Sonra GROUP BY'lı bir görünüm üzerinden dene — hata mesajı ne?
  3. WITH CHECK OPTION: şehir filtreli görünüme uymayan bir satır eklemeyi önce seçeneksiz, sonra seçenekle dene.
  4. İndeksli görünüm — yasak listesini kanıtla: SCHEMABINDING'li bir görünüm yaz ve sırayla LEFT JOIN, alt sorgu, DISTINCT, UNION ve COUNT(*) ekleyerek benzersiz clustered indeks oluşturmayı dene. Her biri için hata mesajını not et. COUNT(*) yerine COUNT_BIG(*) yazınca ne oldu?
  5. Otomatik güncelleme: indeksli görünüm kurulduktan sonra alttaki tabloya satır ekle ve görünümü sorgula — tazeleme komutu vermeden güncel mi?
  6. PostgreSQL karşılığı: aynı toplamı materyalize görünümle kur. Tabloya satır ekle, görünümü sorgula — değişti mi? REFRESH çalıştırıp tekrar bak.
  7. CONCURRENTLY: benzersiz indeks olmadan REFRESH MATERIALIZED VIEW CONCURRENTLY dene, hatayı oku; indeksi kurup tekrarla.
  8. Prosedür: usp_SiparisOlustur'u yaz, SET NOCOUNT ON olmadan ve olduğunda istemci çıktısını karşılaştır.
  9. Enjeksiyon: metin birleştirmeli dinamik SQL yaz ve parametre olarak ' OR 1=1 -- gönder. Sonra sp_executesql ile parametreli hâle getirip tekrarla.
  10. Skaler UDF maliyeti: 200 bin satırlık bir tabloda skaler UDF'li ve CROSS APPLY + satır içi TVF'li iki sorgunun süresini ve planını karşılaştır. Paralellik hangisinde var?
  11. Volatility (PostgreSQL): tabloya bakan bir fonksiyonu IMMUTABLE işaretleyip onunla ifade indeksi kur; tabloyu değiştirip sorguyu tekrar çalıştır — sonuç hâlâ doğru mu?
  12. Tetikleyici: tek satır varsayan tetikleyiciyi yaz, 1 satır ekle (çalışır), sonra 100 satırlık tek INSERT yap. Ne oldu? Küme mantıklı hâline çevirip tekrarla.
  13. OUTPUT kısıtı: tetikleyicili tabloda OUTPUT inserted.Id kullan, hatayı gör, OUTPUT ... INTO @t ile düzelt.

09Kendini sına

View nedir, performans kazandırır mı?

Görünüm, bir SELECT deyimine verilen addır; kendi verisi yoktur. Sorguladığında motor görünümün tanımını sorgunun içine yerleştirir ve tek bir plan üretir — yani sıradan görünüm performans aracı değildir, alttaki sorgu her seferinde çalışır. Faydası karmaşıklığı gizlemek, soft delete gibi bir iş kuralını tek yerde tutmak, tabloya erişim vermeden sütun/satır bazında yetki vermek ve yeniden adlandırılan tablolar için uyumluluk katmanı sağlamaktır. İki tuzağı: iç içe görünümler iyileştiriciyi zorlar ve gereksiz tabloları plana sokar; SELECT * ile tanımlanan görünüm sütun listesini oluşturma anında dondurur — tabloya sonradan eklenen sütunu görmez ve sütun sırası değişirse yanlış veri döndürebilir, düzeltmesi sp_refreshview, kalıcı çözümü sütunları açıkça yazmaktır.

SQL Server'da indeksli view nasıl yapılır, hangi sorguları kabul etmez? PostgreSQL'de karşılığı nedir?

Görünüm WITH SCHEMABINDING ile tanımlanır (tablolara iki parçalı adla başvurulur ve şema kilitlenir), ardından üzerine benzersiz clustered indeks oluşturulur; bu noktada görünüm gerçekten diske yazılır ve motor onu alttaki tablolara yapılan her yazmada senkron olarak günceller — yani sonuç her zaman günceldir. Karşılığında sert bir yasak listesi kabul edilir: LEFT/RIGHT/FULL OUTER JOIN, alt sorgu ve CTE, DISTINCT, UNION/ UNION ALL, TOP, MIN/MAX/ AVG, deterministik olmayan işlevler ve skaler UDF kullanılamaz; COUNT(*) yerine COUNT_BIG(*) yazmak zorunludur ve GROUP BY varsa mutlaka bulunmalıdır; toplanan sütunlar NOT NULL olmalıdır. Ayrıca yedi ANSI SET seçeneği doğru olmalıdır, yoksa alttaki tabloya yazma hata verir; otomatik plan eşleşmesi yalnızca Enterprise'da vardır, Standard'da WITH (NOEXPAND) gerekir. PostgreSQL'de karşılığı materyalize görünümdür ve temel farkı otomatik tazelenmemesidir: sorgu kısıtı neredeyse yoktur (dış join, alt sorgu, DISTINCT serbest) ama veriyi REFRESH MATERIALIZED VIEW ile elle güncellersin; bu komut tabloyu tam kilitler, kilitsiz sürümü olan REFRESH ... CONCURRENTLY için görünümde benzersiz indeks bulunması şarttır.

Stored procedure'ün avantajları ve dezavantajları nelerdir?

Avantajları: planın önbelleğe alınması, tek çağrıda çok adımlı işin yapılması (ağ gidiş-gelişini azaltır), veriye yakın toplu işlemlerde milyonlarca satırı ağdan geçirmeden işleme, ve yetki sınırı — kullanıcıya tablolara değil yalnızca prosedüre yetki verilebilir. Dezavantajları: birim testi ve hata ayıklaması zordur, sürüm kontrolü ve kod incelemesi ayrı disiplin gerektirir, motor değişince yeniden yazılır (T-SQL ile PL/pgSQL taşınmaz) ve iş mantığı iki yere dağılırsa bakım maliyeti artar. Ayrıca plan yeniden kullanımının kendisi bir risktir: parametre koklaması yüzünden "bazen hızlı bazen çok yavaş" davranış üretir, OPTION (RECOMPILE) veya OPTIMIZE FOR ile yönetilir. Pratik denge: iş kuralları uygulamada, veriye yakın toplu işler veritabanında; yazılan nesneler CREATE OR ALTER / CREATE OR REPLACE biçiminde idempotent betiklerle kaynak kontrolünde tutulur. Her prosedürün başına SET NOCOUNT ON ve genelde SET XACT_ABORT ON yazılır.

Skaler UDF neden tehlikelidir, yerine ne kullanılır?

Skaler kullanıcı fonksiyonu, sorgunun SELECT ya da WHERE kısmında kullanıldığında iyileştiricinin içeriye bakamadığı bir kara kutudur: SQL Server 2019 öncesinde satır satır, yorumlanarak çalışır ve daha kötüsü sorguyu seri plana zorlar — paralellik tamamen kaybolur, milyon satırlık bir sorgu saniyelerden dakikalara çıkar. Ayrıca maliyet tahminleri gerçeği yansıtmaz, bu yüzden plan seçimi de bozulur. 2019'un "scalar UDF inlining" özelliği uygun fonksiyonları sorguya gömer ama döngü, tablo değişkeni veya özyineleme içerenler uygun değildir. Doğru alternatif satır içi tablo değerli fonksiyondur (RETURNS TABLE AS RETURN (SELECT ...)): parametreli bir görünüm gibi davranır, sorguya gömülür, tek plan üretilir ve CROSS APPLY/OUTER APPLY ile bağlanır. Çok deyimli TVF ise ayrı bir tuzaktır: tablo değişkeni gibi davranır ve kardinalite tahmini sabittir (2014 öncesi 1 satır, sonrası 100), bu da kötü join seçimlerine yol açar. PostgreSQL tarafındaki karşılığı volatility etiketidir: fonksiyonu VOLATILE bırakmak her satırda çağrılmasına yol açar, STABLE/IMMUTABLE işaretlemek iyileştiriciye alan açar — ama yanlış etiketlemek sessizce yanlış sonuç üretir.

PostgreSQL'de SECURITY DEFINER fonksiyonlarda hangi güvenlik önlemi zorunludur?

SECURITY DEFINER ile tanımlanan fonksiyon, çağıran kullanıcının değil fonksiyon sahibinin yetkileriyle çalışır — genellikle yetkili bir rolün. Fonksiyonun gövdesinde şema adı yazılmamış bir tablo ya da işlev çağrısı varsa, düşük yetkili bir saldırgan kendi şemasını search_path'ın başına koyup aynı adlı sahte bir işlev tanımlayabilir; fonksiyon çalıştığında bu sahte kod sahibin yetkileriyle çalışır. Bu, PostgreSQL'de yetki yükseltmenin klasik yoludur. Korunma zorunludur ve tektir: fonksiyon tanımına SET search_path = pg_catalog, <şema> eklemek ve gövdedeki tüm nesnelere şema adıyla başvurmak. Ayrıca fonksiyonun EXECUTE yetkisi PUBLIC'ten alınıp yalnızca gereken role verilmelidir.

SQL Server tetikleyicilerinde en sık yapılan hata nedir?

Tetikleyicinin satır başına çalıştığını varsaymak. SQL Server'da AFTER/INSTEAD OF tetikleyicileri deyim başına bir kez çalışır ve inserted/deleted sanal tabloları etkilenen tüm satırları içerir. DECLARE @id int = (SELECT Id FROM inserted) biçimindeki kod tek satırlık testte çalışır, 500 satırlık tek bir INSERT'te ya hata verir ya da rastgele bir satırı işleyip diğerlerini sessizce atlar — sahada en pahalı veri bozulmalarından biridir. Doğrusu küme mantığıyla yazmaktır: UPDATE m ... FROM Musteri m JOIN inserted i ON .... PostgreSQL'de bu ayrım sözdiziminde yapılır (FOR EACH ROW / FOR EACH STATEMENT), bu yüzden aynı hata oluşmaz; oradaki risk, satır tetikleyicisinin toplu yüklemede yüz binlerce kez çalışmasıdır. Ek olarak, tetikleyicisi olan bir tabloda OUTPUT yan tümcesi doğrudan istemciye döndürülemez; OUTPUT ... INTO @tablo biçimi gerekir — mevcut kod, tabloya tetikleyici eklendiği gün "sebepsizce" bozulur.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) View nedir, avantajları nelerdir. (2) View ile tablo arasındaki fark. (3) Stored procedure nedir, avantaj ve dezavantajları. (4) Stored procedure ile function farkı — en klasik soru (fonksiyon değer döndürür ve sorgu içinde kullanılabilir, prosedür veri değiştirebilir ve EXEC ile çağrılır). (5) Trigger nedir, ne zaman kullanılır, sakıncaları. (6) Verilen senaryo için bir prosedür yazınız. (7) Materyalize/indeksli view nedir. (8) SQL enjeksiyonu nedir, dinamik SQL'de nasıl önlenir. (9) INSTEAD OF ve AFTER trigger farkı. (10) Veritabanı nesnelerine yetki verme (GRANT EXECUTE).

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Ayrım — "görünüm adlandırılmış bir sorgudur ve veri saklamaz; prosedür parametre alan, birden çok deyim ve transaction içerebilen bir programdır; fonksiyon ise değer döndürür ve sorgunun içinde kullanılabilir." (2) Kullanım — "görünümü karmaşıklığı gizlemek, soft delete gibi filtreleri tek yerde tutmak ve sütun/satır bazlı yetki vermek için kullanırım." (3) Kıdem vuruşu — "sonucu gerçekten saklamak gerekiyorsa SQL Server'da indeksli görünüm kurarım: SCHEMABINDING ve benzersiz clustered indeks gerekir, buna karşılık OUTER JOIN, alt sorgu, DISTINCT, UNION kabul edilmez ve COUNT(*) yerine COUNT_BIG(*) yazılır; kazancı motorun görünümü senkron güncellemesi, bedeli her yazmada ödenmesidir. PostgreSQL'de karşılığı materyalize görünümdür ama otomatik tazelenmez, REFRESH gerekir ve kilitsiz tazeleme için benzersiz indeks şarttır." (4) Tuzak — "skaler UDF'den kaçınırım, sorguyu seri plana düşürür; yerine satır içi TVF yazıp CROSS APPLY ile bağlarım. Tetikleyicileri küme mantığıyla yazarım, inserted birden çok satır içerebilir." (5) Karar — "iş kurallarını uygulama katmanında tutarım; veritabanına yalnızca veriye yakın toplu işleri koyar ve tüm nesneleri kaynak kontrolünde idempotent betiklerle yönetirim."