Veritabanı Yönetimi

    ORM mi Raw SQL mi

    ORM ile ham SQL arasındaki gerçek farklar ve hangi işte hangisinin kullanılacağı.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Bu tartışma yazılım ekiplerinde neredeyse dini bir hal alır: bir taraf ORM'in kodu okunur ve güvenli kıldığını söyler, diğer taraf ORM'in ne ürettiğini kimsenin bilmediğini ve üretimde asıl yavaşlığın oradan çıktığını savunur. İkisi de kısmen haklıdır ve ikisi de yanlış soruyu tartışır. Doğru soru "hangisi daha iyi?" değil, "hangi iş için hangisi?" sorusudur.

    Bu yazıda ORM ile ham SQL'i altı somut eksende karşılaştıracağım: ürettiği sorgunun performansı, SQL enjeksiyonu karşısındaki durum, bakım ve devir maliyeti, veritabanı taşınabilirliği, gelişmiş SQL özelliklerine erişim ve takımın gerçek beceri dağılımı. Ardından sahada en çok işe yarayan hibrit yaklaşımı, hangi işin hangi tarafa düştüğünü gösteren bir karar tablosuyla birlikte vereceğim. Kod örnekleri belirli bir çerçeveye bağlı kalmadan, ORM'lerin ortak kalıpları üzerinden ilerleyecek.

    İkisi Aslında Neyi Vaat Ediyor#

    ORM (Object-Relational Mapping), tablo satırlarını nesnelere çeviren bir katmandır. Vaadi şudur: veritabanı ayrıntılarıyla uğraşmadan, uygulama diliyle veri okuyup yazarsınız; şema değişiklikleri göç (migration) dosyalarıyla sürüm kontrolüne girer; ilişkiler nesne alanı gibi görünür ve sorgular tip güvenli bir arayüzden yazılır.

    Ham SQL'in vaadi ise tam kontroldür: veritabanına ne gönderdiğinizi harfi harfine bilirsiniz, motorun tüm yeteneklerini (pencere fonksiyonları, ortak tablo ifadeleri, motor özel ipuçları, kısmi indeksler) kullanabilirsiniz ve sorgu planını doğrudan hedefleyerek optimize edersiniz. Aradaki üçüncü seçenek olan sorgu üreticileri (query builder) ise, SQL'in yapısını korurken parametre bağlama ve dize birleştirme derdini üstlenir.

    -- Ham SQL: ne gönderdiğiniz belli
    SELECT u.id, u.ad, COUNT(s.id) AS siparis_sayisi
    FROM musteriler u
    LEFT JOIN siparisler s ON s.musteri_id = u.id AND s.durum = 'tamamlandi'
    WHERE u.olusturma_tarihi >= '2026-01-01'
    GROUP BY u.id, u.ad
    HAVING COUNT(s.id) > 3
    ORDER BY siparis_sayisi DESC
    LIMIT 50;
    

    Aynı sorguyu bir ORM'in akıcı arayüzüyle yazmak mümkündür ama satır sayısı genelde artar ve okuyan kişinin kafasında yine bu SQL'e çevrilmesi gerekir. İşte tartışmanın kalbi burada: soyutlama bir noktadan sonra sizi rahatlatmayı bırakıp araya giren bir çeviri katmanına dönüşür.

    Performans: Yavaş Olan ORM Değil, Ürettiği Sorgu#

    ORM'lerin "yavaş" olduğu iddiası çoğunlukla yanlış tarif edilmiş bir gerçeğe dayanır. Nesne oluşturma maliyeti gerçektir ama tipik bir web isteğinde bu maliyet mikrosaniyelerdir; asıl kayıp, ORM'in kaç sorgu ürettiğinde ve hangi kolonları çektiğinde yatar.

    En büyük tekil suçlu, döngü içinde ilişki okuyarak yüzlerce küçük sorgu üreten desendir; ayrıntılı anlatımı ve çözümleri için N+1 sorgu problemi yazısına bakın. İkinci suçlu, varsayılan olarak tüm kolonları çekmektir: 40 kolonlu bir tablodan üç kolon göstermek için SELECT * çalıştırmak, ağ üzerinde onlarca kat fazla veri taşır ve kapsayıcı indeks (covering index) kullanımını imkânsız kılar.

    -- ORM varsayılanı genelde budur
    SELECT * FROM urunler WHERE kategori_id = 12 ORDER BY fiyat LIMIT 20;
    
    -- İhtiyacınız buysa, sorgu tamamen indeksten karşılanabilir
    SELECT id, ad, fiyat FROM urunler WHERE kategori_id = 12 ORDER BY fiyat LIMIT 20;
    -- İşe yarayacak indeks:
    -- ALTER TABLE urunler ADD INDEX idx_kategori_fiyat (kategori_id, fiyat, ad);
    

    Üçüncüsü, toplu işlemlerdir. 10.000 satır eklerken ORM'in her satır için ayrı INSERT çalıştırması ile tek bir çok değerli INSERT arasındaki fark yüzlerce kattır:

    -- Satır satır ekleme: 10.000 gidiş-dönüş
    INSERT INTO log_kayitlari (mesaj, tarih) VALUES ('x', NOW());
    
    -- Toplu ekleme: tek gidiş-dönüş, çok daha hızlı
    INSERT INTO log_kayitlari (mesaj, tarih) VALUES
      ('x', NOW()), ('y', NOW()), ('z', NOW());
    

    Adil olmak gerekirse: ham SQL yazmak da otomatik hız getirmez. Kötü yazılmış ham bir sorgu, iyi kullanılmış bir ORM'den çok daha yavaştır. Belirleyici olan araç değil, sorgu planını okuyup okumadığınızdır; yöntem için MySQL yavaş sorgu bulma yazısı iyi bir başlangıç.

    Güvenlik: Enjeksiyon Tarafında Gerçek Durum#

    Yaygın inanış "ORM kullanırsan SQL injection olmaz" şeklindedir. Doğrusu şudur: parametre bağlama kullanırsan olmaz. ORM'ler bunu varsayılan olarak yaptığı için güvenlik avantajı sağlarlar, ama bu bir garanti değil, iyi bir varsayılandır. ORM'lerin ham sorgu kaçış kapıları vardır ve orada dize birleştirirseniz açık aynen doğar.

    -- Güvenli: değer ayrı gönderilir, asla SQL olarak yorumlanmaz
    SELECT id, ad FROM musteriler WHERE eposta = ?;
    
    -- Tehlikeli: dize birleştirme (ORM içinde de olsa aynı sonuç)
    -- SELECT id, ad FROM musteriler WHERE eposta = 'kullanici_girdisi'
    

    Bir ORM içinde bile şu tür kod yazılırsa açık gerçektir: sıralama yönünü (ORDER BY $kolon $yon), tablo adını ya da LIMIT değerini kullanıcı girdisinden alıp doğrudan sorguya gömmek. Parametre bağlama yalnızca değerler için çalışır; tanımlayıcılar (kolon/tablo adı) için çalışmaz. Bu yüzden sıralanabilir kolonları bir allowlist ile sınırlamak zorunludur:

    DurumParametre bağlama yeterli miNe yapmalı
    WHERE eposta = ?EvetBağlı parametre
    LIMIT ?Evet (emülasyon kapalıysa)Tam sayıya çevir, bağla
    ORDER BY <kolon>Hayırİzin verilen kolon listesi
    FROM <tablo>HayırSabit eşleme tablosu
    IN (?, ?, ?)EvetEleman sayısı kadar yer tutucu üret

    Buraya bir uyarı ekleyeyim: bazı sürücülerde emülasyonlu hazır ifadeler açıkken bağlı bir LIMIT ? değeri dize olarak tırnaklanır ve sorgu bozulur. PHP tarafında PDO::ATTR_EMULATE_PREPARES değerini false yapmak hem bu sorunu hem de gerçek sunucu tarafı hazırlığı getirir.

    Bakım, Devir ve Takım Gerçeği#

    Kod tabanının ömrü boyunca en pahalı şey yazma değil okuma ve değiştirmedir. ORM'in en güçlü tarafı burada ortaya çıkar: şema göçleri sürüm kontrolünde durur, ilişki tanımları tek yerde toplanır ve yeni gelen geliştirici tabloların birbiriyle ilişkisini modele bakarak anlar. Ham SQL'de aynı bilgi, onlarca dosyaya dağılmış sorguların içine gömülüdür.

    Öte yandan, "SQL bilmeyen bir takım ORM ile idare eder" varsayımı sahada tutmaz. ORM SQL'i gizlemez, erteler. Uygulama büyüdüğünde birileri mutlaka EXPLAIN çıktısını okumak, indeks tasarlamak ve kilitlenmeleri çözmek zorunda kalır. ORM kullanmak SQL öğrenmemek için bir gerekçe değil, çoğu işi hızlandıran bir kolaylıktır.

    Taşınabilirlik iddiası da genelde abartılıdır. Teoride ORM sayesinde MySQL'den PostgreSQL'e geçmek kolaydır; pratikte tam metin arama, JSON operatörleri, tarih fonksiyonları ve kilitleme davranışları farklıdır ve gerçek projelerin çoğunda taşıma yine elle yapılır. İki motorun somut farkları için MySQL ve PostgreSQL karşılaştırması yazısına bakabilirsiniz.

    Karşılaştırma ve Karar Ölçütleri#

    Aşağıdaki tablo, iki yaklaşımı gerçekten hissettiğiniz eksenlerde karşılaştırıyor. Üçüncü sütun olarak sorgu üreticisini de ekledim, çünkü pratikte en çok kullanılan orta yol odur:

    EksenORMSorgu üreticisiHam SQL
    Basit CRUD hızıEn yüksekYüksekDüşük
    Karmaşık rapor sorgusuZayıfOrtaEn iyi
    Enjeksiyon güvenliğiGüçlü varsayılanGüçlü varsayılanDisipline bağlı
    Sorgu üzerinde kontrolAzOrtaTam
    Şema göç yönetimiYerleşikGenelde yerleşikElle
    Motor özel özelliklerSınırlıKısmiTam
    Yeni geliştiriciye devirKolayKolaySQL bilgisi şart
    Hata ayıklamaÜretilen SQL'i görmek gerekirŞeffafŞeffaf

    Karar verirken şu üç soruyu sorun: Bu sorgu iş mantığının parçası mı yoksa raporlama mı? Sonucu nesne olarak mı kullanacağım yoksa doğrudan ekrana/JSON'a mı basacağım? Bu sorguyu altı ay sonra biri okuyacaksa hangi biçim daha anlaşılır olur? İlk soruya "raporlama", ikinciye "doğrudan basacağım" cevabı veriyorsanız ham SQL neredeyse her zaman doğru seçimdir.

    Hibrit Yaklaşım: Pratikte En İyi Cevap#

    Sahada en sağlıklı sonucu veren yaklaşım ikisini de kullanmak, ama sınırı net çizmektir. Uyguladığım kural şöyle:

    1. Tekil kayıt işlemleri ORM'de kalsın. Kayıt oluşturma, güncelleme, silme, doğrulama, olay tetikleme — burada ORM'in kolaylığı tartışmasızdır.
    2. Listeleme ve rapor sorguları ham SQL olsun. Toplama, gruplama, çoklu birleştirme içeren sorguları elle yazın ve sonucu nesneye çevirmeden kullanın.
    3. Ham SQL'ler tek bir katmanda toplansın. Denetleyici içine serpiştirilmiş SQL bakım kâbusudur; bir repository/query sınıfında toplayın.
    4. Her ham sorgu parametreli olsun. İstisnasız. Tanımlayıcılar için allowlist kullanın.
    5. Her ham sorgunun EXPLAIN çıktısı bir kez incelensin ve gereken indeks aynı değişiklikle eklensin.
    -- Rapor sorgusu: ORM'e çevirmeye çalışmayın, okunurluğu düşer
    WITH aylik AS (
      SELECT DATE_FORMAT(olusturma_tarihi, '%Y-%m') AS ay,
             COUNT(*) AS adet,
             SUM(tutar) AS ciro
      FROM siparisler
      WHERE durum = 'tamamlandi'
        AND olusturma_tarihi >= '2026-01-01'
      GROUP BY 1
    )
    SELECT ay, adet, ciro,
           ROUND(ciro / NULLIF(adet, 0), 2) AS ortalama_sepet
    FROM aylik
    ORDER BY ay;
    

    Bu ayrım aynı zamanda önbellekleme stratejinizi de netleştirir: rapor sorguları pahalı ama seyrek değişen sonuçlar üretir, yani önbelleklemeye en uygun olanlar onlardır. Uygulanışı için veritabanı önünde cache katmanı yazısına bakabilirsiniz.

    Sık Yapılan Hatalar#

    ORM'in ürettiği SQL'i hiç görmemek. Her ORM, çalıştırdığı sorguyu günlüğe yazma seçeneği sunar. Geliştirme ortamında bunu açık tutmayan bir ekip, üretimde ne çalıştırdığını bilmiyor demektir. Bu tek alışkanlık, ORM tartışmasının yarısını gereksiz kılar.

    Ham SQL'i "hızlı" diye seçip indeksi unutmak. Elle yazılmış kötü bir JOIN, ORM'in ürettiği sorgudan yavaş olabilir. Ham SQL kontrol verir; kontrolü kullanmazsanız sadece soyutlamayı kaybetmiş olursunuz.

    Sıralama kolonunu kullanıcıdan alıp doğrudan gömmek. ORDER BY enjeksiyonu, listeleme sayfalarında en sık rastlanan gerçek açıklardan biridir ve parametre bağlama bunu çözmez. İzin verilen kolon listesi zorunludur.

    Her şeyi tek tarafa yıkmak. Tüm sorguları ORM'e zorlamak karmaşık raporları okunmaz hale getirir; her şeyi ham SQL yapmak da basit CRUD'u gereksiz yere uzatır ve enjeksiyon riskini artırır. Sınırı iş türüne göre çizin.

    Göçleri ORM dışında yapmak. Hibrit çalışırken bile şema değişikliklerini göç dosyalarında tutun. Elle çalıştırılan ALTER TABLE ifadeleri, ortamlar arasında şema farkı yaratır ve bu farkı fark ettiğiniz an genelde üretimde bir hata aldığınız andır.

    Sıkça Sorulan Sorular#

    ORM kullanmak performansı ne kadar düşürür#

    Nesne oluşturma maliyeti tipik bir web isteğinde ihmal edilebilir düzeydedir; ölçülebilir kayıp neredeyse her zaman üretilen sorgudan gelir. Gereksiz kolon çekmek, döngü içinde ilişki okumak ve satır satır ekleme yapmak, ORM'e atfedilen yavaşlığın gerçek nedenleridir. Bu üç deseni düzelten bir ORM kullanımı, elle yazılmış SQL'e çok yakın performans verir.

    ORM kullanırsam SQL injection'a karşı tamamen güvende miyim#

    Hayır, ama varsayılan olarak çok daha korunaklısınız. ORM'ler değerleri parametre olarak bağlar ve bu enjeksiyonu engeller. Ancak ham sorgu kaçış kapılarında dize birleştirirseniz ya da sıralama kolonu, tablo adı gibi tanımlayıcıları kullanıcı girdisinden alırsanız açık aynen oluşur. Tanımlayıcılar için mutlaka izin verilen değer listesi kullanın.

    Raw SQL yazarsam veritabanı değiştirmem zorlaşır mı#

    Bir miktar zorlaşır ama bu genelde abartılır. ORM kullansanız bile tam metin arama, JSON işlemleri, tarih fonksiyonları ve kilitleme davranışları motorlar arasında farklıdır ve gerçek bir taşıma her durumda elle inceleme gerektirir. Motor değiştirme ihtimaliniz gerçekten yüksekse ham SQL'leri tek bir katmanda toplayın; taşıma o zaman sınırlı sayıda dosyayı ilgilendirir.

    Query builder kullanmak ORM ile ham SQL arasında iyi bir orta yol mu#

    Evet, çoğu proje için en dengeli seçenektir. Sorgunun yapısını SQL'e yakın tutarken parametre bağlamayı ve dize birleştirme riskini üstlenir, ayrıca dinamik koşullar eklemeyi kolaylaştırır. Karmaşık pencere fonksiyonları ve ortak tablo ifadelerinde yine ham SQL'e düşmeniz gerekebilir; bu tamamen normaldir.

    ORM'in ürettiği sorguyu nasıl görebilirim#

    Hemen her ORM'de sorgu günlüğü ya da hata ayıklama aracı bulunur ve çalıştırılan SQL'i parametreleriyle birlikte gösterir. Bu yeterli değilse veritabanı tarafından bakabilirsiniz: test ortamında genel sorgu günlüğünü kısa süreliğine açmak ya da performance_schema özet tablolarını okumak, uygulamanın gerçekte ne gönderdiğini kesin biçimde gösterir.

    Küçük bir projede hangisiyle başlamalıyım#

    ORM ile başlayın. Küçük projede zaman kazandırır, şema göçlerini düzene sokar ve güvenli varsayılanlar sunar. Uygulama büyüdükçe rapor ve listeleme sorgularını tek tek ham SQL'e taşıyın; bu geçiş kademeli ve sorunsuz yapılabilir. Tersi yön, yani ham SQL ile başlayıp sonradan ORM'e geçmek çok daha zahmetlidir.

    Kapanış#

    ORM ile ham SQL arasında kazanan bir taraf yok; ikisini nerede kullanacağını bilen ekip var. Aklınızda kalması gereken dört alışkanlık şu: ORM'in ürettiği SQL'i her zaman görün, tekil kayıt işlemlerini ORM'de rapor sorgularını ham SQL'de tutun, tüm değerleri parametreyle bağlayıp tanımlayıcılar için izin listesi kullanın ve yazdığınız her önemli sorgunun EXPLAIN çıktısını bir kez okuyun.

    Uygulama katmanını iyileştirdikten sonra darboğaz gerçekten donanımda kalıyorsa, daha fazla bellek ve hızlı NVMe disk için VDS ve bulut sunucu paketlerimize bakabilirsiniz; yoğun katalog ve sepet sorguları olan projeler için e-ticaret hosting çözümümüz hazır yapılandırmayla gelir. Sorgu profilleme, indeks tasarımı ve veritabanı ayarlarını bizim üstlenmemizi isterseniz sunucu yönetimi hizmetimiz bu kapsamda.

    ORMSQLYazılım Mimarisi

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.