mysqlimport ile Toplu CSV Yüklemek

Selamlar, bu yazımda MySQL'in çoğu zaman gözden kaçan bir aletini, mysqlimport'u konuşacağız. Elinizde birkaç yüz binlik bir CSV varsa ve bir de tutup INSERT döngüsüyle yüklemeye çalıştıysanız, ne demek istediğimi anlarsınız. Saatlerce bekleyen bir script, bir de yanında soğumuş bir kahve. Halbuki MySQL kutudan çıkar çıkmaz size hızlı bir yol sunuyor, biz farkında değiliz. Hadi başlayalım.

mysqlimport nedir?

mysqlimport aslında LOAD DATA INFILE SQL ifadesinin komut satırı kılığına bürünmüş hali. Yani arka planda bambaşka bir motor yok, MySQL'in kendi toplu yükleme komutu çalışıyor; siz sadece terminalden çağırıyorsunuz. Ama bu basit görünen sarmalama, sıradan INSERT yığınına göre çoğu senaryoda 10 ila 50 kat hız farkı yaratıyor. Tek satırlık bir komutla milyonlarca satır akıtabiliyorsunuz.

İlginç bir kuralı var: hedef tabloyu dosya adından çıkarıyor. Yani siparisler.csv dosyası otomatik olarak siparisler tablosuna gidiyor. Tablo adını parametreden vermiyorsunuz. İlk başlarda bu bana garip gelmişti, ama bir-iki kullanımdan sonra mantığı oturuyor; toplu yükleme yapacaksınız zaten dosyalarınızı tablo adına göre düzenli tutmanız işinize geliyor.

Temel kullanım

En sade biçimi şöyle:

mysqlimport [secenekler] veritabani_adi dosya1 [dosya2 ...]

Bir örnek üzerinden gidelim. Diyelim magaza veritabanında musteriler adlı bir tablomuz var ve elimizde /tmp/musteriler.csv dosyası duruyor:

# Dosya adi tablo adiyla ayni olmali
mysqlimport -u root -p magaza /tmp/musteriler.csv

Bu kadar. Tek satır. CSV ne kadar büyükse, INSERT'e göre kazancınız da o kadar büyür.

Ayraç ve tırnak ayarları

Hayatta her CSV virgülle ayrılmış olmuyor maalesef. Bazıları tab, bazıları pipe (|), bazıları da kim bilir hangi karaktere bayılmış birinin tercihiyle geliyor. mysqlimport bunların hepsini idare ediyor:

# Tab ile ayrilmis dosya
mysqlimport --fields-terminated-by='\t' -u root -p magaza /tmp/siparisler.tsv

# Pipe ile ayrilmis dosya
mysqlimport --fields-terminated-by='|' -u root -p magaza /tmp/urunler.txt

# Tirnak icine alinmis alanlar varsa
mysqlimport \
  --fields-terminated-by=',' \
  --fields-enclosed-by='"' \
  -u root -p magaza /tmp/musteriler.csv

Bana sorarsanız --fields-enclosed-by neredeyse her zaman lazım oluyor; çünkü gerçek CSV'lerde bir yerlerde mutlaka virgül içeren bir ad ya da adres çıkıyor karşınıza.

Başlık satırı, sütun sırası ve çakışmalar

Çoğu CSV'nin ilk satırı sütun adlarıyla geliyor; bunu --ignore-lines ile atlıyorsunuz. Dosyadaki sütunların sırası tablodakinden farklıysa --columns ile mantıklı bir eşleme veriyorsunuz:

# Ilk satiri (basligi) atla
mysqlimport --ignore-lines=1 \
  --fields-terminated-by=',' \
  -u root -p magaza /tmp/musteriler.csv

# Sutun sirasini elle belirt
mysqlimport \
  --columns=id,ad,eposta,olusturulma \
  --ignore-lines=1 \
  --fields-terminated-by=',' \
  -u root -p magaza /tmp/musteriler.csv

Yükleme sırasında aynı birincil anahtara sahip kayıt zaten varsa ne olacak? İki seçeneğiniz var: ya üzerine yazarsınız ya da o satırı atlarsınız.

# Mevcut kayitlari yenisiyle degistir
mysqlimport --replace --fields-terminated-by=',' \
  -u root -p magaza /tmp/musteriler.csv

# Cakisan satirlari sessizce gec
mysqlimport --ignore --fields-terminated-by=',' \
  -u root -p magaza /tmp/musteriler.csv

Hangisini seçeceğiniz veriyi nereden aldığınıza bağlı; gece koşan bir senkronizasyondaysanız --replace, tek seferlik bir göç ise --ignore çoğu zaman daha güvenli.

Paralel yükleme ve LOAD DATA karşılığı

Aylık dosyaları ayrı ayrı tutuyorsanız hepsini birden, paralel olarak yükleyebilirsiniz:

mysqlimport --use-threads=4 \
  --fields-terminated-by=',' \
  -u root -p magaza \
  /tmp/siparisler_ocak.csv \
  /tmp/siparisler_subat.csv \
  /tmp/siparisler_mart.csv \
  /tmp/siparisler_nisan.csv

Perde arkasında üretilen SQL aslında bu:

LOAD DATA INFILE '/tmp/musteriler.csv'
INTO TABLE musteriler
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(id, ad, eposta, olusturulma);

Yani mysqlimport öğrenmek bir bakıma LOAD DATA INFILE öğrenmek demek; biri terminalden, diğeri SQL istemcisinden hayatınıza giriyor.

Sık karşılaşılan tuzaklar

  • Dosya adı tablo adıyla uyuşmuyor: musteri_listesi.csv koyup musteriler tablosuna gitmesini bekleyemezsiniz. Yüklemeden önce dosyayı yeniden adlandırın ya da sembolik bağ kurun.
  • Sunucuda dosya yok: mysqlimport varsayılanda dosyayı sunucuda arar. Dosya sizdeyse --local parametresini ekleyin; ayrıca my.cnf içinde local_infile=1 açık olmalı.
  • Karakter kodlama uyumsuzluğu: Türkçe karakterler bozuk çıkıyorsa --default-character-set=utf8mb4 ekleyin. Çoğu hata buradan çıkıyor.
  • Index'ler darboğaz oluyor: Çok büyük yüklemelerde önce ALTER TABLE ... DISABLE KEYS ile indexleri devre dışı bırakın, yükleme bitince ENABLE KEYS ile geri açın. Bence bir milyondan büyük her yüklemede bunu refleks haline getirmek lazım.

Kapanış

mysqlimport öğrenmesi bir öğleden sonra sürmeyen ama elinizde tuttukça size saatler kazandıran bir alet. Bence MySQL ile çalışan herkesin cebinde olması gereken o ufak ama keskin araçlardan biri. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.