İçeriğe geç

İzolasyon Seviyeleri — Aynı İsim, Üç Farklı Davranış

Orta 9 dk Çok sık karşılaşılır

Önce şunu oku: SQL mi, NoSQL mi? — Önce Erişim Desenini Yaz

30 saniyede özet

İki kişi aynı anda aynı hesaptan para çekerse biri kaybolabilir, transaction kullansan bile. Veritabanlarının koruma ayarları (izolasyon seviyeleri) aynı adı taşısa da her motorda farklı davranır.

Aynı hesaptan iki para çekme isteği aynı anda geldi. İkisi de başarılı döndü, ama bakiyeden yalnızca biri düştü. Kod @Transactional idi; ekip “transaction var, eşzamanlılık sorunu olamaz” diye düşünüyordu.

Transaction içinde bile iki çekişten biri kaybolabilir.
Adım adım oku
  1. İki para çekme transaction'ı aynı anda başlar ve ikisi de bakiyeyi 100 olarak okur.
  2. İkisi de kendi okuduğu değerden 50 düşer ve bakiyeye 50 yazar.
  3. İki çekiş başarılı döndü ama bakiye yalnızca bir kez düştü: kayıp güncelleme.
  4. SELECT FOR UPDATE ile ilk transaction satırı kilitler; ikincisi bekler, güncel bakiyeyi okur ve doğru sonuç sıfır olur.
  1. Bayt: Aynı hesaptan iki para çekildi, ikisi de başarılı, ama bakiyeden yalnızca biri düştü!

  2. Sen: Ama kod @Transactional'dı.

  3. Bayt: Evet! Transaction varsa eşzamanlılık sorunu olamaz sanıyorduk.

  4. Bayt: Ortak alışveriş listesine iki kişi aynı anda bakarsa eve kaç süt gelir?

Aynı anda çalışınca neler bozulur?

izolasyon seviyesiEşzamanlı transaction'ların birbirinin yarım kalmış işlerini ne kadar görebileceğine dair sözleşme: READ UNCOMMITTED'dan SERIALIZABLE'a.Sözlükte gör →, eşzamanlı transaction’ların birbirinin ara hâllerini ne kadar görebileceğine dair bir sözleşmedir. Seviye düştükçe şu durumlara izin verilir:

AnomaliNe olur
Dirty readBaşkasının commit etmediği değeri okursun
Non-repeatable readAynı satırı iki kez okursun, farklı değer gelir
Lost updateİki iş aynı değeri okuyup üstüne yazar; biri kaybolur
Write skewİki iş farklı satırlara yazar; birlikte bir kuralı bozarlar

Son ikisi en tehlikelisidir, çünkü hiçbir satır “bozuk” görünmez. Nöbette en az bir doktor kalmalıyken ikisinin de çıkması, bir write skewİki transaction aynı veriyi okuyup farklı satırlara yazar; tek tek doğru olan kararlar birlikte bir kuralı bozar. Snapshot isolation bunu durdurmaz.Sözlükte gör → örneğidir.

Kafam karıştı, daha basit anlat

İzolasyon, aynı anda çalışan işlerin birbirinin yarım hâlini ne kadar görebileceğidir. Seviye düştükçe daha hızlı çalışır, ama daha tuhaf sonuçlar görebilirsin.

Hızlı kontrolOrta

Metot `@Transactional`; bakiyeyi okuyup, çekilecek tutarı çıkarıp yazıyor. İki istek aynı anda gelince bir çekim kayboluyor. Transaction neden korumadı?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her olayı, hangi anomali olduğuna göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Dirty read

Commit edilmemiş değer okundu.

    Non-repeatable read

    Aynı satır, aynı transaction, farklı değer.

      Lost update

      Bir yazma diğerinin üstüne yazıldı.

        Write skew

        Farklı satırlar, birlikte bozulan kural.

          Aynı isim, farklı davranış

          PostgreSQL'de seviyeyi READ UNCOMMITTED yaptın. Başka bir transaction'ın commit etmediği değeri okuyabilir misin? Cevabı göster

          Hayır. PostgreSQL bu seviyeyi kabul eder ama READ COMMITTED gibi davranır. Dirty read’i yalnızca SQL Server gösterir; Oracle bu seviyeyi hiç sunmaz.

          PostgreSQL ve Oracle MVCCMulti-version concurrency control: yazma yeni bir satır sürümü üretir, okuyan commit edilmiş eski sürümü görür. Okuyan ile yazan birbirini beklemez.Sözlükte gör → kullanır: okuyana satırın commit edilmiş bir sürümünü gösterir, kimseyi bekletmez. SQL Server’ın varsayılanı ise kilittir: okuyan, yazanın işini bitirmesini bekler.

          PostgreSQLSQL ServerOracle
          VarsayılanREAD COMMITTEDREAD COMMITTED (kilitli)READ COMMITTED
          Dirty readHiçbir seviyedeYalnızca READ UNCOMMITTEDHiçbir seviyede
          Lost update’i durduranREPEATABLE READ: hataREPEATABLE READ: deadlockSERIALIZABLE: hata
          Write skew’i durduranSERIALIZABLEREPEATABLE READ: deadlockHiçbiri
          Kafam karıştı, daha basit anlat

          Aynı ayarın adı her veritabanında aynı, ama davranışı farklı olabilir. Kendi veritabanının belgesine bakmadan “bu seviye şunu yapar” deme.

          Hızlı kontrolOrta

          Uzun bir UPDATE transaction'ı açıkken aynı satırları okuyan raporlar SQL Server'da bekliyor, PostgreSQL'de beklemiyor. İkisi de READ COMMITTED. Neden?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Kendin gör

          Aynı iki transaction, üç veritabanı, dört seviye

          Tohum 877683

          Oynat ya da adımla.

          Hız
          Adım 0

          Şu an ne oldu?

          Lost update — PostgreSQL, READ COMMITTED

          İki transaction aynı veriye dokunacak. Veritabanı bu seviyede ne yapıyor: izin mi veriyor, bekletiyor mu, reddediyor mu?

          Görevler0/3

          • Commit edilmemiş bir değeri okuaçık

            İpucu

            Üç motordan yalnızca birinde, en düşük seviyede.

          • SERIALIZABLE seçiliyken write skew yaşaaçık

            İpucu

            Snapshot isolation farklı satırlara yazanları durdurmaz.

          • Lost update senaryosunda hiçbir çekim kaybolmasınaçık

            İpucu

            READ COMMITTED'ın üstüne çık; bedelini de gör.

          Olay günlüğü (0)

          Henüz olay yok. Oynat veya adımla.

          1. Varsayılanla oynat. PostgreSQL, READ COMMITTED: bakiye 50, olması gereken 20.
          2. Seviyeyi REPEATABLE READ yap. T2 hata aldı; veri korundu.
          3. Motoru SQL Server yap. Aynı seviye, bu kez deadlock.
          4. Write skew’e geç, Oracle ve SERIALIZABLE seç. Nöbetçi kalmadı.
          5. Dirty read’i üç motorda dene.
          Hızlı kontrolİleri

          Simülatörde lost update senaryosu REPEATABLE READ'de PostgreSQL'de serialization hatası, SQL Server'da deadlock verdi. Fark nereden geliyor?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Kaybolan güncellemeyi nasıl önlersin?

          Seviyeyi yükseltmek tek yol değildir; çoğu zaman en ucuzu da değildir.

          -- 1. Hesabı veritabanına yaptır: oku-hesapla-yaz yok
          UPDATE accounts SET balance = balance - 30 WHERE id = 1 AND balance >= 30;
          -- 2. Okurken kilitle
          SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
          -- 3. İyimser kilit: sürüm değiştiyse 0 satır güncellenir
          UPDATE accounts SET balance = 70, version = 8 WHERE id = 1 AND version = 7;

          JPA’daki @Version üçüncü yolun kendisidir. SQL Server’da FOR UPDATE yerine WITH (UPDLOCK) ipucu kullanılır.

          Aynı üç yol, bir bankanın kartla para çekme servisinde de karşına çıkar. Spring tarafında nasıl göründüklerine birlikte bakalım.

          Derinleş · Kartla para çekme: kaybolan güncellemeye üç çare 4 dosya · ~73 satır · ilk okumada atlayabilirsin
          Proje dosyaları

          src/main/java/com/bank/account/ AccountRepository.java Repository: bakiyeyi veritabanında düşüren koşullu UPDATE ve satırı kilitleyerek okuyan sorgu.

          src/main/java/com/bank/account/AccountRepository.java
          interface AccountRepository extends JpaRepository<Account, String> {
          // Way 1: the database does the arithmetic. 0 rows updated means insufficient funds.
          // A bulk UPDATE skips @Version, so the version is bumped by hand.
          @Modifying
          @Query("""
          UPDATE Account a
          SET a.balance = a.balance - :amount, a.version = a.version + 1
          WHERE a.iban = :iban AND a.currency = :currency AND a.balance >= :amount
          """)
          int debitIfCovered(String iban, BigDecimal amount, String currency);
          // Way 2: SELECT ... FOR UPDATE on PostgreSQL and Oracle, an UPDLOCK hint on SQL Server.
          @Lock(LockModeType.PESSIMISTIC_WRITE)
          @Query("SELECT a FROM Account a WHERE a.iban = :iban")
          Optional<Account> findForUpdate(String iban);
          }

          src/main/java/com/bank/account/ Account.java Hesap entity'si: @Version ile iyimser kilit; para BigDecimal ve para birimiyle.

          src/main/java/com/bank/account/Account.java
          @Entity
          class Account {
          @Id
          private String iban;
          private String currency; // ISO 4217
          private BigDecimal balance; // numeric(19,2) column
          // Way 3: Hibernate writes UPDATE ... WHERE version = ?; 0 rows means someone else won.
          @Version
          private long version;
          void debit(BigDecimal amount, String inCurrency) {
          if (!currency.equals(inCurrency)) throw new CurrencyMismatchException(iban, inCurrency);
          if (balance.compareTo(amount) < 0) throw new InsufficientFundsException(iban);
          balance = balance.subtract(amount);
          }
          }

          src/main/java/com/bank/card/ CardWithdrawal.java Her deneme yeni bir transaction; çakışmada iş birkaç kez yeniden denenir.

          src/main/java/com/bank/card/CardWithdrawal.java
          @Service
          class CardWithdrawal {
          private static final int MAX_ATTEMPTS = 3;
          private final AccountRepository accounts;
          private final TransactionTemplate tx;
          CardWithdrawal(AccountRepository accounts, TransactionTemplate tx) {
          this.accounts = accounts;
          this.tx = tx;
          }
          // Not @Transactional itself: every attempt must start a fresh transaction.
          void withdraw(String iban, BigDecimal amount, String currency) {
          for (int attempt = 1; ; attempt++) {
          try {
          tx.executeWithoutResult(status ->
          accounts.findById(iban).orElseThrow().debit(amount, currency));
          return;
          } catch (ConcurrencyFailureException e) {
          // Optimistic conflict, serialization failure or deadlock victim: all mean "try again".
          if (attempt == MAX_ATTEMPTS) throw e;
          }
          }
          }
          }

          counter-example/ ReadComputeWrite.java Şöyle de yazılabilirdi, ama bak ne oluyor: iki ATM aynı bakiyeyi okuyor ve bir çekiş kayboluyor.

          counter-example/ReadComputeWrite.java
          // Reads fine and passes every single-user test.
          @Transactional
          public void withdraw(String iban, BigDecimal amount) {
          BigDecimal balance = jdbc.queryForObject(
          "SELECT balance FROM account WHERE iban = ?", BigDecimal.class, iban);
          if (balance.compareTo(amount) < 0) throw new InsufficientFundsException(iban);
          // Under READ COMMITTED two ATMs can both read the same balance above.
          // Both write their own result here, and one withdrawal disappears.
          jdbc.update("UPDATE account SET balance = ? WHERE iban = ?", balance.subtract(amount), iban);
          }
          Kafam karıştı, daha basit anlat

          Güncellemenin kaybolmaması için her zaman seviyeyi yükseltmen gerekmez. Çoğu zaman hesabı doğrudan veritabanına yaptırmak (SET stok = stok - 1) yeter.

          Hızlı kontrolOrta

          Bu metot yük altında bazen para kaybettiriyor. Hatalı satır hangisi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Hatalı satıra dokun, sonra kontrol et.

          WalletService.java
          Java 21UTF-8LF

          Stok düşümünde lost update var. İzolasyon seviyesini değiştirmeden en sade düzeltme hangisi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Tuzaklar

          @Transactional seviye seçmez. Belirtmezsen veritabanının varsayılanı geçerlidir; üçünde de READ COMMITTED.

          WITH (NOLOCK). SQL Server’da READ UNCOMMITTED demektir: commit edilmemiş, hatta iki kez okunmuş satırlar gelebilir.

          RCSI açık mı? SQL Server’da READ_COMMITTED_SNAPSHOT açıksa okuyucular beklemez; Azure SQL Database’de varsayılan olarak açıktır. Aynı kod iki ortamda farklı davranır.

          Yeniden denemeyen yüksek izolasyon. Serialization hatası ya da deadlock kurbanı bir hata değil, “tekrar dene” demektir. Tekrar deneme yoksa kullanıcı 500 görür.

          Hızlı kontrolOrta

          SQL Server'daki raporlar yavaş diye her sorguya `WITH (NOLOCK)` eklendi. Ne riske girildi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Kendini sına

          Şimşek turu1/5

          Transaction kullanmak her eşzamanlılık sorununu kendiliğinden önler.

          Soru 1/2İleri

          Oracle'da nöbet kuralı (en az bir doktor nöbette) SERIALIZABLE seviyede korunuyor sanılıyordu, ama iki doktor aynı anda çıkabildi. Neden?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Aklında kalacak üç şey

          1. 1 Transaction kullanmak, aynı anda çalışan işlerin birbirini bozmayacağı anlamına gelmez. Üç motorun varsayılanında da oku-hesapla-yaz kalıbında bir güncelleme kaybolur.
          2. 2 Seviyenin adı değil, motorun o seviyedeki davranışı önemlidir: SQL Server kilitle bekletir, PostgreSQL ve Oracle eski sürümü gösterir.
          3. 3 Daha sıkı koruma, yanlış sonucu bir hataya çevirir. Uygulama o hatada işi yeniden denemeyi bilmelidir.
          Sonraki kapı Biri satırı okurken başkası onu değiştiriyor. Okuyan bekler mi, yoksa eski bir kopya mı görür? MVCC ve Kilitler — Eski Satır Sürümleri Nerede Yaşar? · 9 dk

          5 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı