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ı vesp_refreshview- Güncellenebilir görünüm,
WITH CHECK OPTION, PostgreSQL'in otomatik güncellenebilir görünüm kuralları veINSTEAD OFtetikleyici - 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_executesqlile 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/PROCEDUREayrımı, volatility etiketleri veSECURITY DEFINER+search_pathaçığı - 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:
| Yasak | Sebep | Ne yaparsın |
|---|---|---|
LEFT/RIGHT/FULL OUTER JOIN | Eşleşmeyen satırların artımlı bakımı yapılamaz | Yalnızca INNER JOIN; dış join'i uygulama katmanına taşı |
| Alt sorgu, türetilmiş tablo, CTE | Artımlı bakım tanımsız | Görünümü düzleştir |
DISTINCT | Satır kaybının izlenememesi | GROUP BY ile yaz |
UNION / UNION ALL | Aynı | Ayrı görünümler kur |
COUNT(*) | Bakım için satır sayacı gerekir | COUNT_BIG(*) yaz — zorunlu, GROUP BY varsa mutlaka bulunmalı |
MIN, MAX, AVG, STDEV | Artımlı güncellenemez (silmede yeniden hesap gerekir) | SUM + COUNT_BIG tut, ortalamayı dışarıda böl |
SUM nullable sütunda | Bakım belirsizleşir | Sütunu NOT NULL yap ya da ISNULL ile sar |
Skaler UDF, deterministik olmayan işlev (GETDATE()) | Sonuç zamana bağlı olamaz | Tarihi 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:
| Konu | SQL Server indeksli görünüm | PostgreSQL materyalize görünüm |
|---|---|---|
| Güncellik | Her zaman güncel (senkron) | Elle tazelenir — arada veri eskidir |
| Sorgu kısıtı | Çok sert yasak listesi | Neredeyse yok: dış join, alt sorgu, DISTINCT, UNION serbest |
| Yazma maliyeti | Her DML'de ödenir | Yalnızca tazeleme anında |
| Kilit | Yazan transaction'da | REFRESH tam kilit; CONCURRENTLY için benzersiz indeks şart (ve daha yavaş) |
| Otomatik eşleşme | Enterprise'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ür | Yazımı | Davranış |
|---|---|---|
| Skaler UDF | RETURNS int AS BEGIN ... RETURN ... END | Tehlikeli: satır satır çalışır, iyileştirici içeriye bakamaz, sorguyu seri plana zorlar (2019 öncesi) |
| Satır içi TVF | RETURNS TABLE AS RETURN (SELECT ...) | İyi: parametreli görünüm gibidir, sorguya gömülür, tek plan |
| Çok deyimli TVF | RETURNS @t TABLE(...) AS BEGIN ... END | Riskli: 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:
| Etiket | Anlamı | İyileştirici ne yapar |
|---|---|---|
IMMUTABLE | Aynı girdi hep aynı çıktı, veritabanına bakmaz | Sabit gibi önceden hesaplar; ifade indeksinde kullanılabilir |
STABLE | Tek deyim boyunca aynı sonuç (veri okuyabilir) | Deyim içinde tekrar çağırmayabilir; indeks taramasında kullanılabilir |
VOLATILE | Varsayılan. Her çağrıda değişebilir | Her 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çüt | Veritabanı lehine | Uygulama katmanı lehine |
|---|---|---|
| Veri hacmi | Milyonlarca satırı işleyip özet döndürmek — ağdan geçirme | Az 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 becerisi | Güçlü DBA ekibi | Yazı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
- Görünüm kur:
vw_AktifMusteri'yiSELECT *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. - Güncellenebilirlik: tek tablolu görünüm üzerinden
INSERTyap. SonraGROUP BY'lı bir görünüm üzerinden dene — hata mesajı ne? WITH CHECK OPTION: şehir filtreli görünüme uymayan bir satır eklemeyi önce seçeneksiz, sonra seçenekle dene.- İndeksli görünüm — yasak listesini kanıtla:
SCHEMABINDING'li bir görünüm yaz ve sıraylaLEFT JOIN, alt sorgu,DISTINCT,UNIONveCOUNT(*)ekleyerek benzersiz clustered indeks oluşturmayı dene. Her biri için hata mesajını not et.COUNT(*)yerineCOUNT_BIG(*)yazınca ne oldu? - 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?
- 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. CONCURRENTLY: benzersiz indeks olmadanREFRESH MATERIALIZED VIEW CONCURRENTLYdene, hatayı oku; indeksi kurup tekrarla.- Prosedür:
usp_SiparisOlustur'u yaz,SET NOCOUNT ONolmadan ve olduğunda istemci çıktısını karşılaştır. - Enjeksiyon: metin birleştirmeli dinamik SQL yaz ve parametre olarak
' OR 1=1 --gönder. Sonrasp_executesqlile parametreli hâle getirip tekrarla. - 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? - Volatility (PostgreSQL): tabloya bakan bir fonksiyonu
IMMUTABLEişaretleyip onunla ifade indeksi kur; tabloyu değiştirip sorguyu tekrar çalıştır — sonuç hâlâ doğru mu? - Tetikleyici: tek satır varsayan tetikleyiciyi yaz, 1 satır ekle (çalışır),
sonra 100 satırlık tek
INSERTyap. Ne oldu? Küme mantıklı hâline çevirip tekrarla. OUTPUTkısıtı: tetikleyicili tablodaOUTPUT inserted.Idkullan, hatayı gör,OUTPUT ... INTO @tile 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."
