Veritabanı Transaction Nedir? ACID, Isolation Level ve MVCC Rehberi

Yüksek trafikli bir sistemde veritabanı transaction yönetimi, veri bütünlüğünü korurken aynı zamanda performansı yüksek tutmanın temel anahtarıdır. Bu yazıda ACID özellikleri, isolation level çeşitleri, 2PL (two-phase locking), MVCC (Multi-Version Concurrency Control) ve optimistic/pessimistic locking gibi kavramları; Oracle, PostgreSQL, MySQL ve SQL Server bağlamında özetliyoruz.

Transaction Nedir ve Neden Önemli?

Transaction, veritabanında birlikte başarılı olmak veya birlikte başarısız olmak zorunda olan bir dizi okuma ve yazma işlemini temsil eden mantıksal birimdir. Banka havalesi, sipariş oluşturma, stok düşme gibi kritik senaryolar transaction olmadan veri tutarlılığını garanti edemez.

Tüm SQL ifadeleri mutlaka bir transaction içinde çalışır; uygulama sınırları elle çizmese bile veritabanı motoru arka planda bunu yapar. Amaç, eşzamanlı erişim altında bile veri bütünlüğünü ve tutarlılığı korumaktır.

ACID Nedir? Transactionların Dört Temel Özelliği

1. Atomicity (Bölünmezlik)

Atomicity, transaction içindeki tüm işlemlerin bir bütün olarak ele alınmasını sağlar: ya hepsi başarılı olur ya da hepsi geri alınır. Örneğin, bir hesaptan para çekilip diğerine yatırılıyorsa, sadece ilk adımın başarılı olup ikincinin başarısız kalmasına izin verilmez.

Bu amaçla veritabanları, yapılan değişiklikleri geri alabilmek için undo log veya benzeri veri yapılarında önceki değerleri saklar. Oracle, SQL Server, PostgreSQL ve MySQL; bu “önceki sürümleri” farklı mimarilerle yönetir ancak ortak amaç, rollback işlemini mümkün kılmaktır.

2. Consistency (Tutarlılık)

Consistency, transaction bittiğinde veritabanının geçerli bir durumdan yine geçerli bir duruma geçmiş olmasını garanti eder. Yani transaction öncesinde ve sonrasında tüm constraint’ler (primary key, foreign key, unique, check, NOT NULL vb.) ihlal edilmemelidir.

Uygulama tarafında yapılan validasyonlar yeterli değildir; farklı uygulamalar, farklı sunucular aynı veritabanını kullanabilir. Bu nedenle şema tabanlı validasyon ve tutarlı constraint kullanımı kritiktir.

3. Isolation (Yalıtım)

Isolation, eşzamanlı çalışan transaction’ların birbirini nasıl gördüğünü tanımlar. İdeal senaryoda, sistem sanki transaction’lar tek tek sırayla çalışıyormuş gibi davranır (serializable davranış); ancak bu, performans maliyeti yüksek bir durumdur.

Bu nedenle veritabanları, farklı isolation level seçenekleri sunar:

  • Read Uncommitted – En zayıf, kirli okumaya (dirty read) izin verebilir.
  • Read Committed – En yaygın varsayılan seviye, dirty read’i engeller.
  • Repeatable Read – Aynı satırı yeniden okurken farklı sonuçlar görmeyi engeller.
  • Serializable – Tüm anomalileri engellemeye çalışan en güçlü seviye.

4. Durability (Kalıcılık)

Durability, commit edilen verinin sistem çökse bile kaybolmamasını sağlar. Bunun için veritabanları, redo log / transaction log / WAL (Write-Ahead Log) gibi yapılar kullanır. Transaction commit olduğunda ilgili log disk’e yazılır; sistem yeniden başladığında bu log’dan ileriye doğru oynatılarak veri kurtarılır.

İki Temel Yaklaşım: 2PL ve MVCC

Two-Phase Locking (2PL)

Two-Phase Locking, transaction’ların kilit kullanarak sırayla ilerlemesini sağlar. İki fazı vardır:

  • Lock acquisition (genişleme fazı) – Gerekli tüm kilitler alınır, hiçbiri bırakılmaz.
  • Lock release (daralma fazı) – Tüm kilitler bırakılır, artık yeni kilit alınmaz.

Bu modelde shared lock’lar (okuma) ve exclusive lock’lar (yazma) kullanılır. 2PL, teoride tam serializable davranış sağlar, ancak kilit beklemeleri ve deadlock riskini beraberinde getirir.

Multi-Version Concurrency Control (MVCC)

MVCC, “okuyucular yazarları, yazarlar da okuyucuları bloklamasın” fikrine dayanır. Her satırın farklı versiyonları tutulur ve her transaction, tutarlı bir zaman noktasına ait snapshot görür.

Özetle:

  • Okuyan transaction, satırın commit edilmiş “eski” sürümünü görebilir.
  • Yazmaya çalışan transaction ise en güncel sürüm üzerinden değişiklik yapar.
  • Yazar–yazar çakışması hâlinde, bir transaction genelde hata alıp rollback olur.

Oracle ve PostgreSQL gibi veritabanları yoğun biçimde MVCC kullanır; SQL Server ve MySQL de hem kilit tabanlı hem de MVCC tabanlı modlara sahiptir.

Transaction Anomalileri: Dirty Read, Lost Update ve Diğerleri

Isolation level’lar, hangi anomali türlerini engellediğiyle tanımlanır. Başlıca anomaliler şunlardır:

  • Dirty Write – İki transaction aynı satırı aynı anda değiştirir, biri diğerini ezebilir.
  • Dirty Read – Bir transaction, henüz commit edilmemiş veriyi okur.
  • Non-repeatable Read – Aynı satır tekrar okunduğunda farklı değer görülür.
  • Phantom Read – Aynı sorgu tekrar çalıştığında sonuç kümesine yeni satırlar eklenmiş olur.
  • Read Skew – İlişkili satırlar arasında “tarihsel” tutarsızlık görünür.
  • Write Skew – Mantıksal olarak birlikte güncellenmesi gereken satırlar dağınık ve tutarsız kalır.
  • Lost Update – İki tarafın güncellediği veri, son yazan tarafından diğerinin farkında olunmadan ezilir.

Özellikle lost update, e-ticaret ve finans gibi alanlarda kritik veri bozulmalarına yol açar. Bu nedenle sadece Read Committed seviyesine güvenmek yerine ek uygulama tarafı eşzamanlılık kontrolleri kullanmak gerekebilir.

Isolation Level Karşılaştırması: Hangi Seviye Ne Sağlar?

SQL standardına göre isolation level’lar, engelledikleri fenomenlere göre sıralanır:

  • Read Uncommitted – Dirty read dâhil tüm anomalilere açık.
  • Read Committed – Dirty read engellenir; non-repeatable read, phantom, lost update görülebilir.
  • Repeatable Read – Aynı satır tekrar okunduğunda değişmez; bazı phantom ve write skew vakaları kalabilir.
  • Serializable – Teoride tüm anomalileri engeller, fakat performans maliyeti en yüksek seviyedir.

Gerçekte, her veritabanının Serializable ve Repeatable Read implementasyonu farklı ayrıntılara sahiptir. Örneğin:

  • Oracle – Varsayılanı Read Committed; Serializable seviyesi snapshot isolation benzeri davranır ve bazı write skew senaryolarına izin verebilir.
  • PostgreSQLSerializable Snapshot Isolation (SSI) ile gerçekten serializable’a çok yakın davranır.
  • MySQL (InnoDB) – Repeatable Read MVCC ile uygulanır; ancak belirli senaryolarda lost update ve write skew oluşabilir.
  • SQL Server – Varsayılan Read Committed; klasik Serializable seviye genellikle 2PL (locking) ile sağlanır.

Bu nedenle, “sadece isolation level’ı yükseltmek” her zaman tek başına yeterli çözüm değildir; uygulama katmanında da strateji belirlemek gerekir.

Durability ve Transaction Log Mimarisi

Verilerin kalıcı olması için, veritabanı motoru her değişikliği disk tabanlı bir log yapısına yazar: redo log, transaction log veya WAL. Commit anında bu log diske “fsync” ile gönderilir, böylece güç kesilmesi gibi durumlarda dahi veriler log üzerinden yeniden oluşturulabilir.

Bazı sistemler, performans için synchronous/asynchronous flush ayarları sunar. Asenkron flush daha hızlı olabilir ama system crash durumunda bazı son transaction’ların kaybolması ihtimalini kabul etmek anlamına gelir. İş kritik sistemlerde genelde tam dayanıklılık tercih edilir.

Read-Only Transaction ve Replikasyon Senaryoları

Read-only transaction, yalnızca okuma yapan sorgulardan oluşur. Bazı veritabanlarında bu bilgi, log yazma ve sürüm oluşturma maliyetini azaltmak için optimize edilir. PostgreSQL ve MySQL gibi sistemlerde, read-only flag’i belirgin optimizasyonlar sağlayabilir.

Master–Slave replikasyon mimarisinde:

  • Master hem yazma hem okuma alır.
  • Slave düğümler genellikle sadece okuma (read-only) için kullanılır.

Uygulama tarafında, transaction eğer read-only ise ilgili sorguyu otomatik olarak slave üzerinden çalıştıracak bir routing mimarisi kurmak, hem ölçeklenebilirliği hem de sorgu yanıt süresini iyileştirebilir.

Transaction Boundary, JTA ve Dağıtık Transactionlar

Tek bir veritabanı yerine birden fazla kaynak (birden çok DB, mesaj kuyruğu vb.) işin içine girdiğinde, dağıtık transaction kavramı ortaya çıkar. Java ekosisteminde bunun karşılığı JTA (Java Transaction API) ve two-phase commit (2PC) protokolüdür.

2PC’de:

  1. Prepare fazı – Tüm kaynaklara “commit’e hazır mısın?” sorusu sorulur.
  2. Commit fazı – Herkes “hazırım” dediyse commit edilir; aksi halde herkes rollback olur.

Uygulama seviyesinde, genellikle Service katmanı transaction sınırlarını belirler. Spring veya Java EE üzerinde @Transactional gibi notasyonlarla transaction’ların propagation, isolation, read-only ve rollback kuralları deklaratif olarak yönetilebilir.

Uygulama Seviyesinde Transaction ve Optimistic/Pessimistic Locking

Kullanıcı etkileşimli web uygulamalarında, tek bir iş akışı genellikle birden fazla HTTP isteği ile tamamlanır. Bu, veritabanı transaction’larını uzun süre açık tutmak anlamına gelir ki; kilitleri uzun süre tutmak, performans ve ölçeklenebilirlik açısından kabul edilemez.

Bu nedenle, “iş akışı” mantıksal olarak bir transaction gibi düşünülse de, fiziksel olarak birden fazla kısa transaction’a bölünür. Veri çakışmalarını engellemek için iki yaklaşım kullanılır:

Pessimistic Locking (Kötümser Kilitleme)

Pessimistic locking, “nasıl olsa çakışma olacak, en iyisi baştan kilit alalım” felsefesiyle hareket eder. Örneğin:

  • SELECT ... FOR UPDATE ile satırı exclusive kilitlemek,
  • kritik güncelleme öncesinde satır bazlı kilitler almak,

Non-repeatable read, lost update, read skew gibi anomalileri önleyebilir; fakat kilit bekleme süreleri ve deadlock riskini artırır. Kısa süren, kritik güncellemelerde etkilidir.

Optimistic Locking (İyimser Kilitleme)

Optimistic locking aslında kilit kullanmaz; bunun yerine versiyon alanı kullanarak çakışmaları tespit eder. Her satırda bir version kolonu tutulur:

  1. Satır okunurken mevcut version değeri alınır.
  2. Güncelleme yapılırken WHERE id = ? AND version = ? şeklinde sorgu gönderilir.
  3. Güncellenen satır sayısı 0 ise, bu arada başka bir transaction satırı değiştirmiştir; yani stale data ile çalışılmaktadır.

ORM araçları (Hibernate vb.) genellikle bu mekanizmayı otomatik olarak destekler. Böylece uzun süren web akışlarında bile lost update riskini önemli ölçüde azaltmak mümkündür.

Sonuç: Yüksek Performanslı Transaction Yönetimi İçin Öneriler

  • Transaction’ları kısa tutun: Kullanıcı düşünme süresi boyunca açık transaction bırakmayın.
  • Uygun isolation level seçin: Körü körüne Serializable kullanmak yerine, iş ihtiyacına göre Read Committed/Repeatable Read + ek kontrol mekanizmaları kullanın.
  • MVCC’yi anlayarak kullanın: Hangi veritabanının, hangi isolation seviyesinde hangi anomalilere izin verdiğini bilin.
  • Optimistic locking uygulayın: Özellikle web tabanlı, çok kullanıcılı sistemlerde versiyon alanıyla lost update’i engelleyin.
  • Durability ayarlarını iş gereksinimiyle eşleştirin: Veri kaybının kabul edilemez olduğu sistemlerde asenkron log flush’tan kaçının.
  • Read-only routing kullanın: Raporlama ve okuma yoğun sorguları replikalara yönlendirerek ana veritabanını rahatlatın.

Özetle, transaction yönetimi sadece veritabanının işi değil; mimari tasarım, kod yapısı ve iş kuralları birlikte ele alınması gereken bir konudur. Doğru ACID, isolation ve concurrency stratejileri ile hem yüksek performanslı hem de veri bütünlüğü güçlü sistemler tasarlamak mümkündür.

Beğendiysen bir çay ısmarlayabilirsin ☕

Bana çay ısmarla

Database ile ilgili yorumlar

Yorum Paylaş

EMail Zorunlu alanlar * *