---
title: 'Toplu Veri Yükleme'
source: 'https://academia.sh/tr/kurslar/veritabani-yonetimi/toplu-veri-yukleme'
course: 'İlişkisel Veritabanı Yönetimi'
language: tr
updated: '2026-08-17T18:08:55+00:00'
license: 'CC BY-SA 4.0'
---

# Toplu Veri Yükleme

Yüksek hacimli içe ve dışa aktarımın maliyeti: işlem sınırının yükleme hızına etkisi, dizinlerin yükleme sırasında mı sonrasında mı kurulacağı, hazırlık tablosuyla bozuk satırların ayıklanması ve dışa aktarımın biçimi.

İzleme, sistemin olağan yükü altında ne yaptığını gösterir. Bazı işler ise olağan yükün
dışındadır ve sistemi bilerek zorlar: bir arşivin içeri aktarılması, başka bir kurumdan
gelen üye listesinin yüklenmesi, yıllık ödünç kayıtlarının dışarı çıkarılması. Bu işler
milyonlarca satırı tek seferde taşır.

Toplu yüklemenin sıradan yazmadan ayrıldığı nokta şudur: sıradan yazmada bir satırın
maliyeti önemsizdir, toplu yüklemede satır başına düşen her maliyet satır sayısıyla
çarpılır. Bu ders o çarpanların nereden geldiğini ve nasıl kaldırıldığını ele alıyor.

## İşlem Sınırının Bedeli

Her kesinleştirme, motorun değişikliği kalıcı hâle getirmesini gerektirir: günlük diske
yazılır ve yazmanın gerçekten tamamlandığı doğrulanır. Bu adım kursun Motor Mimarisi
konusunda ele alınmıştı ve orada bir satırın güvencesi olarak sunulmuştu. Toplu yüklemede
aynı adım, satır sayısı kadar tekrarlanan bir maliyete dönüşür.

Aşağıdaki blok aynı otuz bin satırı beş farklı parti büyüklüğüyle yüklüyor. Mutlak süreler
makineye, diske ve dosya sistemine bağlıdır; anlamlı olan satırlar arasındaki orandır.

```bash
rm -f yukleme.db yukleme.db-journal

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const fs = require("node:fs");
const N = 30000;
const SEMA = `CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL,
                uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL)`;

const olc = (ad, parti) => {
  fs.rmSync("yukleme.db", { force: true });
  fs.rmSync("yukleme.db-journal", { force: true });
  const db = new DatabaseSync("yukleme.db");
  db.exec("PRAGMA synchronous=FULL");
  db.exec(SEMA);
  const ekle = db.prepare(
    "INSERT INTO odunc(kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?)");
  const t = process.hrtime.bigint();
  for (let i = 0; i < N; i += parti) {
    if (parti > 1) db.exec("BEGIN");
    for (let j = i; j < Math.min(i + parti, N); j++)
      ekle.run(j % 400 + 1, j % 900 + 1, j % 3 + 1, "2025-06-01");
    if (parti > 1) db.exec("COMMIT");
  }
  const ms = Number(process.hrtime.bigint() - t) / 1e6;
  db.close();
  console.log(ad.padEnd(26) + " | " + String(Math.ceil(N / parti)).padStart(6) + " | " +
    (ms.toFixed(0) + " ms").padStart(9) + " | " +
    Math.round(N / (ms / 1000)).toLocaleString("tr-TR").padStart(11));
};

console.log("yol                        | islem  | sure      | satir/saniye");
console.log("---------------------------|--------|-----------|------------");
olc("satir basina bir islem", 1);
olc("100 satirlik partiler", 100);
olc("1.000 satirlik partiler", 1000);
olc("10.000 satirlik partiler", 10000);
olc("tek islem", N);
EOF
```

```text
yol                        | islem  | sure      | satir/saniye
---------------------------|--------|-----------|------------
satir basina bir islem     |  30000 |   5864 ms |       5.116
100 satirlik partiler      |    300 |     66 ms |     454.359
1.000 satirlik partiler    |     30 |     14 ms |   2.162.182
10.000 satirlik partiler   |      3 |     10 ms |   3.094.565
tek islem                  |      1 |      9 ms |   3.373.219
```

Satır başına bir işlem ile tek işlem arasındaki fark altı yüz kattan fazla. Yüklenen veri
aynı, yazılan satırlar aynı, yapılan iş aynı — fark yalnız kaç kez kesinleştirildiğidir.

Eğrinin biçimi de önemlidir. Kazancın büyük bölümü ilk adımda, yüzlük partilere geçişte
elde ediliyor; binden sonrası küçük iyileşme veriyor. Bu, parti büyüklüğünün olabildiğince
büyük seçilmesi gerektiği anlamına gelmez. Tek bir dev işlemin üç sakıncası vardır:
başarısız olduğunda baştan başlanır, geri alma bilgisi işlem süresince birikir ve uzun
süren yazma işlemi kursun Motor Mimarisi konusunda ele alınan ölü satır temizliğini
geciktirir. Yüz bin ile bir milyon satır arasındaki bir parti büyüklüğü, ölçülen kazancın
neredeyse tamamını verirken bu üç sakıncayı da sınırlar.

Parti sınırı aynı zamanda bir ilerleme noktasıdır. Her partinin sonunda kaç satırın
yüklendiği bilinir; yükleme kesildiğinde kalınan yerden sürdürülebilir. Bunun için gelen
verinin sırasının belirlenimci olması ve yüklenen son anahtarın kaydedilmesi yeter.

## Dizinlerin Yükleme Sırasındaki Maliyeti

İkinci çarpan dizinlerdir. Tabloya eklenen her satır, her dizine de bir giriş ekler ve bu
girişin ağacın doğru yerine yerleştirilmesi gerekir. Satırlar dizin anahtarına göre
sıralı gelmediğinde, her ekleme ağacın rastgele bir yerine dokunur.

Alternatif, dizinleri yükleme bittikten sonra kurmaktır: motor o zaman bütün anahtarları
bir kerede sıralar ve ağacı aşağıdan yukarı, sırayla oluşturur.

```bash
rm -f dizinli.db dizinli.db-journal

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const fs = require("node:fs");
const N = 200000;
const SEMA = `CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL,
                uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL)`;
const DIZIN = ["CREATE INDEX odunc_uye ON odunc(uye_id)",
               "CREATE INDEX odunc_kitap ON odunc(kitap_id)",
               "CREATE INDEX odunc_sube_alis ON odunc(sube_id, alis)"];
const sure = (is) => { const t = process.hrtime.bigint(); is();
                       return Number(process.hrtime.bigint() - t) / 1e6; };

const olc = (ad, oncedenDizin) => {
  fs.rmSync("dizinli.db", { force: true });
  fs.rmSync("dizinli.db-journal", { force: true });
  const db = new DatabaseSync("dizinli.db");
  db.exec(SEMA);
  if (oncedenDizin) for (const d of DIZIN) db.exec(d);
  const ekle = db.prepare(
    "INSERT INTO odunc(kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?)");
  const yukleme = sure(() => {
    db.exec("BEGIN");
    for (let i = 0; i < N; i++) ekle.run(i % 400 + 1, i % 900 + 1, i % 3 + 1, "2025-06-01");
    db.exec("COMMIT");
  });
  const dizinKurma = oncedenDizin ? 0 : sure(() => { for (const d of DIZIN) db.exec(d); });
  db.close();
  console.log(ad.padEnd(24) + " | " + (yukleme.toFixed(0) + " ms").padStart(9) + " | " +
    (dizinKurma ? dizinKurma.toFixed(0) + " ms" : "-").padStart(9) + " | " +
    ((yukleme + dizinKurma).toFixed(0) + " ms").padStart(9) + " | " +
    (Math.round(fs.statSync("dizinli.db").size / 1024) + " KB").padStart(9));
};

console.log(N.toLocaleString("tr-TR") + " satir, uc dizin");
console.log();
console.log("yol                      | yukleme   | dizin     | toplam    | dosya");
console.log("-------------------------|-----------|-----------|-----------|----------");
olc("dizinler onceden var", true);
olc("dizinler sonradan", false);
EOF
```

```text
200.000 satir, uc dizin

yol                      | yukleme   | dizin     | toplam    | dosya
-------------------------|-----------|-----------|-----------|----------
dizinler onceden var     |    551 ms |         - |    551 ms |  14128 KB
dizinler sonradan        |     60 ms |     84 ms |    144 ms |  13352 KB
```

Yükleme süresi dokuz kat düştü; dizin kurma süresi eklendiğinde bile toplam yaklaşık dörtte
bire indi. Dosya da küçüldü: toplu kurulan bir dizin sayfalarını sıkı doldurur, satır satır
büyütülen dizin ise sayfa bölünmeleri nedeniyle boşluk bırakır. Bu boşluk, dizinlerin
bakımı konusunda — bu kursun Dizinler ve Bölümleme konusunda — şişme başlığı altında ele
alınan durumdur.

Yöntemin bedeli, dizinlerin bulunmadığı süre boyunca tablonun okuma başarımının düşmesidir.
İlk yükleme yapılan boş bir tabloda bu bedel yoktur. Var olan ve kullanılan bir tabloya
büyük bir aktarım yapılıyorsa dizinleri düşürmek seçenek değildir; o durumda parti
büyüklüğü ayarlanır ve yükleme yoğun olmayan bir saate alınır.

Aynı gerekçe kısıtlar için de geçerlidir. Yabancı anahtar denetimi her satırda bir arama
yapar; benzersizlik kısıtı her satırda bir denetim gerektirir. Motorların çoğu kısıtların
işlem sonuna ertelenmesine izin verir; ertelenen denetim satır başına değil, bir kerede
yapılır.

## Hazırlık Tablosu ve Bozuk Satırlar

Dışarıdan gelen veri temiz gelmez. Tek bir bozuk satır, doğrudan hedef tabloya yazan bir
yüklemeyi işlemin ortasında durdurur ve o ana kadar yapılan iş geri alınır. Yirmi bin
satırın bin üç yüzüncüsünde duran bir yükleme, hem zaman kaybettirir hem de sorunun
tamamını göstermez — sıradaki bozuk satırlar henüz görülmemiştir.

Yerleşik çözüm bir **hazırlık tablosudur (staging table)**: gelen dosya, her sütunu metin
olan ve hiçbir kısıt taşımayan bir tabloya olduğu gibi alınır. Doğrulama, veri veritabanına
girdikten sonra sorguyla yapılır. Geçerli satırlar hedefe taşınır, geçersizler nedeniyle
birlikte ayrılır.

```bash
rm -f aktarim.db odunc_gelen.csv temiz.csv

# Disaridan gelen dosya: 20.000 satirlik bir arsiv aktarimi, icinde bozuk satirlar var.
node - <<'EOF' > odunc_gelen.csv
console.log("id,kitap_id,uye_id,sube_id,alis");
for (let i = 1; i <= 20000; i++) {
  if (i === 77) { console.log("yetmisyedi,12,34,1,2025-05-04"); continue; }   // sayi degil
  if (i === 512) { console.log("512,12,34,1,04.05.2025"); continue; }         // tarih bicimi
  if (i === 900) { console.log("900,12,99999,1,2025-05-04"); continue; }      // olmayan uye
  if (i === 1301) { console.log("1301,12,34,1"); continue; }                  // eksik alan
  console.log([i, (i % 400) + 1, (i % 900) + 1, (i % 3) + 1, "2025-05-04"].join(","));
}
EOF

sqlite3 aktarim.db <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT NOT NULL);
WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<900)
INSERT INTO uye(id,ad) SELECT n,'Uye-'||n FROM s;
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL, uye_id INT NOT NULL
                     REFERENCES uye(id), sube_id INT NOT NULL, alis TEXT NOT NULL);
-- Hazirlik tablosu: her sutun metin, hicbir kisit yok.
CREATE TABLE hazirlik(id TEXT, kitap_id TEXT, uye_id TEXT, sube_id TEXT, alis TEXT);
CREATE TABLE hatali(satir TEXT, neden TEXT);
SQL

sqlite3 aktarim.db ".import --csv --skip 1 odunc_gelen.csv hazirlik"
echo "hazirlik tablosuna alinan satir: $(sqlite3 aktarim.db 'SELECT COUNT(*) FROM hazirlik;')"

sqlite3 -box -header aktarim.db <<'SQL'
INSERT INTO hatali(satir, neden)
SELECT id || ',' || IFNULL(kitap_id,'') || ',' || IFNULL(uye_id,'') || ',' ||
       IFNULL(sube_id,'') || ',' || IFNULL(alis,''),
       CASE
         WHEN id GLOB '*[^0-9]*' OR uye_id GLOB '*[^0-9]*' THEN 'sayisal olmayan alan'
         WHEN alis IS NULL OR alis NOT GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
              THEN 'tarih bicimi'
         WHEN NOT EXISTS (SELECT 1 FROM uye WHERE uye.id = CAST(hazirlik.uye_id AS INTEGER))
              THEN 'olmayan uye'
       END
FROM hazirlik
WHERE id GLOB '*[^0-9]*' OR uye_id GLOB '*[^0-9]*'
   OR alis IS NULL OR alis NOT GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
   OR NOT EXISTS (SELECT 1 FROM uye WHERE uye.id = CAST(hazirlik.uye_id AS INTEGER));

INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis)
SELECT CAST(id AS INTEGER), CAST(kitap_id AS INTEGER), CAST(uye_id AS INTEGER),
       CAST(sube_id AS INTEGER), alis
FROM hazirlik
WHERE id NOT GLOB '*[^0-9]*' AND uye_id NOT GLOB '*[^0-9]*'
  AND alis GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
  AND EXISTS (SELECT 1 FROM uye WHERE uye.id = CAST(hazirlik.uye_id AS INTEGER));

SELECT (SELECT COUNT(*) FROM hazirlik) AS gelen,
       (SELECT COUNT(*) FROM odunc) AS alinan,
       (SELECT COUNT(*) FROM hatali) AS ayrilan;
SELECT neden, COUNT(*) AS satir FROM hatali GROUP BY neden ORDER BY satir DESC;
SQL

# Disari aktarim: ayni bicimde bir dosya uretilir.
sqlite3 aktarim.db <<'SQL'
.mode csv
.headers on
.output temiz.csv
SELECT id, kitap_id, uye_id, sube_id, alis FROM odunc WHERE sube_id=1;
SQL
echo "disari yazilan satir: $(($(wc -l < temiz.csv) - 1))"
head -2 temiz.csv
```

```text
odunc_gelen.csv:1302: expected 5 columns but found 4 - filling the rest with NULL
hazirlik tablosuna alinan satir: 20000
┌───────┬────────┬─────────┐
│ gelen │ alinan │ ayrilan │
├───────┼────────┼─────────┤
│ 20000 │ 19996  │ 4       │
└───────┴────────┴─────────┘
┌──────────────────────┬───────┐
│        neden         │ satir │
├──────────────────────┼───────┤
│ tarih bicimi         │ 2     │
│ sayisal olmayan alan │ 1     │
│ olmayan uye          │ 1     │
└──────────────────────┴───────┘
disari yazilan satir: 6665
id,kitap_id,uye_id,sube_id,alis
3,4,4,1,2025-05-04
```

Yükleme durmadı. Yirmi bin satırın tamamı veritabanına girdi, on dokuz bin dokuz yüz doksan
altısı hedefe taşındı ve dört satır nedeniyle birlikte ayrıldı. Ayrılan satırların
listesi düzeltilip yeniden yüklenebilir; bu, tek bir hatalı satır yüzünden bütün aktarımın
tekrarlanmasından çok daha ucuzdur.

Çıktının ilk satırı ayrıca öğreticidir. Eksik alanlı satır, içe aktarım aracı tarafından
bir uyarıyla karşılandı ve eksik sütun boş değerle dolduruldu — yani sessizce
reddedilmedi, sessizce **kabul edildi**. Doğrulama katmanı olmasaydı bu satır tarih alanı
boş olarak hedefe girerdi. İçe aktarma araçlarının hatalı biçimi nasıl karşıladığı motora
ve araca göre değişir; hangi davranışın geçerli olduğu varsayılmaz, sınanır.

Dışa aktarım aynı sözleşmenin ters yönüdür: sütun sırası, ayırıcı, başlık satırı ve tarih
biçimi karşı taraf için belirlenir. Aktarımların iki yönü de aynı biçim tanımına bağlıysa,
dışarı verilen dosya kendi hazırlık tablosuna geri yüklenerek sınanabilir.

## Yükleme Sırasındaki Diğer Kararlar

Üç ayar daha yükleme süresini belirler ve üçü de aynı ödünleşimi taşır: dayanıklılığın
geçici olarak azaltılması.

**Günlük ayarı.** Motorların bir bölümü, toplu yükleme sırasında günlüğe daha az yazan bir
kip sunar. Kazanç büyüktür; karşılığında yükleme sırasında bir çökme olursa tablo
kurtarılamaz duruma gelebilir ve yükleme baştan yapılır. Boş bir tabloya ilk yükleme
yapılırken kabul edilebilir, var olan veriye eklerken değil.

**Eşitleme ayarı.** Kesinleştirmede diske yazmanın tamamlanmasını beklemeyen bir ayar,
küçük partilerde büyük fark yaratır. Yükleme bittikten sonra eski değere döndürülmesi
gerekir; bu adımın unutulması, sistemin sonraki aylarını sessizce risk altında bırakır.

**Kopyalama komutu.** Motorların çoğu, satır satır ekleme yerine akış hâlinde veri alan bir
komut sağlar. Yukarıdaki içe aktarım bunun bir örneğidir. Kazancı, deyim ayrıştırma ve
tekil kayıt işleme yükünün ortadan kalkmasıdır.

Son olarak yüklemenin ardından istatistiklerin yenilenmesi gerekir. Planlayıcı, tabloda kaç
satır olduğunu ve değerlerin nasıl dağıldığını kendi topladığı istatistiklerden bilir;
İleri SQL kursunda ele alınan bu bilgi, toplu yüklemeden sonra gerçekle bağını yitirir.
Yenilenmediğinde planlayıcı boş sandığı bir tabloyu tarar ve yükleme sonrası sorgular
beklenmedik biçimde yavaşlar.

## Özet

- Kesinleştirme sayısı toplu yüklemenin birinci maliyetidir; satır başına bir işlem ile
  partili yükleme arasında ölçümde altı yüz katı aşan fark çıktı.
- Kazancın büyük bölümü ilk birkaç yüz satırlık partide elde edilir; çok büyük partiler
  yeniden başlama maliyetini ve geri alma bilgisini büyütür.
- Dizinler yükleme sırasında satır başına bakım gerektirir; sonradan toplu kurulduğunda
  hem süre düşer hem de dizin daha az yer kaplar.
- Hazırlık tablosu, gelen veriyi kısıtsız biçimde alıp doğrulamayı sorguya taşır; bozuk
  satırlar yüklemeyi durdurmadan nedeniyle birlikte ayrılır.
- İçe aktarma araçları hatalı biçimli satırları sessizce kabul edebilir; davranış
  varsayılmaz, sınanır.
- Yükleme sonrası istatistiklerin yenilenmesi gerekir; yenilenmediğinde planlayıcı eski
  bilgiyle çalışır.

## Sonraki Adım

Bu konudaki dersler bir veritabanının yaşamını sürdürmesi için gereken her işi kurdu:
yetkilendirme, yedekleme, kurtarma, çoğaltma, erişilebilirlik, bağlantı yönetimi, izleme ve
toplu aktarım. Geriye bir tanesi kaldı ve hepsini birden ilgilendirir. Motorun sürümü
zamanla değişir; güvenlik düzeltmeleri, hata gidermeleri ve desteğin sona ermesi yükseltmeyi
zorunlu kılar. Yükseltme, çalışan bir sistemin altındaki katmanı değiştirmektir: kesinti
penceresi, uyumluluk denetimi, geri dönüş planı ve uygulamanın iki sürümle birden
çalışabilmesi gerekir. Son ders bunu ele alıyor.
