MySQL LOAD DATA LOCAL INFILE ile Toplu Yükleme

Selamlar, bu yazımda MySQL'in en sevdiğim toplu veri ekleme yollarından birine, LOAD DATA LOCAL INFILE komutuna bakacağız. Elimizde 50 bin satırlık bir CSV varsa ve bunu INSERT döngüsüyle yüklemeye kalktıysanız muhtemelen siz de çay molasını uzatmışsınızdır. Bu komut tam olarak o çileyi bitirmek için var. Hadi başlayalım.

LOAD DATA LOCAL INFILE nedir?

LOAD DATA INFILE, dosyayı sunucu makineden okur. Yani CSV'nizi önce scp ile sunucuya atmanız, sonra da MySQL kullanıcısının FILE yetkisinin olması gerekir. LOCAL anahtar kelimesi devreye girince işler değişiyor: dosya istemcinin (yani sizin) makinesinden okunup bağlantı üzerinden sunucuya akıtılıyor. Sunucuya dosya kopyalamak yok, FILE yetkisi gerekmiyor. Kısacası developer dostu varyantı bu.

Sunucu ve istemci tarafında ayar

local_infile her iki tarafta da açık olmak zorunda. Sunucu tarafında my.cnf dosyasına şunu ekliyoruz:

[mysqld]
local_infile = 1

Çalışan bir MySQL'de geçici olarak açmak istiyorsanız:

SET GLOBAL local_infile = 1;

İstemci tarafı da unutulmamalı. mysql CLI'sı ile bağlanırken bayrağı geçiyoruz:

mysql --local-infile=1 -u app -p mydb

İlk kurulumda en sık takıldığım yer burası olur. Sunucuda açıyorum, sonra ERROR 3950 (42000): The used command is not allowed görüp bir saat boş yere debug ediyorum, halbuki istemcide bayrak yok. Bence ilk başta her iki tarafı birden kontrol etmek alışkanlık olmalı.

Basit bir CSV yüklemesi

Diyelim ki customers.csv elimizde, içinde başlık satırı, virgülle ayrılmış alanlar ve tırnaklı string'ler var:

id,name,email,city
1,Alice,alice@example.com,'New York'
2,Bob,bob@example.com,Chicago

Hedef tabloya yüklemek için tam komutu yazalım:

LOAD DATA LOCAL INFILE '/home/mert/customers.csv'
INTO TABLE customers
FIELDS TERMINATED BY ','
       OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(id, name, email, city);

Buradaki kritik parçalar şunlar: FIELDS TERMINATED BY ',' alanları virgüle göre ayırıyor; OPTIONALLY ENCLOSED BY '"' ise tırnak içine alınmış string'leri (özellikle içinde virgül geçenleri) doğru parse ediyor. IGNORE 1 ROWS ise başlık satırını atlıyor. CSV'lerde header tutmak yaygın olduğu için bu satırı neredeyse her zaman ekliyorum.

Veri donusturme: degisken kullanimi

Bazen tarihler 2026-05-29 gibi gelmiyor da 29/05/2026 formatında geliyor. Ya da hesaplanmış bir alan eklemek istiyoruz. Bu durumda @degisken formunu kullanıp SET ile dönüştürüyoruz:

LOAD DATA LOCAL INFILE '/tmp/orders.csv'
INTO TABLE orders
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(id, user_id, amount, @raw_date)
SET created_at = STR_TO_DATE(@raw_date, '%d/%m/%Y');

Performans: transaction ve indexler

Milyon satırlık dosyalarda iki şeyi öneririm. Birincisi yüklemeyi bir transaction'a sarın; her satırda fsync yemekten kurtulursunuz. İkincisi, hedef tablonun ikincil indexleri varsa yükleme öncesi ALTER TABLE customers DISABLE KEYS deyip yükleme bitince ENABLE KEYS ile geri açın. InnoDB'de bu daha sınırlı etki yapar ama MyISAM'da fark uçurum gibidir. Foreign key kontrolünü geçici kapatmak (SET FOREIGN_KEY_CHECKS = 0;) da batch işlerde mantıklı, fakat tutarlılığı sonradan kendiniz doğrulamak şartıyla.

Sık karşılaşılan hatalar

  • İstemcide bayrağı unutmak: Sunucuda local_infile=1 yetmiyor, istemci de izin vermek zorunda. Yukarıdaki --local-infile=1 veya driver tarafında allow_local_infile=True şart.
  • Guvenlik riskini hafife almak: LOCAL INFILE aktifken bağlandığınız sunucu sizden istediği herhangi bir dosyayı isteyebilir. Tanımadığınız sunuculara local_infile=1 ile bağlanmayın; production'da ihtiyacınız bittiğinde kapatın.
  • Satır sonu karakteri: Windows'tan gelen CSV'lerde \r\n olur. LINES TERMINATED BY '\r\n' yazmazsanız son alana \r yapışır ve sonradan WHERE city='Chicago' çalışmaz.

Kapanış

Bence orta-büyük dosyalarda LOAD DATA LOCAL INFILE hâlâ MySQL'in en hızlı toplu yükleme aracı. Doğru ayar bir kere yapıldıktan sonra geri dönmek istemiyorsunuz. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.