MariaDB ve MySQL’de VIEW Oluşturma ve Kullanım Senaryoları
Veritabanı yönetiminde zamanla karşılaştığım en büyük sorunlardan biri, uygulamaların doğrudan temel tablolara bağımlı hale gelmesiydi. Bir tablo yapısını değiştirmeniz gerektiğinde, bağımlı olan onlarca sorguyu güncellemeniz gerekiyordu. İşte tam bu noktada VIEW’lar hayat kurtarıcı oluyor. MariaDB ve MySQL’de VIEW’lar, karmaşık sorguları soyutlamanın, güvenliği artırmanın ve uygulama kodunu sade tutmanın en pratik yollarından biri.
VIEW Nedir ve Neden Kullanılır?
VIEW, bir ya da birden fazla tabloya dayanan ve sanki gerçek bir tabloymuş gibi sorgulayabileceğiniz sanal bir tablodur. Veritabanında fiziksel olarak veri depolamaz; bunun yerine tanımlı SQL sorgusunu her çağrıldığında çalıştırır.
VIEW kullanmanın sysadmin ve geliştirici perspektifinden birkaç önemli nedeni var:
- Güvenlik: Kullanıcılara tüm tabloyu değil, sadece ihtiyaç duydukları sütunları gösterebilirsiniz
- Soyutlama: Tablo yapısı değişse bile VIEW arayüzünü sabit tutabilirsiniz
- Sorgu basitleştirme: Sık kullanılan karmaşık JOIN sorgularını tek bir VIEW arkasına saklayabilirsiniz
- Tutarlılık: Birden fazla uygulama aynı VIEW’ı kullandığında, iş mantığı tek bir yerden yönetilir
- Bakım kolaylığı: VIEW tanımını güncelleyerek tüm bağımlı uygulamaları etkileyebilirsiniz
Temel VIEW Sözdizimi
Önce basit bir VIEW oluşturmakla başlayalım. Diyelim ki bir e-ticaret veritabanımız var ve müşteri bilgilerini içeren bir tablomuz mevcut.
-- Basit bir VIEW oluşturma
CREATE VIEW musteri_ozet AS
SELECT
musteri_id,
ad,
soyad,
email,
CONCAT(ad, ' ', soyad) AS tam_ad,
kayit_tarihi
FROM musteriler
WHERE aktif = 1;
-- VIEW'ı sorgulamak
SELECT * FROM musteri_ozet;
-- Belirli koşulla sorgulamak
SELECT tam_ad, email
FROM musteri_ozet
WHERE kayit_tarihi > '2024-01-01';
Dikkat edin, VIEW’ı oluşturduktan sonra normal bir tablo gibi sorgulayabiliyoruz. WHERE aktif = 1 filtresi VIEW tanımında yer aldığı için, bu VIEW’ı kullanan her sorgu otomatik olarak yalnızca aktif müşterileri görecek.
OR REPLACE ve IF NOT EXISTS Seçenekleri
Gerçek dünyada VIEW’ları güncellemeniz sıklıkla gerekecek. Bunun için CREATE OR REPLACE VIEW kullanmak en temiz yol.
-- Varsa güncelle, yoksa oluştur
CREATE OR REPLACE VIEW musteri_ozet AS
SELECT
musteri_id,
ad,
soyad,
email,
telefon,
CONCAT(ad, ' ', soyad) AS tam_ad,
kayit_tarihi,
DATEDIFF(NOW(), kayit_tarihi) AS uyelik_gun_sayisi
FROM musteriler
WHERE aktif = 1;
-- VIEW hakkında bilgi almak
DESCRIBE musteri_ozet;
-- VIEW tanımını görmek
SHOW CREATE VIEW musteri_ozet;
SHOW CREATE VIEW komutu özellikle production ortamlarında sorun giderirken çok işinize yarayacak. Mevcut VIEW’ın tam tanımını görmenizi sağlar.
Gerçek Dünya Senaryosu 1: Çok Tablolu Raporlama VIEW’ı
Bir müşteri siparişlerini takip eden sistem düşünelim. Her rapor için aynı uzun JOIN sorgusunu yazmak yerine, bir VIEW oluşturalım.
-- Siparis detaylarini gosteren kapsamli bir VIEW
CREATE OR REPLACE VIEW siparis_detay_raporu AS
SELECT
s.siparis_id,
s.siparis_tarihi,
s.durum,
CONCAT(m.ad, ' ', m.soyad) AS musteri_adi,
m.email AS musteri_email,
sd.urun_id,
u.urun_adi,
u.kategori,
sd.adet,
sd.birim_fiyat,
(sd.adet * sd.birim_fiyat) AS satir_toplam,
s.kargo_ucreti,
s.indirim_tutari,
(
SELECT SUM(sd2.adet * sd2.birim_fiyat)
FROM siparis_detaylari sd2
WHERE sd2.siparis_id = s.siparis_id
) + s.kargo_ucreti - s.indirim_tutari AS genel_toplam
FROM siparisler s
INNER JOIN musteriler m ON s.musteri_id = m.musteri_id
INNER JOIN siparis_detaylari sd ON s.siparis_id = sd.siparis_id
INNER JOIN urunler u ON sd.urun_id = u.urun_id
WHERE s.durum != 'iptal';
-- Bu VIEW ile kolayca rapor almak
SELECT
musteri_adi,
siparis_tarihi,
urun_adi,
adet,
genel_toplam
FROM siparis_detay_raporu
WHERE siparis_tarihi BETWEEN '2024-01-01' AND '2024-12-31'
AND durum = 'tamamlandi'
ORDER BY siparis_tarihi DESC;
Bu yaklaşımın güzelliği şu: Muhasebe departmanı, satış ekibi ve müşteri hizmetleri aynı VIEW’ı farklı filtrelerle kullanabilir. JOIN mantığını her seferinde yeniden yazmak zorunda kalmazlar.
Güvenlik Odaklı VIEW Kullanımı
Sistemlerimde sıklıkla uyguladığım bir pattern: Tablolara doğrudan erişim yerine VIEW üzerinden erişim vermek. Özellikle hassas veriler içeren tablolarda bu yaklaşım kritik önem taşıyor.
-- Calisanlar tablosu hassas bilgiler icerebilir
-- Sadece gerekli sutunlari gosteren VIEW olusturalim
CREATE VIEW calisan_dizin AS
SELECT
calisan_id,
ad,
soyad,
departman,
pozisyon,
dahili_telefon,
kurumsal_email
FROM calisanlar
WHERE aktif = 1;
-- Maas, TC kimlik, banka bilgisi gibi hassas alanlar gizlendi
-- Muhasebe için ayrı bir VIEW
CREATE VIEW calisan_maas_raporu AS
SELECT
calisan_id,
ad,
soyad,
departman,
maas,
ek_odeme,
maas + ek_odeme AS toplam_maas
FROM calisanlar
WHERE aktif = 1;
-- Kullanicilara tablo degil VIEW erişimi ver
GRANT SELECT ON sirket_db.calisan_dizin TO 'hr_readonly'@'%';
GRANT SELECT ON sirket_db.calisan_maas_raporu TO 'muhasebe_user'@'%';
-- Temel tabloya dogrudan erisimi engelle
REVOKE SELECT ON sirket_db.calisanlar FROM 'hr_readonly'@'%';
Bu yaklaşımla hr_readonly kullanıcısı sadece dizin bilgilerini görürken, muhasebe_user maaş detaylarına erişebiliyor ama TC kimlik numarası veya banka hesabı gibi bilgilere ulaşamıyor.
WITH CHECK OPTION Kullanımı
VIEW üzerinden veri değiştirme işlemi yapılabildiği durumlarda, WITH CHECK OPTION son derece önemli bir güvenlik katmanı ekliyor.
-- Sadece aktif urunleri gosteren updatable VIEW
CREATE VIEW aktif_urunler AS
SELECT
urun_id,
urun_adi,
kategori,
fiyat,
stok_miktari,
aktif
FROM urunler
WHERE aktif = 1
WITH CHECK OPTION;
-- Bu INSERT calismaz cunku aktif=0 VIEW kosulunu ihlal eder
-- INSERT INTO aktif_urunler (urun_adi, kategori, fiyat, stok_miktari, aktif)
-- VALUES ('Test Urun', 'Elektronik', 100, 50, 0);
-- HATA: CHECK OPTION failed
-- Bu INSERT calisir
INSERT INTO aktif_urunler (urun_adi, kategori, fiyat, stok_miktari, aktif)
VALUES ('Yeni Kulaklık', 'Elektronik', 299.90, 100, 1);
-- UPDATE de kontrol edilir
-- Aktif urunu pasife cekmek VIEW uzerinden mumkun olmayacak
UPDATE aktif_urunler SET aktif = 0 WHERE urun_id = 5;
-- HATA: CHECK OPTION failed
WITH CHECK OPTION olmasaydı, VIEW üzerinden yanlışlıkla aktif = 0 olan bir kayıt eklenebilir ya da güncellenebilirdi. Bu kontrol mekanizması, VIEW’ın kendi filtresini ihlal eden veri manipülasyonlarını engelliyor.
Gerçek Dünya Senaryosu 2: Performans İzleme VIEW’ları
Bir MySQL/MariaDB sunucusunu yönetirken sık sık aynı sistem sorgularını çalıştırıyorsunuz. Bu sorguları VIEW olarak kaydetmek ciddi zaman tasarrufu sağlıyor.
-- Veritabani buyukluklerini gosteren VIEW
CREATE VIEW veritabani_boyutlari AS
SELECT
table_schema AS veritabani,
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS boyut_mb,
ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 3) AS boyut_gb,
COUNT(*) AS tablo_sayisi
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
GROUP BY table_schema
ORDER BY boyut_mb DESC;
-- Indeks kullanimi olmayan tablolari bulan VIEW
CREATE VIEW indekssiz_tablolar AS
SELECT
t.table_schema AS veritabani,
t.table_name AS tablo,
t.table_rows AS tahmini_kayit,
ROUND((t.data_length + t.index_length) / 1024 / 1024, 2) AS boyut_mb
FROM information_schema.tables t
LEFT JOIN information_schema.statistics s
ON t.table_schema = s.table_schema
AND t.table_name = s.table_name
AND s.index_name != 'PRIMARY'
WHERE t.table_schema NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
AND s.table_name IS NULL
AND t.table_type = 'BASE TABLE'
AND t.table_rows > 1000;
-- Artik hizlica kontrol edebilirsiniz
SELECT * FROM veritabani_boyutlari;
SELECT * FROM indekssiz_tablolar ORDER BY tahmini_kayit DESC;
Bu VIEW’ları bir kez oluşturduğunuzda, information_schema tablosunu her sorgulayışınızda uzun SQL yazmak yerine iki kelimeyle istediğiniz bilgiye ulaşabiliyorsunuz.
VIEW’ların Güncellenmesi ve Silinmesi
VIEW yönetimi, production ortamında dikkatli ele alınması gereken bir konu. Özellikle bir VIEW’ı güncellediğinizde hangi uygulamaların etkileneceğini bilmeniz gerekiyor.
-- Mevcut VIEW'ı tamamen degistirme
ALTER VIEW musteri_ozet AS
SELECT
musteri_id,
ad,
soyad,
email,
telefon,
sehir,
CONCAT(ad, ' ', soyad) AS tam_ad,
kayit_tarihi,
son_giris_tarihi
FROM musteriler
WHERE aktif = 1 AND email_dogrulandi = 1;
-- VIEW'ı silme
DROP VIEW IF EXISTS eski_musteri_raporu;
-- Birden fazla VIEW'ı silme
DROP VIEW IF EXISTS gecici_view1, gecici_view2, eski_view3;
-- VIEW'lari listeleme
SELECT TABLE_NAME, VIEW_DEFINITION
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'eticaret_db';
-- Bir VIEW'a bagimli diger nesneleri kontrol etme
SELECT
REFERENCED_TABLE_NAME,
TABLE_NAME AS view_adi
FROM information_schema.VIEW_TABLE_USAGE
WHERE TABLE_SCHEMA = 'eticaret_db'
AND REFERENCED_TABLE_NAME = 'musteriler';
DROP VIEW IF EXISTS kullanmayı alışkanlık haline getirin. Eğer VIEW mevcut değilse hata yerine uyarı verir, bu da otomasyon scriptlerinde hayat kurtarır.
Nested VIEW’lar: VIEW Üzerine VIEW
Bazı durumlarda bir VIEW’ı başka bir VIEW’ın içinde kullanmak çok temiz bir mimari sunar. Ancak performans açısından dikkatli olmak gerekiyor.
-- Alt VIEW: Ham siparis istatistikleri
CREATE VIEW musteri_siparis_istatistik AS
SELECT
musteri_id,
COUNT(*) AS toplam_siparis,
SUM(toplam_tutar) AS toplam_harcama,
AVG(toplam_tutar) AS ortalama_siparis_tutari,
MAX(siparis_tarihi) AS son_siparis_tarihi,
MIN(siparis_tarihi) AS ilk_siparis_tarihi
FROM siparisler
WHERE durum = 'tamamlandi'
GROUP BY musteri_id;
-- Üst VIEW: Musteri segmentasyonu (alt VIEW kullanarak)
CREATE VIEW musteri_segmentleri AS
SELECT
m.musteri_id,
CONCAT(m.ad, ' ', m.soyad) AS musteri_adi,
m.email,
msi.toplam_siparis,
msi.toplam_harcama,
msi.son_siparis_tarihi,
CASE
WHEN msi.toplam_harcama >= 10000 THEN 'VIP'
WHEN msi.toplam_harcama >= 3000 THEN 'Premium'
WHEN msi.toplam_harcama >= 1000 THEN 'Standart'
ELSE 'Yeni'
END AS segment,
DATEDIFF(NOW(), msi.son_siparis_tarihi) AS son_alimdan_beri_gun
FROM musteriler m
LEFT JOIN musteri_siparis_istatistik msi ON m.musteri_id = msi.musteri_id
WHERE m.aktif = 1;
-- Segmentlere gore analiz
SELECT
segment,
COUNT(*) AS musteri_sayisi,
ROUND(AVG(toplam_harcama), 2) AS ort_harcama,
SUM(toplam_harcama) AS segment_toplam_gelir
FROM musteri_segmentleri
GROUP BY segment
ORDER BY segment_toplam_gelir DESC;
Bu nested VIEW yaklaşımı, iş mantığını katmanlara bölerek her VIEW’ın tek bir sorumluluğu üstlenmesini sağlıyor. CRM sisteminiz müşteri segmentlerini kullanırken, pazarlama aracınız ham istatistik VIEW’ını kullanabilir.
ALGORITHM Seçeneği: MERGE vs TEMPTABLE
MariaDB ve MySQL’de VIEW performansını etkileyen önemli bir seçenek var: ALGORITHM.
-- MERGE algoritması: VIEW sorgusu ana sorguyla birleştirilir
-- Genellikle daha performanslıdır
CREATE ALGORITHM = MERGE VIEW basit_musteri AS
SELECT musteri_id, ad, soyad, email
FROM musteriler
WHERE aktif = 1;
-- TEMPTABLE algoritması: Önce geçici tablo oluşturulur
-- GROUP BY, DISTINCT, agregasyon içeren VIEW'larda zorunlu olabilir
CREATE ALGORITHM = TEMPTABLE VIEW sehir_musteri_sayisi AS
SELECT
sehir,
COUNT(*) AS musteri_sayisi,
AVG(DATEDIFF(NOW(), kayit_tarihi)) AS ort_uyelik_gun
FROM musteriler
WHERE aktif = 1
GROUP BY sehir;
-- UNDEFINED: MySQL/MariaDB otomatik seçer (varsayılan, genellikle en iyi seçim)
CREATE ALGORITHM = UNDEFINED VIEW akilli_view AS
SELECT
musteri_id,
ad,
soyad,
email
FROM musteriler
WHERE aktif = 1;
Pratikte çoğu durumda ALGORITHM = UNDEFINED bırakın, veritabanı motoru kendisi en uygununu seçer. Ama GROUP BY veya DISTINCT içeren VIEW’larda, MERGE seçilememesi durumunda TEMPTABLE kullanılacağını bilmek EXPLAIN planlarını yorumlarken işinize yarar.
Bakım ve Sorun Giderme
Üretim ortamında VIEW’larla ilgili sıkça karşılaşılan problemler ve çözümleri:
-- Kirik VIEW'lari tespit etme
-- Temel tablo degistiginde VIEW bozulabilir
SELECT
v.TABLE_SCHEMA,
v.TABLE_NAME,
v.VIEW_DEFINITION
FROM information_schema.VIEWS v
WHERE v.TABLE_SCHEMA = 'eticaret_db';
-- VIEW bagimliklarini kontrol et
SELECT DISTINCT
TABLE_NAME AS view_adi,
REFERENCED_TABLE_SCHEMA AS kaynak_db,
REFERENCED_TABLE_NAME AS kaynak_tablo,
REFERENCED_COLUMN_NAME AS kaynak_sutun
FROM information_schema.VIEW_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'eticaret_db'
ORDER BY TABLE_NAME, REFERENCED_TABLE_NAME;
-- VIEW performansini analiz etme
EXPLAIN SELECT * FROM siparis_detay_raporu WHERE durum = 'tamamlandi';
-- Yavaş VIEW'ı debug etme
SET optimizer_trace = "enabled=on";
SELECT * FROM siparis_detay_raporu WHERE siparis_id = 12345;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
SET optimizer_trace = "enabled=off";
VIEW bağımlılıklarını düzenli kontrol etmek, tablo yapısı değişikliklerinden önce kritik önem taşıyor. Bir sütun adını veya tipini değiştirmeden önce bu VIEW’ı sorgulayan bir script çalıştırmayı alışkanlık haline getirin.
VIEW Kullanımında Sık Yapılan Hatalar
Yıllar içinde gördüğüm yaygın hatalar:
- Her şeyi VIEW yapmak: Basit, tek seferlik sorgular için VIEW gereksiz karmaşıklık yaratır
- Nested VIEW’larda aşırıya kaçmak: 4-5 katman VIEW üst üste geldiğinde performans ciddi biçimde düşebilir
- TEMPTABLE VIEW’a filtre uygulamak: TEMPTABLE algoritmalı bir VIEW üzerine WHERE koşulu koyduğunuzda, önce tüm geçici tablo oluşturulur, sonra filtre uygulanır
- Updatable VIEW varsayımı: Tüm VIEW’lar güncellenebilir değildir; JOIN, GROUP BY, DISTINCT, alt sorgu içerenler genellikle salt okunurdur
- VIEW üzerinde INDEX tanımlamak: VIEW’lara doğrudan index eklenemez; temel tablolardaki index’ler VIEW performansını etkiler
Updatable VIEW sınırlamalarını test etmek için:
-- Bu VIEW güncellenebilir mi kontrol et
SELECT TABLE_NAME, IS_UPDATABLE
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'eticaret_db';
-- GROUP BY iceren VIEW guncelleme denemesi - hata verecek
-- UPDATE sehir_musteri_sayisi SET musteri_sayisi = 10 WHERE sehir = 'İstanbul';
-- ERROR 1288 (HY000): The target table sehir_musteri_sayisi of the UPDATE is not updatable
Sonuç
VIEW’lar, doğru kullanıldığında veritabanı yönetimini ve uygulama geliştirmeyi ciddi ölçüde kolaylaştırıyor. Güvenlik katmanı olarak kullanıldığında hassas verileri koruyabiliyor, soyutlama aracı olarak kullanıldığında tablo yapısı değişikliklerinin etkisini minimize edebiliyor ve raporlama aracı olarak kullanıldığında karmaşık JOIN sorgularını tekrar tekrar yazmaktan kurtarıyor.
Ancak VIEW’lar her derde deva değil. Performans açısından kritik sorgularda EXPLAIN planlarını mutlaka inceleyin. TEMPTABLE algoritmasına düşen VIEW’ların büyük veri setlerinde beklenmedik yavaşlıklara neden olabileceğini aklınızdan çıkarmayın. Nested VIEW yapılarını mümkün olduğunca sade tutun ve VIEW bağımlılıklarını düzenli olarak belgeleyin.
Veritabanı yönetiminde VIEW kullanımını sistematik hale getirdiğinizde, hem güvenlik hem de bakım açısından çok daha yönetilebilir bir yapıya kavuşacaksınız. Özellikle birden fazla uygulamanın aynı veritabanını kullandığı senaryolarda VIEW’lar, veri katmanı ile uygulama katmanı arasındaki kontratı belirleyen kritik unsurlar haline gelir.
