İçeriğe Atla
Mustafa Erbay
Kariyer veritabani-derinlemesine · 8 dk okuma · görüntülenme Read in English

Veritabanı İşlem Yalıtım Seviyeleri: Neden Her Zaman Kritik?

Veritabanı işlem yalıtım seviyelerinin gerçek dünya uygulamalarındaki önemini, karşılaştığım sorunları ve doğru seçimin kariyerime etkilerini kendi…

100%

Veritabanı işlemlerinin yalıtım seviyeleri, genellikle yazılım geliştirmeye yeni başlayanların “detay” olarak gördüğü, ancak tecrübelendikçe ne kadar kritik olduğunu anladığım bir konu oldu. Yıllar içinde, bu basit görünen ayarın, bir uygulamanın veri bütünlüğünden performansına, hatta iş süreçlerinin doğruluğuna kadar nasıl derinlemesine etki ettiğini defalarca tecrübe ettim. Bu yazı, bana göre neden bu kadar önemli olduğunu ve kariyerimde hangi noktalarda karşıma çıktığını anlatacak.

Bir üretim ERP’sinde çalışırken, sevkiyat listeleriyle ilgili tutarsızlıklar yaşıyorduk. Raporu gün içinde birkaç kez çektiklerinde farklı toplamlar görünüyor, bu da büyük bir kafa karışıklığına neden oluyordu. İlk başta uygulama katmanında bir bug aradım, ancak root cause aslında veritabanının varsayılan işlem yalıtım seviyesinden ve eş zamanlı çalışan diğer işlemlerden kaynaklanıyordu. Bu tür ince detaylar, genelde kimsenin aklına gelmez ama sistemin kalbinde yatan sorunları çözmek için anahtar olabilir.

Veritabanı İşlem Yalıtım Seviyeleri: Temel Bir Bakış

İşlem yalıtım seviyeleri (Transaction Isolation Levels), veritabanındaki eş zamanlı işlemlerin birbirini ne kadar etkileyeceğini belirler. ANSI SQL standardı dört ana seviye tanımlar: Read Uncommitted, Read Committed, Repeatable Read ve Serializable. Ancak her veritabanı sistemi, bu seviyeleri kendi iç mimarisine göre farklı şekillerde uygular. Örneğin, PostgreSQL Read Uncommitted seviyesini desteklemez ve Read Committed varsayılanıdır.

Benim deneyimimde, bu seviyeler sadece teorik tanımlar değil, doğrudan uygulamanın güvenilirliğini ve veri bütünlüğünü etkileyen pratik kararlardır. Yanlış bir seçim, finansal raporlarda tutarsızlıklar, stok hataları veya müşteri verilerinde bozulmalar gibi ciddi sonuçlara yol açabilir. Bu yüzden, bir sistem tasarlarken veya mevcut bir sistemi debug ederken, bu seviyeleri anlamak ve doğru kullanmak bence olmazsa olmazdır.

Yukarıdaki sevkiyat senaryosunun kökü de buydu: raporlama işlemi READ COMMITTED seviyesinde çalışırken, gün içinde yeni kayıtlar ekleniyor ve bazıları güncelleniyordu. Rapor, her SELECT sorgusunda en son commit edilmiş veriyi gördüğü için, işlem süresi boyunca değişen veriler nedeniyle tutarsız sonuçlar üretiyordu. Bu, “Phantom Read” ve “Non-Repeatable Read” senaryolarına güzel bir örnektir ve bu konuyu daha derinlemesine araştırmam gerektiğinin ilk sinyali olmuştu.

READ COMMITTED ve Beklenmedik Sonuçları

READ COMMITTED, birçok veritabanının (PostgreSQL dahil) varsayılan yalıtım seviyesidir ve genellikle çoğu uygulama için yeterli kabul edilir. Bu seviyede, bir işlem yalnızca diğer işlemler tarafından commit edilmiş verileri görür. Bu, “kirli okumaları” (dirty reads) engeller; yani, henüz commit edilmemiş, potansiyel olarak geri alınabilecek verileri okumazsınız. Ancak, bu seviye “tekrarlanamayan okumalar” (non-repeatable reads) ve “hayalet okumalar” (phantom reads) sorunlarına açıktır.

Benim tecrübelerimde, READ COMMITTED seviyesinin getirdiği bu esneklik, bazen sinsi sorunlara yol açtı. Özellikle birden fazla SELECT sorgusu içeren uzun süreli işlemler veya raporlamalar söz konusu olduğunda, işlemin başında okuduğum bir verinin, işlemin ilerleyen aşamalarında başka bir işlem tarafından güncellenip commit edilmesiyle değiştiğini gördüm. Bu durum, veri analizi veya finansal raporlama gibi alanlarda kritik tutarsızlıklar yaratabiliyor.

Bunun klasik bir örneği, envanter yönetiminde stok kontrol edip rezervasyon yapan bir işlemdir: işlem önce ürünün mevcut stokunu sorgular, sonra müşteri için belirli bir miktarı ayırır ve ardından stok miktarını günceller. Eğer ilk sorgu ile güncelleme arasında başka bir işlem aynı ürünün stoğunu değiştirirse, READ COMMITTED seviyesinde çalışan işlem yanlış stok miktarıyla rezervasyon yapabilir. Bu da müşteriye “ürün mevcut” deyip sonra “stokta yok” deme riskini getirir.

BEGIN;
-- İlk SELECT: stok 100
SELECT stock_quantity FROM products WHERE id = 123;

-- Bu arada başka bir işlem stoğu 90'a düşürüyor ve commit ediyor.

-- İkinci SELECT: stok 90
SELECT stock_quantity FROM products WHERE id = 123;
COMMIT;

Yukarıdaki senaryo, READ COMMITTED altında aynı işlem içinde bile tutarsız okumaların nasıl ortaya çıkabileceğini gösteriyor. Eğer iş mantığı bu tür dalgalanmalara karşı hassassa, daha yüksek bir yalıtım seviyesine geçmek veya uygulama katmanında ek kilitleme mekanizmaları kullanmak gerekebilir. Benim için bu, sadece veritabanı ayarlarının değil, aynı zamanda iş akışının ve uygulamanın nasıl tasarlandığının da bu seviyelerle doğrudan ilişkili olduğunu gösterdi.

REPEATABLE READ: Tutarlılık İçin Bir Adım

REPEATABLE READ yalıtım seviyesi, READ COMMITTED’ın çözemediği tekrarlanamayan okuma sorununu gidermek için tasarlanmıştır. Bu seviyede, bir işlem başladığında okuduğu tüm veriler, o işlem bitene kadar aynı kalır. Yani, aynı işlemi içinde aynı SELECT sorgusunu kaç kez çalıştırırsanız çalıştırın, her zaman aynı sonuç kümesini alırsınız. Bu, özellikle raporlama ve analitik işlemlerde veri tutarlılığını sağlamak için çok önemlidir.

Ancak, REPEATABLE READ dahi “hayalet okumalar” (phantom reads) sorununa karşı her zaman tam koruma sağlamaz. Phantom reads, bir işlem boyunca bir sorgunun döndürdüğü satır kümesinin değişmesidir; yani, işlemin başında belirli bir koşulu sağlayan 5 satır varken, işlemin ortasında başka bir işlem o koşulu sağlayan yeni bir satır eklerse, aynı sorgu bu yeni satırı görebilir. PostgreSQL’in REPEATABLE READ uygulaması, SELECT sorguları için gerçekten phantom read’leri engeller (MVCC snapshot sayesinde), ancak diğer bazı veritabanı sistemlerinde durum farklı olabilir. Bu yüzden, standart tanımlamaları ve spesifik veritabanı davranışlarını iyi ayırt etmek gerekiyor.

Finansal hesaplama içeren karmaşık raporlarda bu seviye kritik hâle gelir. Bu raporların doğru olabilmesi için, rapor süresince okunan tüm finansal kayıtların sabit kalması gerekir. READ COMMITTED ile böyle bir rapor çalıştırıldığında, ara ara başka işlemler yeni gelir/gider kayıtları ekledikçe hesaplamalar sürekli değişebilir. REPEATABLE READ’e geçmek bu sorunu çözer.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
-- İlk SELECT: Tüm gelir kalemlerini al
SELECT SUM(amount) FROM transactions WHERE type = 'income' AND date BETWEEN '2026-01-01' AND '2026-01-31';

-- Bu arada başka bir işlem aynı tarihler için yeni bir gelir kaydı ekliyor ve commit ediyor.

-- İkinci SELECT: Aynı sorguyu tekrar çalıştır, sonuç ilkine eşit olmalı
SELECT SUM(amount) FROM transactions WHERE type = 'income' AND date BETWEEN '2026-01-01' AND '2026-01-31';
COMMIT;

Bu örnekte, REPEATABLE READ sayesinde ikinci SELECT sorgusu, ilk sorgunun gördüğü verinin snapshot’ını kullanarak aynı sonucu döndürecektir. Bu, raporlama veya belirli bir anlık durumu analiz etme ihtiyacı olan uygulamalar için büyük bir güvenilirlik sağlar. Ancak, bu seviyenin artan kaynak kullanımı ve potansiyel kilitlenme riskleri olduğunu da unutmamak gerekir. Her ne kadar PostgreSQL’in MVCC (Multi-Version Concurrency Control) yapısı bu riskleri minimize etse de, transaction retries için hazırlıklı olmak her zaman iyidir.

SERIALIZABLE: Güvenlik ve Performans Dengesi

SERIALIZABLE, ANSI SQL standardındaki en yüksek yalıtım seviyesidir. Bu seviyede, eş zamanlı çalışan tüm işlemler, sanki birbiri ardına sırayla (seri olarak) çalışmış gibi görünür. Bu, bir işlemin diğer bir işlem tarafından oluşturulan herhangi bir tutarsızlığı görmesini tamamen engeller; yani, dirty reads, non-repeatable reads ve phantom reads gibi tüm anormallikler ortadan kalkar. Veri bütünlüğünün mutlak öncelikli olduğu kritik uygulamalar için ideal bir seçimdir.

Ancak, bu güvenlik seviyesinin bir bedeli vardır: performans. SERIALIZABLE işlemleri, çakışan işlemler olduğunda serialization_failure hatasıyla karşılaşabilir ve geri alınmak zorunda kalabilir. Bu, uygulamanın bu tür hataları yakalayıp işlemi tekrar denemesi (retry logic) gerektiği anlamına gelir. Bu ekstra yük, özellikle yüksek eş zamanlılığa ve yoğun yazma işlemlerine sahip sistemlerde ciddi bir darboğaz yaratabilir.

Bir bankanın iç platformunda, karmaşık bir mutabakat süreci tasarlarken SERIALIZABLE kullanmak zorunda kaldım. Bu süreç, farklı hesaplar arasında para transferlerini ve bakiye güncellemelerini içeriyordu ve en ufak bir tutarsızlık kabul edilemezdi. İlk başta, serialization_failure hatalarını yeterince iyi ele almadığım için sık sık işlem kesintileri yaşıyorduk. Debug süreci, bu hataları yakalayıp işlemleri otomatik olarak yeniden denememiz gerektiğini öğretti.

import psycopg2
from psycopg2 import errors

def run_serializable_transaction(conn_string, query_func):
    for _ in range(5): # 5 deneme hakkı ver
        conn = None
        try:
            conn = psycopg2.connect(conn_string)
            conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
            cur = conn.cursor()
            query_func(cur)
            conn.commit()
            return True
        except errors.SerializationFailure:
            print("SerializationFailure detected, retrying...")
            if conn:
                conn.rollback() # İşlemi geri al
        except Exception as e:
            print(f"An unexpected error occurred: {e}")
            if conn:
                conn.rollback()
            return False
        finally:
            if conn:
                conn.close()
    print("Max retries reached, transaction failed.")
    return False

# Örnek kullanım
def my_complex_query(cursor):
    cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1;")
    cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2;")

# run_serializable_transaction("dbname=mydb user=myuser", my_complex_query)

Bu örnek, SERIALIZABLE seviyesinde bir işlem yaparken SerializationFailure hatalarını nasıl yakalayıp yeniden deneyeceğimizi gösteriyor. Bu basit retry mekanizması, uygulamanın dayanıklılığını ve güvenilirliğini önemli ölçüde artırdı. SERIALIZABLE kullanırken, uygulama katmanının bu tür hatalara karşı hazırlıklı olması gerektiğini unutmamak, benim için büyük bir ders oldu. Performans kayıplarını minimize etmek için, bu seviyeyi gerçekten ihtiyaç duyulan kritik işlemlerle sınırlı tutmak en akıllıca yaklaşımdır.

Gerçek Dünya Uygulamalarında Trade-off’lar ve Yanlış Anlamalar

Veritabanı yalıtım seviyelerini seçmek, her zaman bir trade-off meselesidir: veri tutarlılığı, eş zamanlılık ve performans arasında bir denge kurmak zorundasınız. “Her zaman en yüksek seviyeyi kullanın, çünkü en güvenlisi odur” gibi bir düşünce, pratikte çoğu zaman sürdürülemez performans sorunlarına yol açar. Diğer yandan, “varsayılan seviye her zaman yeterlidir” düşüncesi de, sinsi veri bozulmalarına zemin hazırlayabilir.

Yoğun veri girişi yapılan bir sistemde her şeyi SERIALIZABLE ile çalıştırmakta ısrar etmek tipik bir yanlıştır: sonuç olarak veritabanı kilitlenmeleri ve serialization_failure hataları nedeniyle kayda değer oranda işlem başarısızlığı ortaya çıkar. Oysa detaylı bir analiz ve profil çıkarma çoğu zaman, işlemlerin büyük kısmının READ COMMITTED veya belirli bölümlerin REPEATABLE READ ile yeterince güvenli olabileceğini gösterir. Doğru seviyeler seçildiğinde işlem başarısızlık oranı ihmal edilebilir bir düzeye iner.

Bu durum, yalıtım seviyesi seçiminin sadece teknik bir karar olmadığını, aynı zamanda iş süreçlerinin doğasını ve uygulamanın beklentilerini de yansıtması gerektiğini gösterdi. İş mantığı, hangi verilerin ne kadar süreyle tutarlı kalması gerektiğini dikte eder. Örneğin, bir kullanıcının sepetindeki ürün miktarının anlık olarak değişmesi kabul edilebilirken, finansal bir raporun iki kez çalıştırıldığında farklı sonuçlar vermesi kabul edilemez.

Veritabanı yalıtım seviyeleri konusunda doğru kararı vermek, aynı zamanda veritabanı performans optimizasyonunun da ayrılmaz bir parçasıdır. Gereksiz yere yüksek bir yalıtım seviyesi kullanmak, kilitleme çekişmelerini artırarak sistemin genel throughput’unu düşürebilir. Bu nedenle, her zaman en az kısıtlayıcı seviyeyi seçmeli, ancak bu seviyenin iş gereksinimlerini karşıladığından emin olmalıyız. Daha önce veritabanı performans optimizasyonunda karşılaştığım zorluklar yazımda bu dengeleri daha detaylı ele almıştım.

Kariyerimde Yalıtım Seviyelerinin Yeri: Neden Önemli Bir Bilgi?

Bu konuyu kariyer kategorisinde yazmamın özel bir nedeni var: veritabanı işlem yalıtım seviyelerini derinlemesine anlamak, bence bir yazılımcının veya sistem mimarının yetkinlik seviyesini doğrudan gösteren önemli bir ayıraçtır. Yüzeysel bilgiyle birçok uygulama geliştirebilirsiniz, ancak veri bütünlüğü sorunları ortaya çıktığında, bu detayları bilmek sizi diğerlerinden ayırır.

Benim kariyerimde, bu bilgi birçok kez fark yaratmamı sağladı. Finansal raporlarda periyodik olarak ortaya çıkan ve uzun süredir çözülemeyen küçük tutarsızlıklarda genellikle raporlama aracı, iş mantığı veya manuel veri giriş hataları suçlanır. Oysa çoğu zaman kök sebep, karmaşık bir çok adımlı işlemin READ COMMITTED seviyesinde çalışırken, eş zamanlı başka bir işlemin veriyi değiştirip raporun ortasında farklı bir snapshot almasına izin vermesidir. Bu tür sorunlar çoğunlukla basit bir SET TRANSACTION ISOLATION LEVEL komutuyla çözülür.

Bu tür bir anlayış, sadece bug çözmekle kalmaz, aynı zamanda daha sağlam ve ölçeklenebilir sistemler tasarlamanıza da yardımcı olur. Sistem mimarisi kararları alırken, bir monolith’ten microservice’lere geçiş yaparken veya event-sourcing gibi mimari desenleri uygularken, işlemlerin nasıl yalıtılacağını bilmek kritik öneme sahiptir. kurumsal yazılım mimarisi kararlarında deneyimlerim yazımda bu tür mimari seçimlerin altındaki motivasyonları daha detaylı anlatmıştım.

Bu bilgi, gelecekteki olası sorunları öngörmenizi ve daha en başta doğru tasarımı yapmanızı sağlar. Bir sistemin sadece “çalışıyor” olması yeterli değildir, aynı zamanda “doğru çalışıyor” olması gerekir. Veritabanı işlem yalıtım seviyeleri, bu “doğru çalışma”nın temel taşlarından biridir.

Sonuç: Yalıtım Seviyeleri Bir Lüks Değil, Bir Zorunluluktur

Veritabanı işlem yalıtım seviyeleri, çoğu zaman göz ardı edilen, ancak veri bütünlüğünün ve sistem güvenilirliğinin temelini oluşturan kritik bir konudur. Benim 20 yıla yakın tecrübemde, bu konuyu anlamanın sadece teknik bir detay olmaktan öte, sağlam ve güvenilir yazılım sistemleri inşa etmenin anahtarı olduğunu defalarca gördüm. READ COMMITTED’ın varsayılan olduğu bir dünyada yaşıyor olsak da, iş gereksinimlerinizin ne zaman REPEATABLE READ veya SERIALIZABLE gerektirdiğini bilmek, sizi olası veri felaketlerinden korur.

Bu konuya zaman ayırmak, sadece iyi bir veritabanı yöneticisi veya yazılımcı olmak için değil, aynı zamanda kompleks sistemlerdeki sinsi hataları tespit edip çözebilen, dolayısıyla kariyerinde fark yaratabilen bir profesyonel olmak için de elzemdir. Benim net pozisyonum: Bu seviyelerin ne anlama geldiğini, hangi senaryolarda hangi sorunlara yol açtığını ve hangi seviyenin hangi trade-off’ları getirdiğini anlamak, her teknoloji insanının bilgi dağarcığında bulunması gereken temel bir yetkinliktir.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

ERP sistemimde sevkiyat listesi tutarsızlıklarını önlemek için işlem yalıtım seviyesini nasıl ayarlamalıyım?
Ben bu sorunu ilk kez ERP’de gördüğümde, varsayılan Read Committed seviyesinin yeterli olmadığını fark ettim. Çözüm olarak, kritik raporlamalar yaptığımız servis katmanında transaction scope’u Repeatable Read olarak belirledim ve sadece o sorgular için explicit isolation hint’i ekledim. Böylece aynı anda çalışan iki işlem aynı satırı farklı okurken çakışma oluşmadı, raporlar tutarlı hâle geldi. Uygulamada, isolation seviyesini kod içinde setTransactionIsolation ile ayarlamak, veri tabanı bağlantı havuzunda global bir değişiklik yapmadan hedeflenmiş bir kontrol sağlar.
Read Committed ve Repeatable Read arasında seçim yaparken performans ve veri bütünlüğü açısından ne gibi trade‑off’lar var?
Ben her iki seviyeyi de karşılaştırdığımda, Read Committed daha az kilit tutar ve yüksek eş zamanlılıkta daha hızlı yanıt verir; ancak aynı sorgu birden fazla kez çalıştırıldığında farklı sonuçlar dönebilir. Repeatable Read ise aynı transaction içinde okunan satırları kilitleyerek tutarlılığı garanti eder, fakat kilit süresi uzadıkça deadlock riski ve işlem gecikmesi artar. Projemde finansal kapanış raporları için Repeatable Read seçtim; günlük operasyonlar için ise Read Committed tercih ettim. Böylece kritik veri bütünlüğü gereken yerlerde güvenliği, rutin işlemlerde ise hızı korumuş oldum.
İşlem yalıtım seviyesini yanlış seçtiğimde ortaya çıkan hataları nasıl tanıyarak düzeltirim?
Ben bir kez yanlışlıkla Read Uncommitted kullandığımda, raporlarımda “phantom reads” ve tutarsız toplamlar gördüm. Hata tespiti için önce sorgu planlarını ve lock wait‑eventlerini izledim; SQL Server Profiler’da “Lock:Escalation” ve “Deadlock graph” raporları bana hangi seviyenin problem yarattığını gösterdi. Sorunu çözmek için ilgili service metodunda isolation seviyesini Read Committed’a yükselttim ve test ortamında aynı senaryoyu tekrar çalıştırdım; sonuçlar tutarlı hâle geldi. Kötü bir izolasyon seviyesinin izini sürmek, log‑ları ve performans izleyicileriyle birlikte transaction boundary’larını gözden geçirmekle mümkün olur.
PostgreSQL Read Uncommitted desteklemediği doğru mu, bu yüzden Read Committed her zaman yeterli mi?
Ben PostgreSQL’de çalışırken, dokümantasyonda Read Uncommitted’un bir alias olarak Read Committed’a eşlendiğini gördüm; yani gerçek bir “dirty read” imkanı yok. Bu yüzden Read Committed çoğu uygulama için yeterli kabul edilir, fakat benim kritik stok güncellemelerimde hâlâ Repeatable Read (SERIALIZABLE) tercih ettim çünkü aynı anda birden fazla sipariş işlenirken phantom satırların oluşmasını engellemek istedim. Dolayısıyla PostgreSQL’da Read Uncommitted yoktur, ama “her zaman yeterli” demek yanıltıcıdır; iş gereksiniminize göre daha katı bir seviye seçmek veri bütünlüğünü korur.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar