CSV
LOAD
MySQL · Import · Performance · InnoDB · Magento
Import großer Datenmengen mit LOAD DATA und Bulk Inserts

Eine Million Zeilen einzeln einzufügen dauert Stunden. Mit LOAD DATA INFILE, Bulk Inserts und Transaction Batching dauert es Minuten. Dieser Artikel erklärt alle relevanten Techniken — inklusive Magento-Kontext.

LOAD DATA INFILE Multi-Row INSERT Transaction Batching unique_checks=0

1. Multi-Row INSERT: Optimale Batch-Größen

Der einfachste Weg um Inserts zu beschleunigen ist das Zusammenfassen vieler Zeilen in einem einzigen INSERT-Statement. Statt für jede Zeile eine eigene Query zu schicken, enthält eine Query tausende Zeilen. Der Overhead für Netzwerkkommunikation, Parser und Commitment wird auf viele Zeilen verteilt.

Die optimale Batch-Größe liegt in der Praxis zwischen 1.000 und 5.000 Zeilen pro INSERT. Zu kleine Batches verschwenden den Optimization-Overhead. Zu große Batches führen zu langen Transaktionen, großen Redo-Log-Einträgen und erhöhtem Speicherverbrauch. Für 10 MB Zeilen pro Batch ist das ein guter Ausgangspunkt.


-- Single-row INSERT: slow for bulk operations
INSERT INTO product_staging (sku, name, price, qty) VALUES ('SKU-001', 'Product 1', 29.99, 100);
INSERT INTO product_staging (sku, name, price, qty) VALUES ('SKU-002', 'Product 2', 49.99, 50);
-- ...repeated 100,000 times = 100,000 round trips

-- Multi-row INSERT: much faster — 1 round trip for 1000 rows
INSERT INTO product_staging (sku, name, price, qty) VALUES
    ('SKU-001', 'Product 1',  29.99, 100),
    ('SKU-002', 'Product 2',  49.99,  50),
    ('SKU-003', 'Product 3',  19.99, 200),
    ('SKU-004', 'Product 4',  99.99,  25),
    -- ... up to ~1000-5000 rows per statement
    ('SKU-999', 'Product 999', 15.99, 75);

-- INSERT ... SELECT for server-side bulk inserts (no network round trip)
-- Transforms data from staging into final table in one operation
INSERT INTO catalog_product_entity (sku, type_id, attribute_set_id, created_at)
SELECT
    ps.sku,
    'simple' AS type_id,
    4        AS attribute_set_id,  -- Default attribute set
    NOW()
FROM product_staging ps
WHERE ps.sku NOT IN (SELECT sku FROM catalog_product_entity)
ORDER BY ps.sku;

2. LOAD DATA INFILE: Der schnellste CSV-Import

LOAD DATA INFILE ist die schnellste Methode um CSV-Daten in MySQL zu importieren — typischerweise 5- bis 20-mal schneller als Multi-Row INSERTs, weil MySQL die Datei direkt liest und den Parser-Overhead minimiert. Die Datei muss auf dem Server-Filesystem liegen (oder bei LOAD DATA LOCAL INFILE auf dem Client).


-- Basic LOAD DATA INFILE for product import
LOAD DATA INFILE '/var/lib/mysql-files/products.csv'
INTO TABLE product_staging
FIELDS TERMINATED BY ','
       ENCLOSED BY '"'
       ESCAPED BY '\\'
LINES TERMINATED BY '\n'
IGNORE 1 LINES  -- skip header row
(sku, name, description, price, special_price, qty, weight, status);

-- With column transformations and default values
LOAD DATA INFILE '/var/lib/mysql-files/customers.csv'
INTO TABLE customer_import_staging
CHARACTER SET utf8mb4
FIELDS TERMINATED BY ';'       -- semicolon-separated
       OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'     -- Windows line endings
IGNORE 1 LINES
(
    @email,
    @firstname,
    @lastname,
    @dob,
    @group_id
)
SET
    email      = LOWER(TRIM(@email)),         -- normalize email
    firstname  = TRIM(@firstname),
    lastname   = TRIM(@lastname),
    dob        = IF(@dob = '', NULL, STR_TO_DATE(@dob, '%d.%m.%Y')),
    group_id   = IF(@group_id = '', 1, @group_id),
    created_at = NOW(),
    import_batch = 'batch_2025_01_17';

-- LOAD DATA LOCAL INFILE: file is on the client machine (not server)
-- Requires --local-infile=1 in MySQL client config
LOAD DATA LOCAL INFILE '/home/user/export/orders.csv'
INTO TABLE order_import_staging
FIELDS TERMINATED BY ','
       ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;

Wichtig: Der Pfad in LOAD DATA INFILE muss im secure_file_priv-Verzeichnis liegen (standardmäßig /var/lib/mysql-files/). Das kann mit SHOW VARIABLES LIKE 'secure_file_priv' geprüft werden.

3. Transaction Batching: Redo-Log unter Kontrolle

Eine einzige riesige Transaktion für einen Import mit Millionen von Zeilen ist keine gute Idee. Das Redo-Log wächst ins Unermessliche, ein Fehler in Zeile 999.999 rollt alles zurück, und der Speicherverbrauch kann die Datenbank zum Absturz bringen. Transaction Batching teilt den Import in handhabbare Einheiten auf.


-- Transaction batching pattern: commit every N rows
-- This example processes rows from a staging table in batches

SET @batch_size = 2000;
SET @offset = 0;
SET @total = (SELECT COUNT(*) FROM product_staging WHERE processed = 0);

-- Batch loop (pseudocode — implement in application or stored procedure)
WHILE @offset < @total DO
    START TRANSACTION;

    INSERT INTO catalog_product_entity (sku, type_id, attribute_set_id, created_at)
    SELECT sku, type_id, attribute_set_id, NOW()
    FROM product_staging
    WHERE processed = 0
    ORDER BY id
    LIMIT @batch_size OFFSET @offset;

    -- Mark batch as processed
    UPDATE product_staging
    SET processed = 1, processed_at = NOW()
    WHERE processed = 0
    ORDER BY id
    LIMIT @batch_size;

    COMMIT;

    SET @offset = @offset + @batch_size;
END WHILE;

-- Alternative: Use the ID range approach for large tables
-- Process IDs 1-2000, then 2001-4000, etc.
START TRANSACTION;
INSERT INTO target_table SELECT * FROM staging WHERE id BETWEEN 1 AND 2000;
COMMIT;

START TRANSACTION;
INSERT INTO target_table SELECT * FROM staging WHERE id BETWEEN 2001 AND 4000;
COMMIT;

4. Keys deaktivieren während des Imports

Für MyISAM-Tabellen kann mit ALTER TABLE t DISABLE KEYS das Schreiben in nicht-eindeutige Sekundärindizes während des Imports ausgesetzt werden. Das ist bei großen Bulk-Loads erheblich schneller, weil die Indexes nach dem Import in einem Durchgang gebaut werden statt bei jedem INSERT aktualisiert zu werden.

Für InnoDB gibt es diesen Mechanismus nicht direkt, aber äquivalente Optionen:


-- MyISAM: disable/enable non-unique indexes
ALTER TABLE myisam_table DISABLE KEYS;
LOAD DATA INFILE '/path/to/data.csv' INTO TABLE myisam_table ...;
ALTER TABLE myisam_table ENABLE KEYS;

-- InnoDB equivalent: disable unique checks (dangerous if data has duplicates!)
SET unique_checks = 0;
SET foreign_key_checks = 0;  -- see next section

LOAD DATA INFILE '/var/lib/mysql-files/products.csv'
INTO TABLE product_staging
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;

SET unique_checks = 1;
SET foreign_key_checks = 1;

-- WARNING: unique_checks=0 does NOT check for duplicate key violations
-- MySQL will still detect duplicates on the PRIMARY KEY (clustered index)
-- but UNIQUE secondary indexes are not enforced during the insert
-- Only use this if you are 100% certain the data has no duplicates!

-- Safer alternative: sort the import data by primary key before loading
-- InnoDB inserts are faster when data is in primary key order
-- This allows sequential page writes instead of random I/O
LOAD DATA INFILE '/var/lib/mysql-files/sorted_products.csv'
INTO TABLE product_staging
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;

5. foreign_key_checks=0: Chancen und Risiken

Das Deaktivieren von Foreign Key Checks (SET foreign_key_checks = 0) kann Imports signifikant beschleunigen, da MySQL nicht für jede eingefügte Zeile in der referenzierten Tabelle nachschlagen muss. Es ist aber ein zweischneidiges Schwert: Wenn die Daten ungültige Referenzen enthalten, werden diese still importiert — ohne Fehlermeldung.


-- The risk of foreign_key_checks=0
-- Without FK checks, this will succeed even if store_id 999 doesn't exist:
SET foreign_key_checks = 0;
INSERT INTO sales_order (store_id, customer_id, grand_total, created_at)
VALUES (999, 12345, 150.00, NOW());  -- store_id 999 may not exist!
SET foreign_key_checks = 1;

-- After re-enabling FK checks, the orphaned row remains — inconsistent DB!

-- Safe pattern: validate referential integrity BEFORE disabling FK checks
-- Check that all store_ids in import exist in store table
SELECT DISTINCT si.store_id
FROM staging_import si
LEFT JOIN store s ON s.store_id = si.store_id
WHERE s.store_id IS NULL;
-- If this returns rows, there are invalid store_ids in the import!

-- Only proceed if the above check returns zero rows
SET foreign_key_checks = 0;
INSERT INTO target_table SELECT * FROM staging_import;
SET foreign_key_checks = 1;

-- Verify no orphans were created after import
SELECT COUNT(*) AS orphaned_orders
FROM sales_order so
LEFT JOIN store s ON s.store_id = so.store_id
WHERE s.store_id IS NULL;

6. innodb_autoinc_lock_mode für parallele Inserts

Wenn mehrere parallele Prozesse gleichzeitig in eine AUTO_INCREMENT-Tabelle schreiben, kann der Standard-Auto-Increment-Locking-Mode zum Bottleneck werden. innodb_autoinc_lock_mode=2 (interleaved mode) erlaubt parallelen Inserts, ohne dass sie aufeinander warten müssen — was die Gesamtperformance bei Bulk-Imports erheblich verbessert.


-- Check current auto_increment lock mode
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
/*
0 = traditional: full table lock for all INSERT statements
1 = consecutive (default before MySQL 8): lock for bulk inserts, interlocked for simple
2 = interleaved: no lock, fastest for concurrent inserts
*/

-- Set in my.cnf for persistent configuration:
-- [mysqld]
-- innodb_autoinc_lock_mode = 2

-- innodb_autoinc_lock_mode=2 is safe when:
-- - INSERT ... SELECT or LOAD DATA with auto-increment is used
-- - Multiple threads insert concurrently
-- Note: with mode=2, IDs may not be consecutive — gaps are possible
-- This is fine for most applications but breaks assumptions about sequential IDs

-- Monitor auto-increment usage during bulk import
SHOW TABLE STATUS WHERE Name = 'product_staging'\G
-- Look at Auto_increment value to see current counter

7. Import-Fortschritt monitoren

Bei langen Imports ist es wichtig zu wissen, wie weit man ist — und ob der Import noch läuft oder hängt. MySQL bietet mehrere Wege zur Beobachtung laufender Operationen:


-- Monitor running imports via information_schema.PROCESSLIST
SELECT
    ID,
    USER,
    DB,
    COMMAND,
    TIME,
    STATE,
    LEFT(INFO, 100) AS query_preview
FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep'
  AND TIME > 5
ORDER BY TIME DESC;

-- For LOAD DATA INFILE: check progress via performance_schema
SELECT
    st.PROCESSLIST_ID,
    st.PROCESSLIST_INFO,
    st.PROCESSLIST_STATE,
    st.PROCESSLIST_TIME
FROM performance_schema.threads st
WHERE st.PROCESSLIST_COMMAND != 'Sleep'
  AND st.PROCESSLIST_TIME > 5;

-- Monitor InnoDB row insert rate
SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted';
-- Run twice with a 10-second pause to calculate rows/second

-- Check how many rows are in the target table so far
SELECT COUNT(*) AS imported_rows FROM product_staging;
-- Compare to total expected rows in the CSV file

8. Magento ImportExport vs direktes SQL

Magento bietet ein eigenes ImportExport-Framework (app/code/Magento/ImportExport), das Produkt- und Kundendaten validiert, transformiert und über definierte Prozesse importiert. Es ist der sichere Weg für Produktions-Imports, weil es Indexer, Caches und Event-System korrekt behandelt.

Direktes SQL ist schneller, aber umgeht alle Magento-seitigen Checks: Indexer werden nicht automatisch aktualisiert, URL-Rewrites werden nicht generiert, Produktbilder werden nicht verarbeitet. Direktes SQL eignet sich für Daten-Migrationen und Staging-Setups, nicht für Produktions-Katalog-Updates.


-- Direct SQL bulk insert into Magento catalog (only for migrations/staging)
-- After this, full reindex is required!

-- Step 1: Disable indexes to speed up EAV inserts
SET foreign_key_checks = 0;
SET unique_checks = 0;

-- Step 2: Insert base product records
INSERT INTO catalog_product_entity
    (entity_id, attribute_set_id, type_id, sku, created_at, updated_at)
SELECT
    NULL,  -- auto-increment
    4,     -- default attribute set
    'simple',
    ps.sku,
    NOW(),
    NOW()
FROM product_staging ps
WHERE ps.sku NOT IN (SELECT sku FROM catalog_product_entity)
LIMIT 5000;  -- batch of 5000

-- Step 3: Re-enable checks
SET foreign_key_checks = 1;
SET unique_checks = 1;

-- Step 4: After ALL batches completed, run full reindex
-- (via CLI, not SQL)
-- bin/magento indexer:reindex catalog_product_flat
-- bin/magento indexer:reindex catalog_product_price
-- bin/magento indexer:reindex catalogsearch_fulltext
-- bin/magento cache:flush

-- Magento's built-in import (recommended for production):
-- bin/magento import:run --entity=catalog_product \
--   --behavior=append \
--   --validation-strategy=validation-stop-on-errors \
--   products_import.csv

Mironsoft

Bulk-Imports für Magento sicher und schnell durchführen

Wir helfen dabei, Produkt- und Kundendaten effizient in Magento zu importieren — mit der richtigen Strategie zwischen ImportExport-Framework und direktem SQL.

Import-Strategie

LOAD DATA vs Bulk INSERT vs Magento Framework für den konkreten Fall wählen

Performance-Tuning

Transaction Batching, Key-Deaktivierung und innodb_autoinc_lock_mode optimieren

Datenmigration

Große Datenmengen zwischen Systemen migrieren ohne Produktionsausfall

MySQL Bulk Import — Das Wichtigste auf einen Blick

Schnellste Methode

LOAD DATA INFILE ist 5–20x schneller als Multi-Row INSERT. Datei muss im secure_file_priv-Verzeichnis liegen.

Transaction Batching

Nie alles in einer Transaktion. 1.000–5.000 Zeilen pro COMMIT verhindert Redo-Log-Overflow und erlaubt Teilrückrollbacks.

Risiken

foreign_key_checks=0 und unique_checks=0 sind gefährlich ohne vorherige Datenvalidierung. Immer vorher prüfen.

Magento-Besonderheit

Direktes SQL umgeht Indexer und Events. Nach direktem Import immer vollständigen Reindex und Cache-Flush durchführen.

FAQ: MySQL Bulk Import mit LOAD DATA und Bulk Inserts

1Was ist der schnellste Weg um Daten in MySQL zu importieren?
LOAD DATA INFILE ist die schnellste Methode — typischerweise 5 bis 20 Mal schneller als Multi-Row INSERT. Es liest die Datei direkt und minimiert den Parser-Overhead.
2Was ist die optimale Batch-Größe für Multi-Row INSERT?
In der Praxis 1.000 bis 5.000 Zeilen pro INSERT Statement. Zu kleine Batches verschwenden Overhead, zu große führen zu langen Transaktionen und hohem Speicherverbrauch.
3Was bedeutet Transaction Batching beim Import?
Statt aller Zeilen in einer Transaktion werden Gruppen von z.B. 2.000 Zeilen in separate Transaktionen aufgeteilt. Das verhindert Redo-Log-Overflow und ermöglicht Teilrückrollbacks bei Fehlern.
4Ist es sicher foreign_key_checks=0 zu setzen?
Nur wenn vorher sichergestellt wurde, dass alle referenziellen Integritäten in den Importdaten korrekt sind. Ohne Überprüfung können Zeilen mit ungültigen Fremdschlüsselwerten importiert werden.
5Was ist LOAD DATA LOCAL INFILE?
LOAD DATA LOCAL INFILE liest die Datei vom Client-Rechner statt vom Server. Erfordert --local-infile=1 in der Client-Konfiguration. Nützlich wenn die Datei nicht auf dem Datenbankserver liegt.
6Was passiert wenn ich Daten direkt per SQL in Magento importiere?
Magento-Indexer, URL-Rewrite-Generierung und Cache werden nicht automatisch aktualisiert. Nach direktem SQL-Import ist ein vollständiger Reindex und Cache-Flush erforderlich.
7Wann sollte ich das Magento ImportExport-Framework verwenden?
Für Produktions-Produktimports immer das Magento ImportExport-Framework verwenden. Es behandelt Indexer, URLs und Bilder korrekt. Direktes SQL ist nur für Migrationen und Staging geeignet.
8Was ist der Unterschied zwischen unique_checks=0 und DISABLE KEYS?
DISABLE KEYS deaktiviert nicht-eindeutige Sekundärindizes in MyISAM. unique_checks=0 deaktiviert UNIQUE-Constraint-Checks in InnoDB — aber nicht den PRIMARY KEY. Beide sind riskant bei Duplikaten in den Daten.
9Was ist innodb_autoinc_lock_mode=2?
Im interleaved mode müssen parallele INSERTs nicht auf einen Auto-Increment Lock warten. Das verbessert die Performance bei gleichzeitigen Bulk-Inserts erheblich. IDs können dadurch Lücken haben.
10Wie überprüfe ich den Fortschritt eines laufenden LOAD DATA INFILE?
Über information_schema.PROCESSLIST oder performance_schema.threads kann der laufende Thread beobachtet werden. Zusätzlich kann man die Zeilenanzahl in der Zieltabelle regelmäßig abfragen.