Üç Eş Zamanlılık Sorunu
Çok işlemcili bir ortamda transaction'lar birbirleriyle girişim yapabilir. Her DBMS'nin önlemek zorunda olduğu üç temel eş zamanlılık sorunu vardır. Bu sorunların kökeni, transaction'ların aynı veri öğesine paralel erişimidir.
Problem 1
İkinci transaction, birinci transaction'ın değişikliğini yazmadan önce veriyi okur ve ardından kendi güncellemesiyle birincinin değişikliğinin üzerine yazar. Birincinin güncelleme kaybolur.
Problem 2
İkinci transaction, henüz commit edilmemiş bir değişikliğe bağımlı hale gelir. Birinci transaction daha sonra rollback yaparsa, ikinci transaction geçersiz (kirli) bir veriyle çalışmış olur.
Problem 3
Bir transaction veri okurken başka bir transaction aynı veriyi güncelleyip commit ederse, okuyan transaction tutarsız bir resim görür. Özellikle toplam/özet hesaplamalarında ciddi yanlış sonuçlara yol açar.
Problem 1: Lost Update (Kayıp Güncelleme)
Transaction A, t'yi okur (t1). Sonra Transaction B de t'yi okur (t2). A güncelleyip yazar (t3). Ardından B de güncelleyip yazar (t4). A'nın güncellemesi kaybolur!
| Transaction A | Zaman | Transaction B |
|---|---|---|
| RETRIEVE t | t1 | |
| t2 | RETRIEVE t | |
| UPDATE t | t3 | |
| t4 | UPDATE t ← A'nın güncellemesini ezer! |
Fig. 16.1 — Transaction A, t4 anında güncellemesini kaybeder
Neden olur? B, A'nın güncellemesini görmeden önce aynı verinin eski kopyasını okumuştur. B'nin yazması A'nın yazmasının üzerine gelir. Neden olduğu çakışma türü: WW (Write-Write)
Problem 2: Uncommitted Dependency (Taahhüt Edilmemiş Bağımlılık)
Transaction B, t'yi günceller (t1). Transaction A, henüz commit edilmemiş bu değeri okur (t2). Sonra B rollback yapar (t3). A artık var olmayan bir değere bağımlı kalmıştır.
| Transaction A | Zaman | Transaction B |
|---|---|---|
| t1 | UPDATE t (commit edilmedi) | |
| RETRIEVE t ← kirli veri! | t2 | |
| t3 | ROLLBACK ← B geri alındı! |
Fig. 16.2 — Transaction A, t2 anında commit edilmemiş değişikliğe bağımlı hale gelir
Dirty Read (Kirli Okuma): A'nın t2'deki okuma işlemi bir "dirty read"dir. B rollback yapınca A'nın okuduğu değer sanki hiç var olmamış gibi olur. Neden olduğu çakışma türü: WR (Write-Read)
Problem 3: Inconsistent Analysis (Tutarsız Analiz)
ACC1=40, ACC2=50, ACC3=30 (Toplam=120). Transaction A toplamı hesaplarken Transaction B bazı hesapları güncelliyor. Sonuç: A'nın bulduğu toplam 110, gerçek ise 120!
| Transaction A (Toplam hesaplıyor) | Zaman | Transaction B (Güncelleme yapıyor) |
|---|---|---|
| RETRIEVE ACC1 → sum=40 | t1 | |
| RETRIEVE ACC2 → sum=90 | t2 | |
| t3 | RETRIEVE ACC3 | |
| t4 | UPDATE ACC3: 30→20 | |
| t5 | RETRIEVE ACC1 | |
| t6 | UPDATE ACC1: 40→50 | |
| t7 | COMMIT | |
| RETRIEVE ACC3 → sum=110 ❌ (110 değil 120 olmalı) | t8 |
Fig. 16.4 — Transaction A tutarsız analiz yapar (110 bulur, 120 olmalı)
A, ACC1'i 40 olarak okumuştu. Oysa B, ACC1'i sonradan 50'ye çıkardı ve ACC3'ü 20'ye düşürdü. A bu değişikliği görmediğinden yanlış bir toplam hesapladı. Neden olduğu çakışma türü: RW (Read-Write)
Conflicts (Çakışmalar)
A ve B iki eş zamanlı transaction ise ve her ikisi de aynı veritabanı nesnesine (örneğin t tuple'ı) okuma veya yazma yapmak istiyorsa dört olasılık söz konusudur. Bu çakışmaların her biri farklı bir soruna yol açar.
RR Çakışması
✅ Sorun YokA okur, B de okur. Okuma işlemleri birbirine müdahale edemez. İki transaction da aynı anda okuyabilir, herhangi bir veri bozulması yaşanmaz.
RW Çakışması
⚠️ Inconsistent AnalysisA okur, B yazmak ister. B yazmasına izin verilirse Tutarsız Analiz problemi ortaya çıkabilir. A okuduğu değerin altında değişiklik yapılmış olur.
WR Çakışması
⚠️ Uncommitted Dep.B yazar, A okumak ister. A okumasına izin verilirse Uncommitted Dependency problemi ortaya çıkabilir. A'nın bu okuma işlemi bir dirty read'dir.
WW Çakışması
❌ Lost UpdateA yazar, B de yazmak ister. B yazmasına izin verilirse Lost Update problemi ortaya çıkabilir. A'nın güncellemesi B tarafından ezilir.
Özet: RR çakışması zararsızdır. RW → Inconsistent Analysis, WR → Uncommitted Dependency (Dirty Read), WW → Lost Update sorununa yol açar. Bu üç sorunlu çakışmayı önlemek Concurrency Control'ün temel görevidir.
Locking (Kilitleme)
Bahsedilen tüm sorunlar, locking (kilitleme) adı verilen bir eş zamanlılık kontrol mekanizmasıyla çözülebilir. Bir transaction, eş zamanlılık sorunlarını önlemek için veritabanının belirli bir kısmını kilitler.
🔒 Exclusive Lock — X (Yazma Kilidi)
- Yalnızca yazma işlemi için kullanılır
- Başka hiçbir transaction aynı anda bu veriye erişemez
- Ne S ne de X kilidi verilebilir
- Tam izolasyon sağlar
📖 Shared Lock — S (Okuma Kilidi)
- Okuma işlemi için kullanılır
- Birden fazla transaction aynı anda S kilidi tutabilir
- Yazma (X kilidi) engellenmiş olur
- Eş zamanlı okumaya izin verir
Kilit Uyumluluk Matrisi (Lock Compatibility Matrix)
Bir transaction, talep ettiği kilit, başka transaction'ların o öğe üzerinde tuttuğu kilitlerle uyumluysa kilit verilebilir:
| Mevcut \ Talep Edilen | S (Shared) | X (Exclusive) |
|---|---|---|
| S (Shared) | ✅ true | ❌ false |
| X (Exclusive) | ❌ false | ❌ false |
Kural: Herhangi bir sayıda transaction aynı öğe üzerinde S kilidi tutabilir. Ancak herhangi bir transaction X kilidi tutuyorsa, başka hiçbir transaction (S veya X) kilit alamaz. X kilidi tam münhasır bir erişimdir.
Two-Phase Locking (2PL)
Bir kilitleme protokolü, bir transaction'ın veritabanındaki veri öğelerini ne zaman kilit alıp bırakabileceğini belirleyen kurallar bütünüdür. En yaygın ve güçlü protokol İki Aşamalı Kilitleme (2PL)'dir.
2PL, çakışma-serileştirilebilir (conflict-serializable) zamanlamaları garanti eder. Bu, eş zamanlı çalışan transaction'ların bir seri çalışmayla eşdeğer sonuç üretmesini sağlar.
Kilit Sayısı / Zaman
📈 Faz 1: Growing (Büyüme Fazı)
- Transaction yeni kilitler alabilir
- Hiçbir kilit bırakılamaz
- Kilit sayısı sadece artar
- Lock point'e kadar sürer
📉 Faz 2: Shrinking (Küçülme Fazı)
- Transaction kilitlerini bırakabilir
- Yeni kilit alınamaz
- Kilit sayısı sadece azalır
- Commit/rollback'e kadar sürer
2PL Örneği: T₃₄ ve T₃₅
Önce kilidin eklenmediği, sonra eklendiği hali karşılaştıralım:
2PL Uzantıları
Deadlock (Kilitlenme)
Kilitleme protokollerinin çoğu deadlock'lara karşı koruma sağlamaz. Deadlock, her transaction'ın kümedeki başka bir transaction'ı beklediği döngüsel bir bekleme durumudur.
Klasik Deadlock Örneği (T₃ ve T₄): T₃, B üzerinde X kilidi tutuyor ve A'nın X kilidini bekliyor. T₄ ise A üzerinde S kilidi tutuyor ve B'nin S kilidini bekliyor. Her ikisi de ilerleyemez — sonsuz bekleme!
Çözüm: T₃ veya T₄'ten biri rollback yapılmalı ve kilitleri serbest bırakılmalı. Hangisinin rollback yapılacağı victim selection (kurban seçimi) problemidir. Genellikle daha az iş yapmış (ya da daha ucuz rollback maliyetli) olan transaction seçilir.
Deadlock'ı Önleme Stratejileri
⏳ Wait-Die (Non-Preemptive)
- T1 daha yaşlıysa bekleyebilir (wait)
- T2 daha gençse rollback yapılır (die)
- Yaşlı transaction genç olanı bekleyebilir
- Genç transaction asla bekleyemez, ölür
- Bir transaction lock almadan önce birkaç kez ölebilir
🗡️ Wound-Wait (Preemptive)
- T1 daha yaşlıysa T2'yi rollback yapar (wound)
- T2 daha yaşlıysa bekleyebilir (wait)
- Yaşlı transaction, genç olanı zorla geri alır
- Genç transaction yaşlı olanı bekleyebilir
- Wait-Die'a göre daha az rollback yapar
Her iki şemada da: Rollback yapılan bir transaction, orijinal timestamp'iyle yeniden başlatılır. Bu sayede yaşlı transaction'ların her zaman önceliği olur ve starvation önlenir. Net etki: en yaşlı transaction kazanır.
Örnek: Wait-Die Şeması
| T1 (Daha yaşlı) | Olay | T2 (Daha genç) |
|---|---|---|
| lock-S(A) ✅ | → | |
| read(A) | → | |
| → | lock-S(B) ✅ | |
| → | read(B) | |
| lock-X(B) → T1 yaşlı, bekleyebilir ⏳ | → | |
| → | lock-X(A) → T2 genç, rollback! ❌ | |
| lock-X(B) ✅ (T2 rollback yaptı, T1 devam eder) | → |
Wait-Die: T1 yaşlı olduğu için bekler, T2 genç olduğu için rollback yapılır
Timeout-Based (Zaman Aşımı Tabanlı) Strateji
⏱️ Timeout Tabanlı Deadlock Önleme
- Bir transaction kilidi yalnızca belirli bir süre bekler
- Süre dolunca transaction rollback yapılır
- Deadlock olursa zaman aşımıyla çözülür
- Uygulaması basittir
- ⚠️ Deadlock olmasa bile gereksiz rollback yapabilir
- ⚠️ İdeal timeout değerini belirlemek zordur
- ⚠️ Starvation (açlık) hâlâ mümkündür
Deadlock Tespiti: Wait-for Graph
Deadlock'ları önlemek yerine tespit etmek de bir seçenektir. Wait-for Graf ile deadlock tespiti yapılabilir:
- Her transaction grafta bir düğüm (vertex)'tür
- Tᵢ → Tⱼ yönlü kenar: Tᵢ, Tⱼ'nin tuttuğu çakışan bir kilidi bekliyor demektir
- Graf bir döngü içeriyorsa sistem deadlock durumundadır
- Periyodik olarak döngü-tespit algoritması çalıştırılır
- Döngü bulunursa kurbana rollback yapılır
✅ Döngü Yok — Deadlock Yok
Döngü yok → Normal çalışma, deadlock yok.
🔴 Döngü Var — Deadlock!
T₁₈ ↔ T₁₉ arasında döngü → Deadlock! Bir kurban seçilmeli.
Starvation (Açlık / Aç Kalma)
Concurrency Control Manager kötü tasarlanmışsa, Starvation da mümkündür. Starvation, bir transaction'ın hiçbir zaman gerekli kilidi alamaması ve sürekli beklemesidir.
⚡ Starvation Senaryosu 1
- T₁ bir öğe üzerinde X kilidi bekliyor
- Art arda gelen transaction'lar aynı öğeye S kilidi istiyor
- S kilitleri birbirleriyle uyumlu olduğundan hepsi onaylanıyor
- T₁'in X kilidi için hiçbir zaman fırsat çıkmıyor
- T₁ sonsuza kadar bekler!
🔄 Starvation Senaryosu 2
- Bir transaction her seferinde deadlock kurbanı seçiliyor
- Rollback → yeniden başlatma → tekrar deadlock kurbanı
- Transaction hiçbir zaman tamamlanamıyor
- Kötü victim selection algoritması neden olur
Çözüm: Concurrency Control Manager, açlığı önleyecek şekilde tasarlanabilir. Örneğin FIFO (First-In First-Out) bekleme kuyruğu kullanılabilir: Bir transaction kilidi beklerken sıraya girer; sonraki S kilidi talepleri o transaction önce onaylanana kadar kuyrukta bekletilir.
Locking ile Sorunların Çözümü
Lost Update — Kilit ile Çözüm
Her iki transaction da güncelleme yapmadan önce X kilidi istediğinde, kilitleme mekanizması bir tarafı bekletir:
| Transaction A | Zaman | Transaction B |
|---|---|---|
| RETRIEVE t (S kilidi alır) | t1 | |
| t2 | RETRIEVE t (S kilidi alır) | |
| UPDATE t → X kilidi ister → bekler ⏳ | t3 | |
| t4 | UPDATE t → X kilidi ister → bekler ⏳ | |
| ⚠️ Deadlock oluştu! (t4 anında) — Güncelleme kaybolmaz ama deadlock çözülmeli. | ||
Fig. 16.6 — Kilit kullanıldığında güncelleme kaybolmaz ama deadlock oluşabilir
Uncommitted Dependency — Kilit ile Çözüm
| Transaction A | Zaman | Transaction B |
|---|---|---|
| t1 | UPDATE t (X kilidi alır) | |
| RETRIEVE t → S kilidi ister → bekler ⏳ | t2 | |
| t3 | COMMIT / ROLLBACK (X kilidi bırakılır) | |
| t4: RETRIEVE t → S kilidi alır ✅ (temiz veriyi okur) | t4 |
Fig. 16.7 — Kilit sayesinde A, commit edilmemiş değişikliği göremez
Isolation Levels (İzolasyon Seviyeleri)
İzolasyon seviyesi, bir transaction çalışırken diğer transaction'lardan ne ölçüde etkileneceğini belirler. Yüksek izolasyon = daha az girişim = daha az eş zamanlılık (performans düşer). Düşük izolasyon = daha fazla girişim = daha yüksek performans ama riskler artar.
Read Anomalileri (Okuma Anormallikleri)
Anomali 1
Bir transaction, başka bir transaction tarafından henüz commit edilmemiş değerleri okur. O transaction rollback yaparsa, okunan veri hiç var olmamış olur.
Anomali 2
Bir transaction aynı nesneyi iki kez okur ve farklı değerler görür; oysa kendisi bu değeri değiştirmemiştir. Başka bir transaction araya girip güncelleme yapmıştır.
Anomali 3
Bir transaction bir sorguyu yeniden çalıştırır ve koşulu karşılayan satır kümesinin değiştiğini görür. Başka bir transaction yeni satır eklemiş/silmiştir.
İzolasyon Seviyeleri Tablosu
| İzolasyon Seviyesi | Dirty Read | Nonrepeatable Read | Phantom Read | Açıklama |
|---|---|---|---|---|
| READ UNCOMMITTED | ✗ Evet | ✗ Evet | ✗ Evet | Commit edilmemiş veriler bile okunabilir. En düşük izolasyon, en yüksek performans. |
| READ COMMITTED | ✓ Hayır | ✗ Evet | ✗ Evet | Yalnızca commit edilmiş veriler okunabilir. Çoğu veritabanının varsayılan seviyesi. |
| REPEATABLE READ | ✓ Hayır | ✓ Hayır | ✗ Evet | Okunan satırlar transaction boyunca değişmez. Phantom hâlâ mümkün. |
| SERIALIZABLE | ✓ Hayır | ✓ Hayır | ✓ Hayır | En yüksek izolasyon. Tam seri çalışmayla eşdeğer. En düşük performans. |
Trade-off: İzolasyon seviyesi ne kadar yükselirse, transaction'lar arasındaki girişim o kadar azalır ama eş zamanlılık (ve dolayısıyla verimlilik) de düşer. Uygulama gereksinimlerine göre doğru seviye seçilmelidir. Çoğu DBMS varsayılan olarak READ COMMITTED kullanır.
İzolasyon Seviyeleri Görsel Özeti
🚀 En yüksek performans
⚙️ Varsayılan seviye
🔒 MySQL varsayılanı
🛡️ Tam güvenlik
Concurrency Control — Tam Özet
Veritabanında eş zamanlılık sorunları kilitleme protokolleri ile yönetilir. 2PL conflict-serializability garanti eder, deadlock özel stratejilerle ele alınır, izolasyon seviyeleri performans-güvenlik dengesini ayarlar.