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.csvkoyupmusterilertablosuna gitmesini bekleyemezsiniz. Yüklemeden önce dosyayı yeniden adlandırın ya da sembolik bağ kurun. - Sunucuda dosya yok:
mysqlimportvarsayılanda dosyayı sunucuda arar. Dosya sizdeyse--localparametresini ekleyin; ayrıcamy.cnfiçindelocal_infile=1açık olmalı. - Karakter kodlama uyumsuzluğu: Türkçe karakterler bozuk çıkıyorsa
--default-character-set=utf8mb4ekleyin. Çoğu hata buradan çıkıyor. - Index'ler darboğaz oluyor: Çok büyük yüklemelerde önce
ALTER TABLE ... DISABLE KEYSile indexleri devre dışı bırakın, yükleme bitinceENABLE KEYSile 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.
