Veritabanı Yönetimi

    MySQL Partitioning ile Tablo Bölümleme

    RANGE bölümleme ile büyük tabloları yönetme, pruning ve eski veriyi hızlı temizleme rehberi.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Bir log ya da işlem tablosu yüz milyon satıra ulaştığında iki şey aynı anda bozulur: sorgular yavaşlar ve eski veriyi silmek imkânsız hale gelir. DELETE FROM log WHERE tarih < '2025-01-01' komutu saatlerce çalışır, ikili günlüğü şişirir, kilit tutar ve sonunda diskte hiç yer açmaz — çünkü InnoDB silinen yeri işletim sistemine geri vermez. MySQL partitioning tam olarak bu iki soruna karşı tasarlanmış bir araçtır.

    Bölümleme, tek bir mantıksal tabloyu fiziksel olarak birden çok parçaya ayırır. Sorgu, ilgilenmediği parçaları hiç açmaz (partition pruning) ve bir dönemin verisini silmek DROP PARTITION ile saniyeler sürer. Bu yazıda bölümleme türlerini, tarih bazlı bir kurulumu adım adım, bakım otomasyonunu, pruning'in gerçekten çalıştığını nasıl doğrulayacağını ve bölümlemenin göz ardı edilirse sistemi bozan kısıtlarını anlatacağım. Çünkü partitioning, yanlış kolona uygulandığında hiçbir şey kazandırmadan tüm sorguları yavaşlatabilen bir özelliktir.

    Bölümleme Neyi Çözer, Neyi Çözmez#

    Öncelikle yaygın bir yanlış anlamayı düzeltelim: partitioning genel amaçlı bir performans özelliği değildir. Tek satır getiren, birincil anahtara göre çalışan bir sorgu bölümlemeden hiçbir fayda görmez; hatta tüm bölümlere bakmak zorunda kalırsa yavaşlar bile. Kazanç yalnızca şu üç durumda gerçektir:

    1. Sorgular bölümleme anahtarına göre filtreliyorsa. Tarihe göre bölünmüş bir tabloda "son 7 gün" sorgusu yalnızca bir-iki bölüme dokunur.
    2. Dönemsel veri silme ihtiyacı varsa. DROP PARTITION anlıktır ve diskteki yeri gerçekten geri verir.
    3. Tablo tek bir dosyaya sığmayacak kadar büyükse. Her bölüm ayrı .ibd dosyası olur; bakım işlemleri parça parça yapılabilir.

    Bunların hiçbiri geçerli değilse bölümleme yapma. Doğru index tasarımı, çoğu durumda bölümlemeden daha fazla kazandırır ve hiçbir kısıt getirmez; konuyu SQL index tasarımı yazısında ele aldım.

    Bölümlemenin çözmediği şeyleri de netleştirelim: yazma darboğazını çözmez (tüm bölümler aynı sunucudadır), yatay ölçekleme sağlamaz (bu shard'lamadır, ayrı bir konudur) ve rastgele erişimli sorguları hızlandırmaz.

    Bölümleme Türleri ve Hangisini Seçmelisin#

    MySQL dört temel bölümleme türü sunar. Pratikte üretimde gördüğüm kurulumların büyük çoğunluğu birincisidir.

    TürNasıl bölerTipik kullanım
    RANGEDeğer aralıklarına göreTarih bazlı log, işlem, olay tabloları
    LISTBelirli değer listelerine göreBölge kodu, kiracı (tenant) kimliği
    HASHAnahtarın hash'ine göre eşit dağıtımDengeli dağıtım, dönemsel silme yok
    KEYMySQL'in kendi hash fonksiyonuHASH'e benzer, tamsayı olmayan anahtarlar için

    RANGE ve LIST için ayrıca COLUMNS varyantı vardır: RANGE COLUMNS doğrudan DATE ya da VARCHAR kolonlarıyla çalışır ve tamsayıya çevirmeni gerektirmez. Bu, tarih bazlı bölümlemede en temiz yöntemdir.

    -- Tarih bazlı bölümleme: RANGE COLUMNS ile doğrudan DATETIME üzerinde
    CREATE TABLE olay (
      id      BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      tur     VARCHAR(50)     NOT NULL,
      detay   TEXT            NULL,
      tarih   DATETIME        NOT NULL,
      PRIMARY KEY (id, tarih),            -- bölümleme anahtarı PK'ya dahil OLMALI
      KEY ix_tur_tarih (tur, tarih)
    ) ENGINE=InnoDB
    PARTITION BY RANGE COLUMNS (tarih) (
      PARTITION p2026_06 VALUES LESS THAN ('2026-07-01'),
      PARTITION p2026_07 VALUES LESS THAN ('2026-08-01'),
      PARTITION p2026_08 VALUES LESS THAN ('2026-09-01'),
      PARTITION p_max    VALUES LESS THAN (MAXVALUE)
    );
    

    Buradaki PRIMARY KEY (id, tarih) satırı tesadüf değil, zorunluluktur. MySQL, bölümleme anahtarının tablodaki her benzersiz index'in (birincil anahtar dahil) parçası olmasını şart koşar. Bu kısıt, bölümlemenin en çok engel çıkardığı noktadır ve tasarımı doğrudan etkiler; ileride ayrı bir bölümde ele alacağım.

    p_max bölümü de kritiktir: aralık dışında kalan bir satır eklemeye çalışırsan MySQL hata verir. MAXVALUE bölümü bu hatayı önleyen emniyet supabıdır, ama içi dolarsa bakım zorlaşır; bu yüzden aylık bölümleri zamanında eklemelisin.

    Partition Pruning'i Doğrulamak#

    Bölümlemenin bütün kazancı pruning'den gelir: optimizer, sorgunun hangi bölümlerle ilgili olduğunu anlayıp diğerlerini hiç açmaz. Bunu varsayma, EXPLAIN ile kanıtla — partitions sütunu tam olarak bu bilgiyi verir.

    -- İyi: yalnızca bir bölüme dokunur
    EXPLAIN SELECT COUNT(*) FROM olay
    WHERE tarih >= '2026-08-01' AND tarih < '2026-09-01';
    -- partitions: p2026_08
    
    -- Kötü: bölümleme anahtarı sorguda yok, TÜM bölümler taranır
    EXPLAIN SELECT COUNT(*) FROM olay WHERE tur = 'login';
    -- partitions: p2026_06,p2026_07,p2026_08,p_max
    

    İkinci sorgu bölümlemenin faydasını değil zararını gösterir: her bölüm için ayrı bir tarama açılır ve toplam maliyet bölümsüz tablodan yüksek olabilir. Bu yüzden uygulamadaki sorguların ezici çoğunluğunun bölümleme anahtarını içerdiğinden emin olmalısın.

    Pruning'i bozan klasik hata, kolona fonksiyon uygulamaktır:

    -- Pruning ÇALIŞMAZ: kolon fonksiyon içinde
    WHERE YEAR(tarih) = 2026 AND MONTH(tarih) = 8
    
    -- Pruning ÇALIŞIR: düz aralık
    WHERE tarih >= '2026-08-01' AND tarih < '2026-09-01'
    

    Aynı kural index'ler için de geçerlidir; plan okumanın tamamını MySQL EXPLAIN ile sorgu analizi yazısında anlattım. EXPLAIN çıktısındaki partitions sütununda tek bir isim görmek, bölümlemenin gerçekten işe yaradığının tek kanıtıdır.

    Bakım: Yeni Bölüm Ekleme ve Eski Veriyi Silme#

    Bölümlemenin en somut kazancı burada ortaya çıkar. Eski bir dönemi silmek, milyonlarca satırlık bir DELETE yerine tek bir DDL komutudur:

    -- Haziran verisini sil: anlık, ikili günlüğe milyonlarca satır yazılmaz,
    -- diskteki .ibd dosyası gerçekten silinir
    ALTER TABLE olay DROP PARTITION p2026_06;
    
    -- Yeni ay ekle: MAXVALUE bölümü varsa önce onu bölmen gerekir
    ALTER TABLE olay REORGANIZE PARTITION p_max INTO (
      PARTITION p2026_09 VALUES LESS THAN ('2026-10-01'),
      PARTITION p_max    VALUES LESS THAN (MAXVALUE)
    );
    

    DROP PARTITION ile DELETE arasındaki fark yalnızca hız değildir. DELETE işlemi undo log üretir, ikili günlüğe her satırı yazar (replikasyonu tıkar) ve InnoDB'de boşalan yeri işletim sistemine iade etmez — yani disk dolmaya devam eder. DROP PARTITION ise ilgili dosyayı doğrudan siler. Binlog'un neden bu kadar hızlı şiştiğini ve doldurduğunda ne yapılacağını MySQL binlog diski doldurdu yazısında anlattım.

    Bölüm eklemeyi elle yapmayı unutursan tüm yeni satırlar p_max içinde birikir ve pruning faydası kaybolur. Bu yüzden işi otomatikleştirmelisin. Basit bir yöntem, ayda bir çalışan bir betiktir:

    #!/bin/bash
    # Gelecek ay için bölüm ekle (ayın 1'inde cron ile çalıştır)
    # 0 3 1 * * /usr/local/bin/olay-partition-ekle.sh
    SONRAKI=$(date -d "+2 month" +%Y-%m-01)
    AD=p$(date -d "+1 month" +%Y_%m)
    
    mysql -u bakim -p"$DB_SIFRE" sirket <<SQL
    ALTER TABLE olay REORGANIZE PARTITION p_max INTO (
      PARTITION ${AD} VALUES LESS THAN ('${SONRAKI}'),
      PARTITION p_max VALUES LESS THAN (MAXVALUE)
    );
    SQL
    

    Mevcut bölümlerin durumunu ve satır dağılımını düzenli kontrol et:

    SELECT partition_name, table_rows,
           ROUND(data_length/1024/1024) AS veri_mb,
           ROUND(index_length/1024/1024) AS index_mb
    FROM information_schema.partitions
    WHERE table_schema = DATABASE() AND table_name = 'olay'
    ORDER BY partition_ordinal_position;
    

    p_max içindeki satır sayısı artmaya başladıysa bölüm eklemeyi kaçırmışsındır.

    Var Olan Bir Tabloyu Bölümlemek#

    Zaten büyümüş bir tabloyu bölümlemek, boş tabloda bölümleme kurmaktan çok daha zahmetlidir çünkü tablo baştan sona yeniden yazılır. Adımlar şöyle olmalı:

    1. Birincil anahtarı düzelt. Bölümleme anahtarı benzersiz index'lerin parçası değilse önce onu çözmelisin: PRIMARY KEY (id) yerine PRIMARY KEY (id, tarih).
    2. Yedek al. Bu bir yeniden yazma işlemidir; geri dönüş planın olmalı. Yöntemi mysqldump ile veritabanı yedekleme yazısında bulabilirsin.
    3. Boş alanı doğrula. İşlem sırasında tablonun ikinci bir kopyası oluşur; diskte en az tablo boyutu kadar boş yer gerekir.
    4. Düşük trafikte uygula. Bakım penceresinde çalıştır, replikasyon gecikmesini izle.
    5. Pruning'i doğrula. Uygulamanın en sık çalışan sorgularını EXPLAIN ile kontrol et.
    -- 1) Birincil anahtarı bölümleme anahtarını içerecek şekilde değiştir
    ALTER TABLE olay DROP PRIMARY KEY, ADD PRIMARY KEY (id, tarih);
    
    -- 2) Bölümlemeyi uygula (tablo yeniden yazılır, uzun sürer)
    ALTER TABLE olay
    PARTITION BY RANGE COLUMNS (tarih) (
      PARTITION p2026_06 VALUES LESS THAN ('2026-07-01'),
      PARTITION p2026_07 VALUES LESS THAN ('2026-08-01'),
      PARTITION p2026_08 VALUES LESS THAN ('2026-09-01'),
      PARTITION p_max    VALUES LESS THAN (MAXVALUE)
    );
    

    Çok büyük tablolarda bu tek komutu çalıştırmak saatler sürebilir ve tabloyu kilitleyebilir. Alternatif yaklaşım, bölümlenmiş yeni bir tablo oluşturup veriyi parça parça kopyalamak, sonra RENAME TABLE ile takas etmektir; bu, kesinti süresini saniyelere indirir.

    Bölümlemeyi geri almak istersen tek komut yeterlidir ve veri kaybolmaz:

    ALTER TABLE olay REMOVE PARTITIONING;
    

    Kısıtlar ve Sık Yapılan Hatalar#

    Bölümlemenin kısıtları, özelliğin kendisi kadar önemlidir ve önceden bilinmezse projeyi çıkmaza sokar.

    Benzersiz index kısıtı. Bölümleme anahtarı, tablodaki her UNIQUE ve PRIMARY KEY index'in içinde bulunmalıdır. Yani email üzerinde tekillik isteyip tarihe göre bölümlemek istiyorsan, tekilliği veritabanı düzeyinde koruyamazsın. Bu, çoğu projede bölümlemeyi eleyen ilk kısıttır.

    Yabancı anahtar desteklenmez. Bölümlenmiş bir InnoDB tablo ne yabancı anahtar içerebilir ne de başka bir tablonun yabancı anahtarı tarafından hedeflenebilir. İlişkisel bütünlük tamamen uygulamanın sorumluluğuna geçer.

    FULLTEXT index kullanılamaz. Bölümlenmiş tabloya tam metin index'i ekleyemezsin; arama gereksinimi olan tablolarda bu ikisini birlikte kullanamazsın. Alternatifleri MySQL full-text arama yazısında ele aldım.

    Bölüm sayısını abartmak. Her bölüm ayrı bir dosya ve ayrı bir açık tablo tanıtıcısıdır. Günlük bölümlerle üç yıllık veri tutmak bin bölüm demektir; bu, açık dosya sayısını ve bakım maliyetini gereksiz yere şişirir. Çoğu sistem için aylık bölüm doğru granülariteyi verir.

    Yanlış kolona bölümleme. En pahalı hata budur. Sorgular musteri_id ile filtreliyorken tabloyu tarihe göre bölersen her sorgu tüm bölümlere dokunur ve sistem yavaşlar. Bölümleme anahtarı, sorguların ezici çoğunluğunda WHERE içinde bulunan kolon olmalıdır.

    NULL değerler. RANGE bölümlemede NULL, en küçük değer kabul edilir ve ilk bölüme düşer. Bölümleme anahtarı olacak kolonu NOT NULL tanımlamak bu sürprizi tamamen ortadan kaldırır.

    Son olarak depolama motorunu da unutma: bölümleme InnoDB ile çalışır ve MyISAM'da davranışı farklıdır; motor seçimi konusunu InnoDB ve MyISAM karşılaştırması yazısında ayrıntılı ele aldım.

    Sıkça Sorulan Sorular#

    Partitioning sorguları her zaman hızlandırır mı#

    Hayır. Hızlanma yalnızca sorgu bölümleme anahtarına göre filtreliyorsa gerçekleşir; o zaman optimizer ilgisiz bölümleri hiç açmaz. Anahtarı içermeyen bir sorgu ise tüm bölümlere ayrı ayrı bakmak zorunda kalır ve bölümsüz tablodan daha yavaş çalışabilir. Bu yüzden bölümlemeden önce uygulamanın en sık çalışan sorgularını incelemeli ve ortak filtre kolonunu belirlemelisin.

    Bölümlenmiş tabloya yabancı anahtar ekleyebilir miyim#

    Hayır. InnoDB, bölümlenmiş tablolarda yabancı anahtar kısıtlarını desteklemez; ne tablonun kendisi yabancı anahtar içerebilir ne de başka bir tablo ona referans verebilir. İlişkisel bütünlüğü uygulama katmanında sağlaman gerekir. Bu kısıt, sıkı bütünlük gerektiren merkezi tablolarda bölümlemeyi genellikle eleyen sebeplerden biridir.

    Kaç bölüm oluşturmalıyım#

    Granülariteyi silme dönemine göre seç. Verinin bir yıl saklandığı bir sistemde aylık bölümler on iki-on üç bölüm demektir ve bu gayet yönetilebilirdir. Günlük bölümler yalnızca çok yüksek hacimli ve kısa saklama süreli sistemlerde anlamlıdır. Bölüm sayısı yüzleri geçtiğinde açık dosya sayısı, meta veri yükü ve bakım süresi hissedilir biçimde artar.

    DROP PARTITION verimi gerçekten diskten siler mi#

    Evet, ve bu bölümlemenin en somut kazancıdır. innodb_file_per_table açıkken her bölüm ayrı bir .ibd dosyasıdır ve bölüm düşürüldüğünde dosya işletim sistemine iade edilir. Buna karşılık aynı satırları DELETE ile silmek diskte yer açmaz; boşluk tablonun içinde kalır ve ancak OPTIMIZE TABLE ile geri kazanılır, o da uzun süren ve pahalı bir işlemdir.

    Var olan büyük tabloyu bölümlemek ne kadar sürer#

    Tablo tamamen yeniden yazıldığı için süre veri boyutuna ve disk hızına bağlıdır; onlarca gigabaytlık tablolarda saatleri bulabilir. İşlem sırasında diskte tablo boyutu kadar ek yer gerekir ve tablo büyük ölçüde kilitlenir. Kesintiyi en aza indirmek için bölümlenmiş yeni bir tablo oluşturup veriyi parça parça kopyalamak ve sonunda RENAME TABLE ile takas etmek daha güvenli bir yöntemdir.

    Bölümleme yerine ne kullanmalıyım#

    Çoğu durumda doğru index tasarımı yeterlidir ve hiçbir kısıt getirmez; önce onu tüket. Amacın yalnızca eski veriyi temizlemekse ve bölümleme kısıtları sana uymuyorsa, veriyi küçük partiler hâlinde silen bir arşivleme işi (DELETE ... LIMIT 5000 döngüsü) çalışılabilir bir alternatiftir. Yazma darboğazı yaşıyorsan bölümleme bunu çözmez; o zaman okuma kopyaları veya uygulama düzeyinde parçalama gerekir.

    Bölümlemeyi geri alabilir miyim#

    Evet, ALTER TABLE tablo_adi REMOVE PARTITIONING komutu bölümlemeyi kaldırır ve tablo normal bir tabloya döner; veri kaybolmaz. Ancak bu da tam bir yeniden yazma işlemidir, yani büyük tablolarda uzun sürer ve diskte ek yer ister. Ayrıca bölümleme için birincil anahtara eklediğin kolonu geri çıkarmak istiyorsan bunu ayrı bir işlem olarak planlamalısın.

    Kapanış#

    Partitioning'i doğru kullanmanın özü şu birkaç kararda yatıyor: bölümleme anahtarını sorguların gerçekten filtrelediği kolondan seç, EXPLAIN çıktısındaki partitions sütunuyla pruning'i her zaman doğrula, bölüm ekleme işini cron ile otomatikleştir ki p_max şişmesin ve eski veriyi DELETE yerine DROP PARTITION ile temizle. Bölümlemeye başlamadan önce benzersiz index, yabancı anahtar ve FULLTEXT kısıtlarının projene uyduğunu mutlaka doğrula — bu üçü, sonradan fark edildiğinde geri dönüşü pahalı olan kısıtlardır.

    Büyük tablolarla çalışmak disk kapasitesi ve G/Ç hızı kadar düzenli yedekleme de gerektirir; bir bölümü yanlışlıkla düşürmek anlık ve geri alınamaz bir işlemdir. Veritabanını geniş NVMe alanı olan bir makinede çalıştırmak istersen VDS ve dedicated sunucu paketlerimiz uygun bir zemin sunar, esneklik gerekiyorsa bulut sunucu ile büyüyebilirsin. Düzenli yedek ve bakım işini bize bırakmak isterseniz yedekleme ve sunucu yönetimi hizmetlerimiz bu tarafı üstlenir.

    MySQLPartitioningŞema Tasarımı

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.