MySQL MEDIUMBLOB ile İkili Veri Saklamak

Selamlar, bu yazıda MySQL'in MEDIUMBLOB tipine bakacağız. PDF'leri, küçük resimleri, imza dosyalarını veritabanında tutmak isteyen ekipler bir noktada bu tipe denk geliyor; ben de yıllar içinde 'şu dosyayı veritabanına atalım, kolay olsun' kararının bazen kurtarıcı, bazen ayak bağı olduğunu gördüm. Hadi tipin sınırlarına ve doğru kullanım senaryolarına dalalım.

MEDIUMBLOB nedir?

MEDIUMBLOB, en fazla 16.777.215 bayt yani yaklaşık 16 MB ikili veri saklayabilen bir tiptir. MySQL'in BLOB ailesinde BLOB (64 KB) ile LONGBLOB (4 GB) arasındaki boşluğu doldurur. Yani çok küçük olmayan ama devasa da sayılmayacak dosyalar için biçilmiş kaftan.

Burada gözden kaçmayan bir detay var: InnoDB'nin DYNAMIC satır formatında BLOB içeriği satırla aynı sayfada tutulmaz. Verinin kendisi off-page'e gider, satırda yalnızca 9 ile 12 bayt arası bir işaretçi kalır. Bu sayede 65.535 baytlık satır limitine BLOB sütunları neredeyse hiç yük bindirmez. Aslında MEDIUMBLOB'un cazibesinin yarısı bu mimari karardan geliyor.

Tablo tasarımı

Bir dosya yükleme tablosu kurarken ne istediğimizi en baştan netleştirelim. Aşağıdaki şema, hem dosyanın kendisini hem de işine yarayacak meta verileri tek satırda toplar:

CREATE TABLE uploaded_files (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    filename    VARCHAR(255) NOT NULL,
    mime_type   VARCHAR(100) NOT NULL,
    file_data   MEDIUMBLOB NOT NULL,
    file_size   INT UNSIGNED GENERATED ALWAYS AS (LENGTH(file_data)) STORED,
    uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

file_size sütununu generated column olarak tutmamın sebebi listeleme sorgularında LENGTH(file_data) çağırarak BLOB'u sayfaya taşımak zorunda kalmamak. Küçük bir numara ama büyük bir tasarruf.

Veri ekleme ve okuma

Python tarafında mysql-connector-python ile bir PDF eklemek oldukça düz:

import mysql.connector

conn = mysql.connector.connect(host='localhost', user='root', database='mydb')
cursor = conn.cursor()

with open('sunum.pdf', 'rb') as f:
    pdf_bytes = f.read()

cursor.execute(
    """INSERT INTO uploaded_files (filename, mime_type, file_data)
       VALUES (%s, %s, %s)""",
    ('sunum.pdf', 'application/pdf', pdf_bytes)
)
conn.commit()
print(f'{len(pdf_bytes)} bayt yazildi')

Geri okumak da aynı şekilde sade. Tek dikkat edilecek nokta, fetchone() BLOB'u baştan sona belleğe alır. Yani 15 MB'lık bir dosyayı okuyorsanız o satır anında 15 MB RAM tüketir:

cursor.execute(
    'SELECT filename, mime_type, file_data FROM uploaded_files WHERE id = %s',
    (1,)
)
filename, mime_type, file_data = cursor.fetchone()

with open(f'indirilen_{filename}', 'wb') as f:
    f.write(bytes(file_data))

max_allowed_packet ayarı

Büyük insert'lerde başınıza gelecek ilk hata genelde Packet too large olur. Çünkü MySQL istemci ile sunucu arasındaki tek bir paketin boyutuna sınır koyar. MEDIUMBLOB ile çalışıyorsanız bu sınırı yükseltmeniz gerek:

-- Mevcut degeri gor
SHOW VARIABLES LIKE 'max_allowed_packet';

Sunucu tarafında my.cnf içine max_allowed_packet = 20M yazmak çoğu durumda yeter. İstemci tarafında da bağlantı parametresiyle ayarlanır:

mysql -u root -p mydb --max_allowed_packet=20M

Bu ayarı yapmadan yapılan testler 'lokalde çalışıyordu' efsanesinin en büyük kaynaklarından biridir.

Sık karşılaşılan hatalar

  • SELECT * ile listelemek: Listeleme sorgularında BLOB sütununu çekmek bütün satırları sayfaya yüklemenize yol açar. Sadece ihtiyacınız olan sütunları seçin, dosyayı yalnızca indirme anında çekin.
  • MIME türü doğrulamamak: İstemci 'image/png' diyorsa diye doğrudan inanmayın. python-magic veya benzer bir kütüphane ile baytlardan tipini doğrulayın; aksi halde XSS veya yanlış servis edilen dosya sorunları kapıda.
  • Yedek boyutunu görmezden gelmek: BLOB'lar yedek dosyalarınızı şişirir. Bir noktada mysqldump çıktısı 50 GB'ı bulduğunda 'biz neyi nereye koyduk?' diye soracaksınız.
  • Versiyon yönetimi beklemek: MEDIUMBLOB tek bir mevcut hali tutar; CDN, varyant üretimi, geçmiş sürümler istiyorsanız S3 benzeri nesne depolama daha doğru adres.

Ne zaman MEDIUMBLOB, ne zaman dış depolama?

Bence kural basit: dosya 5 MB altındaysa, çok sık okunmuyorsa ve satırla transaksiyonel olarak tutarlı kalması önemliyse MEDIUMBLOB iyi seçimdir. Mesela kullanıcı imzası, PDF rapor eki, küçük thumbnail. Ama dosyalar büyük, sık okunan, CDN üzerinden dağıtılması gereken şeylerse S3 veya GCS'e gönderin, veritabanında sadece URL ile metadata tutun.

Kapanış

MEDIUMBLOB, doğru senaryoda hayatı çok kolaylaştıran bir tip; 'her dosyayı veritabanına atalım' refleksiyle kullanılırsa da en hızlı şekilde sırtınıza yük olan bir tip. Boyutu, erişim sıklığını ve yedek maliyetini birlikte değerlendirin; karar zaten kendiliğinden çıkar. Umarım faydalı olur, görüşmek üzere.