İçeriğe geç
academia.sh

Ders 09 / 20

Kaydetme Noktaları

SAVEPOINT ile işlem içinde ara durak tanımlama, ROLLBACK TO ile kısmi geri alma, RELEASE'in kesinleştirme olmadığı, iç içe noktaların davranışı ve aynı noktaya birden çok kez dönme.

İçindekiler

Önceki derste geri alma tek düğmeliydi: ROLLBACK işlemin başına döner, arada yapılmış her şeyi siler. Toplu işlerde bu fazla kaba kalır. Yüz kitaplık bir envanter aktarımında üçüncü kayıt hatalıysa, ilk ikisini de atmak gereksiz iş demektir; aktarımın baştan başlaması gerekir.

Standart SQL bunun için işlem içinde ara duraklar tanımlar. Kaydetme noktası (savepoint), işlemin belirli bir anına verilen addır; geri alma o ada kadar yapılabilir ve öncesi korunur.

Ara Durak Tanımlamak

Üç deyim vardır. SAVEPOINT ad bulunulan noktaya ad verir. ROLLBACK TO ad o noktadan sonra yapılmış değişiklikleri siler, ama işlemi bitirmez. RELEASE ad noktayı listeden düşürür ve o noktaya artık dönülemez.

sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, tur TEXT NOT NULL);
INSERT INTO kitap VALUES (1,'Kayip Zaman','roman');

BEGIN;
INSERT INTO kitap VALUES (11,'Ada','roman');

SAVEPOINT parti2;
INSERT INTO kitap VALUES (12,'Kuyu','roman');
INSERT INTO kitap VALUES (13,'Yaz Gunlugu','deneme');
ROLLBACK TO parti2;

INSERT INTO kitap VALUES (14,'Kum Saati','siir');
COMMIT;

SELECT id, baslik FROM kitap ORDER BY id;
SQL
┌────┬─────────────┐
│ id │   baslik    │
├────┼─────────────┤
│ 1  │ Kayip Zaman │
│ 11 │ Ada         │
│ 14 │ Kum Saati   │
└────┴─────────────┘

Dört satır eklenmeye çalışıldı, üçü kesinleşti. parti2 noktasından sonra eklenen 12 ve 13 numaralı kitaplar silindi; noktadan önce eklenen 11 numaralı kitap korundu. Geri almadan sonra işlem sürdü ve 14 numaralı kitap eklendi — ROLLBACK TO, işlemi kapatmaz.

Bu, toplu aktarımın standart deseninin temelidir: her parti öncesinde bir kaydetme noktası konur, parti hatasız biterse nokta serbest bırakılır, hata verirse o noktaya dönülüp parti atlanır. Önceki partilerin işi korunur.

RELEASE Kesinleştirme Değildir

RELEASE sözcüğü yanıltıcıdır; hiçbir şeyi kalıcı yapmaz. Yaptığı tek şey, o adı geçerli noktalar listesinden çıkarmaktır. Nokta serbest bırakıldıktan sonra yapılan değişiklikler hâlâ işlemin parçasıdır ve dış ROLLBACK onları da siler:

sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, tur TEXT NOT NULL);

BEGIN;
SAVEPOINT parti;
INSERT INTO kitap VALUES (11,'Ada','roman');
RELEASE parti;
SELECT 'release sonrasi' AS an, COUNT(*) AS satir FROM kitap;
ROLLBACK;
SELECT 'rollback sonrasi' AS an, COUNT(*) AS satir FROM kitap;
SQL
┌─────────────────┬───────┐
│       an        │ satir │
├─────────────────┼───────┤
│ release sonrasi │ 1     │
└─────────────────┴───────┘
┌──────────────────┬───────┐
│        an        │ satir │
├──────────────────┼───────┤
│ rollback sonrasi │ 0     │
└──────────────────┴───────┘

Serbest bırakma sonrasında satır duruyordu; işlemin geri alınmasıyla kayboldu. Kalıcılığı sağlayan tek deyim COMMIT’tir. Kaydetme noktaları, işlemin içindeki yapıyı düzenler; işlemin dışarıya karşı sözünü değiştirmez.

Bu ayrım, kaydetme noktalarının “iç içe işlem” olmadığını da söyler. Gerçek bir iç içe işlem, iç işlemi dış işlemden bağımsız kesinleştirebilirdi. Kaydetme noktalarında böyle bir şey yoktur: dış işlem geri alınırsa içerideki her şey gider.

İç İçe Noktalar

Kaydetme noktaları yığıt gibi davranır: her yeni nokta öncekilerin üstüne konur. Bir noktaya dönüldüğünde, o noktadan sonra tanımlanmış tüm noktalar da geçersizleşir.

sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, tur TEXT NOT NULL);

BEGIN;
INSERT INTO kitap VALUES (11,'Ada','roman');
SAVEPOINT dis;
  INSERT INTO kitap VALUES (12,'Kuyu','roman');
  SAVEPOINT ic;
    INSERT INTO kitap VALUES (13,'Yaz Gunlugu','deneme');
  ROLLBACK TO ic;
  INSERT INTO kitap VALUES (14,'Kum Saati','siir');
ROLLBACK TO dis;
INSERT INTO kitap VALUES (15,'Uzak Kiyi','roman');
COMMIT;

SELECT id, baslik FROM kitap ORDER BY id;
SQL
┌────┬───────────┐
│ id │  baslik   │
├────┼───────────┤
│ 11 │ Ada       │
│ 15 │ Uzak Kiyi │
└────┴───────────┘

Beş ekleme yapıldı, ikisi kaldı. ROLLBACK TO ic yalnız 13 numaralı satırı sildi; 12 ve 14 ayakta kaldı. Sonra ROLLBACK TO dis çalıştı ve dis noktasından sonraki her şeyi — 12 ve 14 numaralı satırları, ayrıca ic noktasının kendisini — sildi. Dış noktadan önce eklenen 11 numaralı satır korundu, geri almadan sonra eklenen 15 numaralı satır kesinleşti.

Girintiler yalnız okumaya yardım eder; SQL’de anlamları yoktur. Yığıt davranışını okunur kılan tek şey, adların kapsamı yansıtacak biçimde seçilmesidir.

Aynı Noktaya Birden Çok Kez Dönmek

ROLLBACK TO, geri döndüğü noktayı tüketmez. Nokta yerinde kalır ve tekrar kullanılabilir. Bu, bir işlemin içinde birkaç seçeneği sırayla denemeyi mümkün kılar:

sqlite3 -box -header <<'SQL'
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, tur TEXT NOT NULL);
INSERT INTO kitap VALUES (11,'Ada','roman');

BEGIN;
SAVEPOINT deneme;
INSERT INTO kitap VALUES (11,'Cakisan','roman');
ROLLBACK TO deneme;
INSERT INTO kitap VALUES (12,'Kuyu','roman');
ROLLBACK TO deneme;
INSERT INTO kitap VALUES (13,'Yaz Gunlugu','deneme');
RELEASE deneme;
COMMIT;

SELECT id, baslik FROM kitap ORDER BY id;
SQL
Runtime error near line 6: UNIQUE constraint failed: kitap.id (19)
┌────┬─────────────┐
│ id │   baslik    │
├────┼─────────────┤
│ 11 │ Ada         │
│ 13 │ Yaz Gunlugu │
└────┴─────────────┘

deneme noktasına iki kez dönüldü ve ikisinde de çalıştı. İlk deneme kısıt ihlaline takıldı; ikincisi başarılıydı ama yine geri alındı; üçüncüsü kesinleşti. Bir öncekiler işlemde iz bırakmadı.

Aynı ada ikinci kez SAVEPOINT yazılırsa yeni bir nokta oluşur ve eskisi gölgelenir; geri alma en son tanımlanana döner. Karışıklığı önlemek için adların benzersiz seçilmesi ya da noktanın kullanımdan sonra serbest bırakılması gerekir.

Parti Deseni

Desenin bütünü, hatayı yakalayıp karar veren bir uygulama koduyla tamamlanır. Aşağıdaki betik üç partilik bir aktarım yapar; ikinci partide çakışan bir kimlik vardır:

cat > aktarim.mjs <<'JS'
import { DatabaseSync } from 'node:sqlite';

const db = new DatabaseSync(':memory:');
db.exec(`CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
                            tur TEXT NOT NULL);
         INSERT INTO kitap VALUES (11,'Ada','roman');`);

const partiler = [
  [[21, 'Kuyu', 'roman'], [22, 'Kum Saati', 'siir']],
  [[23, 'Yaz Gunlugu', 'deneme'], [11, 'Cakisan', 'roman']],
  [[24, 'Uzak Kiyi', 'roman']],
];

const ekle = db.prepare('INSERT INTO kitap VALUES (?, ?, ?)');
db.exec('BEGIN');
partiler.forEach((parti, sira) => {
  db.exec(`SAVEPOINT parti_${sira}`);
  try {
    for (const satir of parti) ekle.run(...satir);
    db.exec(`RELEASE parti_${sira}`);
    console.log(`parti ${sira}: kabul (${parti.length} satır)`);
  } catch (hata) {
    db.exec(`ROLLBACK TO parti_${sira}`);
    console.log(`parti ${sira}: atlandı — ${hata.message}`);
  }
});
db.exec('COMMIT');

for (const s of db.prepare('SELECT id, baslik FROM kitap ORDER BY id').all()) {
  console.log(s.id, s.baslik);
}
JS
node aktarim.mjs
parti 0: kabul (2 satır)
parti 1: atlandı — UNIQUE constraint failed: kitap.id
parti 2: kabul (1 satır)
11 Ada
21 Kuyu
22 Kum Saati
24 Uzak Kiyi

İkinci partinin ilk satırı — 23 numaralı kitap — geçerliydi ve eklenmişti; parti geri alınınca o da gitti. Bu, desenin bilinçli bir tercihidir: parti bir bütün olarak kabul edilir ya da tümüyle reddedilir. Geçerli satırların korunması isteniyorsa parti boyutu bire indirilir, o zaman da her satır için ayrı bir nokta maliyeti doğar.

Betikte RELEASE yalnız başarılı partilerde çalışıyor. Başarısız parti için nokta yerinde kalır, ancak COMMIT tüm noktaları kapattığı için bu bir sızıntı oluşturmaz.

Nerede Kullanılır

Kaydetme noktalarının üç tipik kullanım alanı vardır.

Toplu aktarım. Her partiden önce nokta konur; parti hatalıysa o noktaya dönülüp kayıt bir hata kütüğüne yazılır ve aktarım sürer. Alternatif, her partiyi ayrı işlem yapmaktır; o zaman da partiler arası bütünlük garantisi kaybolur.

Denenip vazgeçilebilen adımlar. Bir hesabın maliyetli bir yolu önce denenir, sonuç uygun değilse geri alınıp başka yol seçilir. İşlemin bütünü açık kaldığı için ara sonuçlar dışarıya sızmaz.

Kütüphane sınırlarında yeniden kullanılabilir kod. Kendisi işlem açıp açmadığını bilmeyen bir işlev, koşulsuz BEGIN yazamaz — zaten açık bir işlemin içindeyse hata alır. Bunun yerine bir kaydetme noktası açar; hata olursa yalnız kendi yaptığını geri alır ve çağıranın işlemine dokunmaz. Çoğu veri erişim kitaplığındaki “iç içe işlem” desteği gerçekte budur.

Maliyet tarafında dikkat edilecek nokta, kaydetme noktalarının bedava olmamasıdır: motor, her noktadan sonraki değişiklikleri geri alabilmek için ek kayıt tutar. Satır başına bir nokta açan bir aktarım, işin kendisinden çok muhasebesini büyütür. Nokta sayısı parti düzeyinde tutulur, satır düzeyinde değil.

Özet

  • SAVEPOINT işlem içinde bir ara durağa ad verir; ROLLBACK TO o noktadan sonrasını siler ve işlemi kapatmaz.
  • RELEASE kesinleştirme değildir; yalnız noktayı listeden düşürür, kalıcılığı sağlayan tek deyim COMMIT’tir.
  • Noktalar yığıt gibi davranır: bir noktaya dönülünce ondan sonra tanımlanmış noktalar da geçersizleşir.
  • ROLLBACK TO noktayı tüketmez; aynı noktaya birden çok kez dönülerek farklı seçenekler denenebilir.
  • Nokta başına ek kayıt tutulduğu için kaydetme noktaları parti düzeyinde kullanılır, satır düzeyinde değil.

Sonraki Adım

Bu iki ders boyunca veritabanına yalnız bir bağlantı erişiyordu; işlemin gördüğü tek şey kendi değişiklikleriydi. Gerçek sistemlerde ise aynı anda birçok bağlantı okur ve yazar. “Kesinleşmemiş değişikliği başkası görebilir mi”, “aynı sorgu işlem içinde iki kez çalıştırılınca aynı sonucu verir mi” soruları o zaman anlam kazanır. Sonraki ders, bu soruların yanıtlarını düzeye bağlayan yalıtım kavramını tanımlayacak ve standartta tanımlanan okuma anomalilerini tek tek ele alacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat