---
title: 'Tablo Bölümleme'
source: 'https://academia.sh/tr/kurslar/veritabani-yonetimi/tablo-bolumleme'
course: 'İlişkisel Veritabanı Yönetimi'
language: tr
updated: '2026-08-17T18:08:57+00:00'
license: 'CC BY-SA 4.0'
---

# Tablo Bölümleme

Bir tablonun aralığa, listeye ve karmaya göre parçalara ayrılması, sorgunun yalnız ilgili bölüme dokunduğunun ölçümle gösterilmesi, eski verinin silinmek yerine düşürülmesi ve bölüm anahtarının dağılımının hesaplanması.

Dizin Bakımı dersi bir soruyu askıda bıraktı. Eski ödünç kayıtlarını silmek, dizin
sırasıyla örtüşmediği her yerde ölü boşluk bıraktı ve o boşluğu geri almak tabloyu baştan
yazmayı gerektirdi. Sorunun kaynağı silmenin kendisi değil, silinecek verinin canlı
verinin arasına karışmış olmasıdır.

**Bölümleme (partitioning)**, bu karışmayı baştan önler: mantıksal olarak tek bir tablo,
fiziksel olarak birden çok parçada saklanır. Her parçaya **bölüm (partition)** denir ve
bir satırın hangi bölüme düşeceğini **bölüm anahtarı (partition key)** belirler. Bu ders
üç bölümleme biçimini kurar, kazancını ölçer ve kazancın nereden gelmediğini de gösterir.

## Aralık Bölümlemesi

En yaygın biçim **aralık bölümlemesidir (range partitioning)**: bölüm anahtarının değer
aralıkları bölümlere karşılık gelir. Kütüphane kayıtlarında doğal anahtar alış tarihidir
ve doğal aralık yıldır.

Bazı motorlar bölümlemeyi sözdizimiyle sunar; sunmayanlarda aynı yapı ayrı tablolar ve
bir birleşim görünümüyle kurulur. Aşağıdaki koşum ikinci yolu izler. Her bölüm tablosu
kendi aralığını bir değer denetimiyle bildirir ve bir görünüm bölümleri tek bir ad altında
toplar.

```sh
rm -f bolumsuz.db bolumlu.db bolumle.sql
cat > kurulum.sql <<'SQL'
CREATE TABLE odunc (
  odunc_id    INTEGER PRIMARY KEY,
  kitap_id    INTEGER NOT NULL,
  uye_id      INTEGER NOT NULL,
  alis_tarihi TEXT NOT NULL,
  iade_tarihi TEXT
);
INSERT INTO odunc (odunc_id, kitap_id, uye_id, alis_tarihi, iade_tarihi)
WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 2000000)
SELECT n, 1 + ((n * 7) % 200000), 1 + ((n * 13) % 120000),
       date('2018-01-01', '+' || ((n * 37) % 2437) || ' days'),
       CASE WHEN n % 9 = 0 THEN NULL
            ELSE date('2018-01-01', '+' || (((n * 37) % 2437) + 14) || ' days') END
FROM sayac;
SQL
sqlite3 bolumsuz.db < kurulum.sql
sqlite3 bolumsuz.db 'CREATE INDEX odunc_alis ON odunc(alis_tarihi);'

# Yil basina bir bolum tablosu, deger denetimi ve bolumleri toplayan gorunum.
{
  echo "ATTACH 'bolumsuz.db' AS kaynak;"
  for y in 2018 2019 2020 2021 2022 2023 2024; do
    s=$((y + 1))
    cat <<EOF
CREATE TABLE odunc_$y (
  odunc_id    INTEGER PRIMARY KEY,
  kitap_id    INT NOT NULL,
  uye_id      INT NOT NULL,
  alis_tarihi TEXT NOT NULL CHECK (alis_tarihi >= '$y-01-01' AND alis_tarihi < '$s-01-01'),
  iade_tarihi TEXT);
INSERT INTO odunc_$y SELECT * FROM kaynak.odunc
  WHERE alis_tarihi >= '$y-01-01' AND alis_tarihi < '$s-01-01';
CREATE INDEX odunc_${y}_alis ON odunc_$y(alis_tarihi);
EOF
  done
  echo "CREATE VIEW odunc AS"
  echo "  SELECT * FROM odunc_2018 UNION ALL SELECT * FROM odunc_2019 UNION ALL"
  echo "  SELECT * FROM odunc_2020 UNION ALL SELECT * FROM odunc_2021 UNION ALL"
  echo "  SELECT * FROM odunc_2022 UNION ALL SELECT * FROM odunc_2023 UNION ALL"
  echo "  SELECT * FROM odunc_2024;"
} > bolumle.sql
sqlite3 bolumlu.db < bolumle.sql
sqlite3 bolumlu.db <<'SQL'
.mode column
.headers on
SELECT count(*) AS gorunum_satir FROM odunc;
SELECT name AS bolum, count(*) AS sayfa FROM dbstat
WHERE name LIKE 'odunc\_20__' ESCAPE '\' GROUP BY name ORDER BY name;
SQL
```

```
gorunum_satir
-------------
2000000      
bolum       sayfa
----------  -----
odunc_2018  2966 
odunc_2019  2982 
odunc_2020  2970 
odunc_2021  2959 
odunc_2022  2964 
odunc_2023  2982 
odunc_2024  1994 
```

Görünüm iki milyon satırın hepsini veriyor: uygulama tarafından bakıldığında tablo hâlâ
tektir. Altında yedi ayrı tablo, yedi ayrı dizin ve her biri yaklaşık üç bin sayfalık yedi
ayrı ağaç vardır.

## Yalnız İlgili Bölüme Dokunmak

Bölümlemenin ilk iddiası şudur: bölüm anahtarını süzen bir sorgu diğer bölümleri hiç
okumaz. Bu iddia plan çıktısında **görünmez** — plan yedi bölümü de listeler.

```sh
sqlite3 bolumlu.db "EXPLAIN QUERY PLAN
SELECT count(*) FROM odunc WHERE alis_tarihi BETWEEN '2021-03-01' AND '2021-03-31';"

for hedef in "bolumlu.db|odunc|bolumlu gorunum" \
             "bolumlu.db|odunc_2021|dogrudan 2021 bolumu" \
             "bolumsuz.db|odunc|bolumsuz tek tablo"; do
  db=${hedef%%|*}; kalan=${hedef#*|}; tablo=${kalan%%|*}; ad=${kalan#*|}
  printf '%-22s ' "$ad"
  sqlite3 "$db" <<SQL 2>&1 | grep -E 'Page cache misses|Run Time' | tr '\n' ' '
.stats on
.timer on
SELECT count(*) FROM $tablo WHERE alis_tarihi BETWEEN '2021-03-01' AND '2021-03-31';
SQL
  echo
done
```

```
QUERY PLAN
|--CO-ROUTINE odunc
|  `--COMPOUND QUERY
|     |--LEFT-MOST SUBQUERY
|     |  `--SEARCH odunc_2018 USING INDEX odunc_2018_alis (alis_tarihi>? AND alis_tarihi<?)
|     |--UNION ALL
|     |  `--SEARCH odunc_2019 USING INDEX odunc_2019_alis (alis_tarihi>? AND alis_tarihi<?)
|     |--UNION ALL
|     |  `--SEARCH odunc_2020 USING INDEX odunc_2020_alis (alis_tarihi>? AND alis_tarihi<?)
|     |--UNION ALL
|     |  `--SEARCH odunc_2021 USING INDEX odunc_2021_alis (alis_tarihi>? AND alis_tarihi<?)
|     |--UNION ALL
|     |  `--SEARCH odunc_2022 USING INDEX odunc_2022_alis (alis_tarihi>? AND alis_tarihi<?)
|     |--UNION ALL
|     |  `--SEARCH odunc_2023 USING INDEX odunc_2023_alis (alis_tarihi>? AND alis_tarihi<?)
|     `--UNION ALL
|        `--SEARCH odunc_2024 USING INDEX odunc_2024_alis (alis_tarihi>? AND alis_tarihi<?)
`--SCAN odunc
bolumlu gorunum        Page cache misses:                   141 Run Time: real 0.002 user 0.002195 sys 0.000186 
dogrudan 2021 bolumu   Page cache misses:                   123 Run Time: real 0.001 user 0.000566 sys 0.000115 
bolumsuz tek tablo     Page cache misses:                   123 Run Time: real 0.000 user 0.000570 sys 0.000158
```

Sayfa ıskası sayıları iddiayı doğruluyor. Yalnız 2021 bölümüne doğrudan sorulan sorgu 123
sayfa okudu; görünüm üzerinden sorulan sorgu 141 sayfa. Aradaki 18 sayfa, altı bölümün
dizinine yapılan altı boş yoklamadır — bölüm başına üç sayfa. Yedi bölümün altısı planda
görünüyor ama **verisi hiç okunmuyor**.

Ayrımın adı **bölüm ayıklamasıdır (partition pruning)**. Yerleşik bölümleme sunan
motorlarda ayıklama plan kurulurken yapılır ve ilgisiz bölümler plana hiç girmez; buradaki
modelde çalışma zamanında oluyor, çünkü her boş yoklama kendi dizininde hemen sonuçsuz
kalıyor. İki yolun sonucu yakındır, maliyeti değil: bölüm sayısı yüzlere çıktığında yüzlerce
boş yoklamanın toplamı ihmal edilemez olur.

Üçüncü satır ise beklentiyi düzeltir. Bölümsüz tek tablo aynı sorguyu aynı 123 sayfayla
yanıtladı. Dizin varken bölümleme **okuma başarımı kazandırmaz**; dizin zaten aynı işi
yapıyordu. Bölümlemenin kazancı başka yerdedir.

## Eski Veriyi Silmek ve Bölümü Düşürmek

Asıl kazanç, verinin bütün olarak kaldırılabilmesidir. Aşağıdaki koşum aynı işi iki
kurulumda yapar: 2018 yılının kayıtlarını sistemden çıkarmak.

```sh
rm -f s1.db s2.db
cp bolumsuz.db s1.db
cp bolumlu.db  s2.db
olc() { printf '%-8s dosya=%s  toplam_sayfa=%s  serbest=%s\n' "$2" "$(wc -c < "$1")" \
  "$(sqlite3 "$1" 'PRAGMA page_count;')" "$(sqlite3 "$1" 'PRAGMA freelist_count;')"; }

echo "=== bolumsuz: eski yili silme ==="
olc s1.db once
sqlite3 s1.db <<'SQL' | grep 'Run Time'
.timer on
DELETE FROM odunc WHERE alis_tarihi < '2019-01-01';
SQL
olc s1.db silme
sqlite3 s1.db <<'SQL' | grep 'Run Time'
.timer on
VACUUM;
SQL
olc s1.db vacuum

echo
echo "=== bolumlu: eski bolumu dusurme ==="
olc s2.db once
sqlite3 s2.db <<'SQL' | grep 'Run Time'
.timer on
DROP VIEW odunc;
DROP TABLE odunc_2018;
CREATE VIEW odunc AS
  SELECT * FROM odunc_2019 UNION ALL SELECT * FROM odunc_2020 UNION ALL
  SELECT * FROM odunc_2021 UNION ALL SELECT * FROM odunc_2022 UNION ALL
  SELECT * FROM odunc_2023 UNION ALL SELECT * FROM odunc_2024;
SQL
olc s2.db dusurme
sqlite3 s2.db "SELECT count(*) FROM odunc;"
```

```
=== bolumsuz: eski yili silme ===
once     dosya= 112066560  toplam_sayfa=27360  serbest=0
Run Time: real 0.674 user 0.165852 sys 0.203029
silme    dosya= 112066560  toplam_sayfa=27360  serbest=1398
Run Time: real 0.460 user 0.157747 sys 0.262727
vacuum   dosya= 95285248  toplam_sayfa=23263  serbest=0

=== bolumlu: eski bolumu dusurme ===
once     dosya= 119492608  toplam_sayfa=29173  serbest=0
Run Time: real 0.000 user 0.000095 sys 0.000291
Run Time: real 0.074 user 0.002629 sys 0.007488
Run Time: real 0.000 user 0.000063 sys 0.000187
dusurme  dosya= 119492608  toplam_sayfa=29173  serbest=4367
1700444
```

İki yol aynı 299.556 satırı sistemden çıkardı; maliyetleri karşılaştırılamaz. Silme yolu
0,674 saniye sürdü ve yalnızca 1.398 sayfayı serbest bırakabildi — kalan boşluk sayfaların
içine dağılmıştı. Boşluğu geri almak için çalıştırılan tam yeniden yazma 0,460 saniye daha
aldı ve bu süre boyunca tablo kilitliydi. Toplam bir saniyeyi aştı.

Bölümü düşürme yolu 0,074 saniye sürdü ve 4.367 sayfayı tek işlemde serbest bıraktı.
Yapılan iş satır silmek değil, bir nesneyi kaldırmaktır: maliyet satır sayısıyla değil,
sabit bir katalog işlemiyle ölçülür. Süreler ortama bağlıdır; kararlı olan, birinin
veriyle orantılı, diğerinin veriden bağımsız olmasıdır.

Bu, saklama süresi kuralı olan her sistemde bölümlemenin ana gerekçesidir. Aynı gerekçe
ters yönde de çalışır: yeni bir dönem başladığında boş bir bölüm eklemek de sabit
maliyetlidir.

## Liste ve Karma Bölümlemesi

İki biçim daha vardır. **Liste bölümlemesinde (list partitioning)** her bölüm, anahtarın
belirli değerlerini alır — şehir, şube, ülke. **Karma bölümlemesinde (hash partitioning)**
bölüm, anahtarın bir karma fonksiyonundan geçirilip bölüm sayısına bölünmesiyle bulunur;
amaç anlamlı bir gruplama değil, dengeli bir dağıtımdır.

Seçim, bölümlerin ne kadar dengeli dolacağına bağlıdır ve bu hesaplanabilir. Aşağıdaki
model üye başına ödünç sayısını güç yasasına yakın dağıtır — az sayıda üye çok ödünç alır,
çoğu üye birkaç kayıt bırakır — ve bölüm doluluklarını üç anahtar seçimi için sayar.

```sh
cat > dagilim.mjs <<'JS'
// Model: odunc kayitlarinin bolumlere dagilimi. Gercek bir motorun bolumleyicisi degil;
// olculen sey anahtar secimi ile bolum dolulugu arasindaki iliskidir.
// Uye basina odunc sayisi guc yasasina yakin dagitilir: az sayida uye cok odunc alir.
const UYE = 120000, KAYIT = 2000000;
const sehirler = [['Istanbul', 0.42], ['Ankara', 0.24], ['Izmir', 0.16],
                  ['Bursa', 0.11], ['Konya', 0.07]];

function uyeler() {
  const agirlik = [];
  let toplam = 0;
  for (let i = 1; i <= UYE; i++) { const a = 1 / Math.pow(i, 0.8); agirlik.push(a); toplam += a; }
  return agirlik.map((a, i) => {
    let esik = ((i * 7919) % 100) / 100, k = 0, birikim = 0;
    while (k < sehirler.length - 1 && (birikim += sehirler[k][1]) <= esik) k++;
    return { uye_id: i + 1, odunc: Math.max(1, Math.round(a / toplam * KAYIT)), sehir: sehirler[k][0] };
  });
}
const M = 2654435761n;
const karmaDusuk  = (x) => Number((BigInt(x) * M) % 4294967296n);          // dusuk bitler
const karmaYuksek = (x) => Number(((BigInt(x) * M) % 4294967296n) >> 20n); // yuksek bitler

function rapor(ad, kova) {
  const enCok = Math.max(...kova), enAz = Math.min(...kova);
  const bos = kova.filter((s) => s === 0).length;
  const oran = enAz === 0 ? 'bölüm boş: ' + bos : (enCok / enAz).toFixed(2) + '×';
  console.log(ad.padEnd(24), 'bölüm', String(kova.length).padStart(2),
              '| en dolu', String(enCok).padStart(8), '| en boş', String(enAz).padStart(8),
              '| oran', oran);
}
function dagit(uye, P, anahtar) {
  const kova = new Array(P).fill(0);
  for (const u of uye) kova[anahtar(u.uye_id) % P] += u.odunc;
  return kova;
}
const uye = uyeler();
console.log('modeldeki toplam ödünç:', uye.reduce((t, u) => t + u.odunc, 0));
console.log('— karma bölümleme, güç yasasına yakın anahtar —');
for (const P of [8, 16, 64]) {
  rapor('uye_id % P', dagit(uye, P, (x) => x));
  rapor('karma_yuksek % P', dagit(uye, P, karmaYuksek));
}
console.log('— yapılı anahtar: üyelik numarası dörtün katı —');
const dortluk = uye.map((u) => ({ ...u, uye_id: u.uye_id * 4 }));
rapor('uye_id % 8',       dagit(dortluk, 8, (x) => x));
rapor('karma_dusuk % 8',  dagit(dortluk, 8, karmaDusuk));
rapor('karma_yuksek % 8', dagit(dortluk, 8, karmaYuksek));
console.log('— liste bölümleme: şehir —');
const listeKova = sehirler.map(([ad]) =>
  uye.filter((u) => u.sehir === ad).reduce((t, u) => t + u.odunc, 0));
rapor('şehir', listeKova);
console.log(sehirler.map(([ad], i) => `${ad}=${listeKova[i]}`).join('  '));
JS
node dagilim.mjs
```

```
modeldeki toplam ödünç: 2001046
— karma bölümleme, güç yasasına yakın anahtar —
uye_id % P               bölüm  8 | en dolu   278850 | en boş   238066 | oran 1.17×
karma_yuksek % P         bölüm  8 | en dolu   273855 | en boş   233420 | oran 1.17×
uye_id % P               bölüm 16 | en dolu   158156 | en boş   116396 | oran 1.36×
karma_yuksek % P         bölüm 16 | en dolu   154631 | en boş   114336 | oran 1.35×
uye_id % P               bölüm 64 | en dolu    69604 | en boş    27466 | oran 2.53×
karma_yuksek % P         bölüm 64 | en dolu    69318 | en boş    26434 | oran 2.62×
— yapılı anahtar: üyelik numarası dörtün katı —
uye_id % 8               bölüm  8 | en dolu  1014448 | en boş        0 | oran bölüm boş: 6
karma_dusuk % 8          bölüm  8 | en dolu  1014448 | en boş        0 | oran bölüm boş: 6
karma_yuksek % 8         bölüm  8 | en dolu   285482 | en boş   236957 | oran 1.20×
— liste bölümleme: şehir —
şehir                    bölüm  5 | en dolu   876922 | en boş   139966 | oran 6.27×
Istanbul=876922  Ankara=464332  Izmir=312596  Bursa=207230  Konya=139966
```

Üç bulgu çıkıyor.

Birincisi: karma bölümlemesi **anahtarları** dengeler, satırları değil. Sekiz bölümde
dengesizlik oranı 1,17; altmış dört bölümde 2,53. Bölüm küçüldükçe tek bir çok etkin
üyenin ağırlığı bölümün tamamına göre büyür ve karma fonksiyonunun kalitesi bunu
değiştirmez — üstteki iki satırın hemen hemen aynı çıkması bunu gösteriyor.

İkincisi: karma fonksiyonunun kalitesi **anahtarın yapılı olduğu** durumda belirleyicidir.
Üyelik numaraları dördün katı olduğunda doğrudan bölme sekiz bölümün altısını boş bıraktı.
Çarpımsal karmanın düşük bitleri de aynı sonucu verdi; nedeni aritmetiktir — tek bir
çarpanla çarpıp ikinin kuvvetine bölmek düşük bitleri korur. Yüksek bitler alındığında
dağılım 1,20 oranına düzeldi. Kural: karma değerinin **düşük bitleri bölüm seçmek için
kullanılmaz**.

Üçüncüsü: liste bölümlemesi dengeyi hiç hedeflemez. Şehre göre bölünen kayıtlarda oran
6,27 çıktı ve bu bir kusur değil, gerçekliğin kendisidir. Liste bölümlemesi bölümlerin
ayrı yönetilmesi gerektiğinde seçilir — bir şehrin verisinin ayrı saklanması, ayrı
yedeklenmesi ya da ayrı silinmesi gerekiyorsa dengesizlik kabul edilir.

## Özet

- Bölümleme, mantıksal olarak tek bir tabloyu bölüm anahtarına göre birden çok fiziksel
  parçada saklar; aralık, liste ve karma biçimleri anahtarın bölüme nasıl çevrildiğiyle
  ayrılır.
- Bölüm anahtarını süzen sorgu ilgisiz bölümlerin verisini okumaz: görünüm üzerinden 141,
  doğrudan tek bölümden 123 sayfa okundu; aradaki fark altı boş dizin yoklamasıdır.
- Dizin varken bölümleme okuma başarımı kazandırmadı; bölümsüz tablo aynı sorguyu aynı 123
  sayfayla yanıtladı.
- Asıl kazanç veriyi bütün olarak kaldırmaktır: bir yılın silinip boşluğun geri alınması
  1,13 saniye sürdü ve 1.398 sayfa serbest bıraktı; bölümün düşürülmesi 0,07 saniyede
  4.367 sayfa serbest bıraktı.
- Karma bölümlemesi anahtarları dengeler, satırları değil; bölüm sayısı arttıkça dengesizlik
  büyüdü (1,17 → 2,53). Yapılı anahtarlarda karma değerinin düşük bitleri bölüm seçmek için
  kullanılamaz.

## Sonraki Adım

Bu dersteki bütün bölümler aynı veritabanının içindeydi. Bölümleri ayrı makinelere
dağıtmak, aynı fikri bir adım ileri götürür ve nitelik değiştirir: bölümler arası bir
sorgu artık ağ üzerinden yürür, bir işlem birden çok makineyi kapsar ve bölüm sayısı
değiştiğinde veri taşınmak zorunda kalır. Sonraki ders bu kalıbı ele alır — parça
anahtarının seçiminin uygulamaya ne dayattığı, çapraz parça sorgusunun maliyeti ve
yeniden dengelemede taşınan veri miktarı sayılarak.
