PART
RANGE
SQL · MySQL · Magento · Performance · Datenbank-Architektur
Partitionierung in MySQL: Wann sie hilft und wann nicht

MySQL Partitionierung richtig einsetzen — mit RANGE, LIST, HASH, Partition Pruning und konkretem Magento-Beispiel für sales_order.

RANGE / LIST / HASH / KEY Partition Pruning Magento sales_order Wann nicht partitionieren

1. Was MySQL Partitionierung wirklich bedeutet

MySQL Partitionierung teilt eine logische Tabelle intern in mehrere physische Segmente auf. Für Abfragen bleibt die Tabelle eine einzige Einheit — der MySQL-Optimizer entscheidet aber selbst, welche Partitionen er überhaupt lesen muss. Das Ergebnis ist potenziell massiv reduzierte I/O-Last, weil irrelevante Datensegmente komplett übersprungen werden.

Das klingt nach einer universellen Lösung für große Tabellen. In der Praxis ist MySQL Partitionierung jedoch ein gezieltes Werkzeug mit klaren Voraussetzungen. Sie hilft zuverlässig, wenn Abfragen den Partitionsschlüssel im WHERE-Filter nutzen und wenn Daten regelmäßig archiviert oder gelöscht werden müssen. Außerhalb dieser Szenarien bringt sie häufig mehr Verwaltungsaufwand als Nutzen.

Für Magento-Shops ist das besonders relevant: Log-Tabellen, sales_order und sales_order_item wachsen mit jedem Bestellmonat. Ohne Archivierungsstrategie werden diese Tabellen irgendwann zum Flaschenhals. Partitionierung nach Datum ist dort ein natürlicher Ansatz — vorausgesetzt, die Abfragen werden entsprechend formuliert und der Partitionsschlüssel taucht direkt im WHERE-Filter auf.

Wichtig ist außerdem der Unterschied zu einem Composite Index: Ein gut gewählter Index reduziert die gelesenen Zeilen innerhalb einer Tabelle. MySQL Partitionierung eliminiert ganze Tabellensegmente aus dem Lesepfad. Beide Techniken ergänzen sich, aber sie lösen unterschiedliche Probleme. Wer nur einen Index braucht, sollte nicht gleich zur Partitionierung greifen.

2. Partitionstypen: RANGE, LIST, HASH und KEY

MySQL unterstützt vier grundlegende Partitionstypen. Die Wahl hängt vom Zugriffspattern und der Natur der Partitionsspalte ab. Jeder Typ hat seinen optimalen Einsatzbereich — und klare Schwächen, wenn er außerhalb dieses Bereichs eingesetzt wird.

RANGE-Partitionierung ist die häufigste Form für zeitbasierte Daten. Jede Partition enthält Zeilen, deren Partitionsschlüssel in einem bestimmten Wertebereich liegt. Das passt ideal zu Log-Tabellen und Bestelltabellen, die nach Monat oder Quartal abgefragt und archiviert werden.

-- RANGE partition by quarter using UNIX_TIMESTAMP
CREATE TABLE order_log (
    id         INT          NOT NULL AUTO_INCREMENT,
    order_id   INT          NOT NULL,
    created_at DATETIME     NOT NULL,
    message    VARCHAR(255),
    PRIMARY KEY (id, created_at)
)
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
    PARTITION p_2024_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2024-04-01 00:00:00')),
    PARTITION p_2024_q2 VALUES LESS THAN (UNIX_TIMESTAMP('2024-07-01 00:00:00')),
    PARTITION p_2024_q3 VALUES LESS THAN (UNIX_TIMESTAMP('2024-10-01 00:00:00')),
    PARTITION p_2024_q4 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01 00:00:00')),
    PARTITION p_future   VALUES LESS THAN MAXVALUE
);

LIST-Partitionierung eignet sich für diskrete Wertmengen. In Magento-Setups mit mehreren Stores lässt sich damit nach store_id partitionieren, sodass Abfragen für einen einzelnen Store nur dessen Partition lesen. Das funktioniert jedoch nur dann performant, wenn Abfragen konsequent store_id im WHERE-Filter nutzen.

-- LIST partition by store_id for multi-store Magento setup
CREATE TABLE catalog_log (
    id       INT NOT NULL AUTO_INCREMENT,
    store_id SMALLINT UNSIGNED NOT NULL,
    event    VARCHAR(100),
    PRIMARY KEY (id, store_id)
)
PARTITION BY LIST (store_id) (
    PARTITION p_store_de  VALUES IN (1, 2),
    PARTITION p_store_at  VALUES IN (3, 4),
    PARTITION p_store_ch  VALUES IN (5, 6),
    PARTITION p_default   VALUES IN (0)
);

HASH-Partitionierung verteilt Zeilen gleichmäßig über eine feste Anzahl von Partitionen. MySQL berechnet intern MOD(Ausdruck, Anzahl). Das eignet sich, wenn es kein natürliches Abfragemuster nach Partitionsschlüssel gibt, aber I/O-Last über mehrere Festplatten verteilt werden soll. Pruning funktioniert hier nur bei exakten Gleichheitsbedingungen.

KEY-Partitionierung funktioniert ähnlich wie HASH, überlässt aber MySQL die Hash-Funktion. KEY kann auf jeder Spalte mit einem Index angewendet werden — auch auf VARCHAR oder DATETIME — was sie flexibler macht als HASH. In der Praxis ist KEY häufig die bessere Wahl, wenn kein Integer-Wert als Partitionsschlüssel vorhanden ist.

-- HASH partition for even distribution across 8 partitions
CREATE TABLE session_data (
    session_id CHAR(64)     NOT NULL,
    user_id    INT          NOT NULL,
    data       TEXT,
    PRIMARY KEY (session_id, user_id)
)
PARTITION BY HASH (user_id)
PARTITIONS 8;

3. Partition Pruning: Wie der Optimizer Partitionen überspringt

Partition Pruning ist der entscheidende Mechanismus: MySQL liest nur die Partitionen, die für eine Abfrage relevant sein können. Ohne Pruning wäre Partitionierung für die meisten Abfragen sogar langsamer als eine normale Tabelle — denn der Overhead der Partitionsverwaltung würde ohne Benefit anfallen.

Pruning funktioniert, wenn die WHERE-Bedingung den Partitionsschlüssel direkt enthält. Mit EXPLAIN kann man prüfen, welche Partitionen tatsächlich gelesen werden. In MySQL 8 zeigt EXPLAIN FORMAT=JSON die geprüften Partitionen explizit, ältere Varianten nutzen EXPLAIN PARTITIONS.

-- Check which partitions are accessed (MySQL 8+)
EXPLAIN
SELECT entity_id, increment_id, grand_total
FROM sales_order
WHERE created_at >= '2024-10-01'
  AND created_at <  '2025-01-01';

-- Classic variant: EXPLAIN PARTITIONS (still works in MySQL 8)
EXPLAIN PARTITIONS
SELECT entity_id, increment_id
FROM sales_order
WHERE created_at >= '2024-10-01'
  AND created_at <  '2025-01-01';

-- Check partition statistics in information_schema
SELECT partition_name, table_rows, partition_description
FROM information_schema.partitions
WHERE table_schema = DATABASE()
  AND table_name = 'sales_order'
ORDER BY partition_ordinal_position;

Das Ergebnis zeigt im Feld partitions, welche Partitionen der Optimizer für die Abfrage in Betracht zieht. Idealerweise erscheint dort nur eine oder wenige Partitionen statt aller. Wenn alle Partitionen aufgeführt sind, funktioniert Pruning nicht — häufig weil der WHERE-Filter eine Funktion auf dem Partitionsschlüssel enthält oder der Filter aus einem JOIN abgeleitet wird.

Ein häufiges Missverständnis: Pruning entfällt, wenn JOIN-Bedingungen aus einer anderen, nicht partitionierten Tabelle stammen. Der Optimizer kann in diesem Fall nicht vorab ermitteln, welche Partitionswerte relevant sind, und muss alle Partitionen lesen.

4. Partitionswartung: ADD, DROP und REORGANIZE

Ein großer praktischer Vorteil der RANGE-Partitionierung ist das Archivieren per DROP PARTITION. Im Gegensatz zu einem DELETE über Millionen von Zeilen ist das Löschen einer ganzen Partition eine Metadaten-Operation — sie läuft in Millisekunden und erzeugt kaum Locking. Das ist einer der überzeugendsten Gründe, große historische Tabellen zu partitionieren.

-- Add a new monthly partition before the MAXVALUE catchall
ALTER TABLE sales_order
    REORGANIZE PARTITION p_future INTO (
        PARTITION p_2025_02 VALUES LESS THAN (UNIX_TIMESTAMP('2025-03-01 00:00:00')),
        PARTITION p_future  VALUES LESS THAN MAXVALUE
    );

-- Drop an old partition instantly (metadata operation, no row-level locking)
ALTER TABLE sales_order
    DROP PARTITION p_2023_q1;

-- Move partition data to archive before dropping (requires identical schema)
ALTER TABLE sales_order
    EXCHANGE PARTITION p_2023_q1 WITH TABLE sales_order_archive;

-- Then drop the now-empty partition
ALTER TABLE sales_order
    DROP PARTITION p_2023_q1;

Das Muster für eine automatisierte Verwaltung: Jeden Monat eine neue Partition per REORGANIZE PARTITION aus der MAXVALUE-Partition herauslösen, und alte Partitionen jenseits des Aufbewahrungszeitraums per DROP PARTITION löschen oder per EXCHANGE PARTITION in eine Archivtabelle verschieben. In Kombination mit mysqldump auf Partitionsebene entsteht so ein sehr effizienter Archivierungsworkflow ohne Produktionsauswirkungen.

5. Magento sales_order nach Monat partitionieren

Magento speichert Bestellungen in sales_order und den dazugehörigen Zeilenpositionen in sales_order_item. In einem mittelgroßen Shop wächst sales_order schnell auf mehrere Millionen Datensätze. Abfragen im Admin-Backend, in Reports und von Drittintegrationen laufen regelmäßig über created_at-Filter — ein perfekter Kandidat für RANGE-Partitionierung nach Datum.

-- Example: Partitioned sales_order structure (simplified)
-- IMPORTANT: In production, use pt-online-schema-change to avoid full table lock
CREATE TABLE sales_order_partitioned (
    entity_id       INT UNSIGNED     NOT NULL AUTO_INCREMENT,
    increment_id    VARCHAR(50)      NOT NULL,
    store_id        SMALLINT UNSIGNED,
    status          VARCHAR(32),
    grand_total     DECIMAL(20,4),
    created_at      DATETIME         NOT NULL,
    updated_at      DATETIME,
    customer_id     INT UNSIGNED,
    -- Partition key MUST be in every unique index
    PRIMARY KEY (entity_id, created_at),
    UNIQUE KEY idx_increment_id (increment_id, created_at),
    KEY idx_status (status),
    KEY idx_customer_id (customer_id)
)
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
    PARTITION p_2024_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2024-04-01 00:00:00')),
    PARTITION p_2024_q2 VALUES LESS THAN (UNIX_TIMESTAMP('2024-07-01 00:00:00')),
    PARTITION p_2024_q3 VALUES LESS THAN (UNIX_TIMESTAMP('2024-10-01 00:00:00')),
    PARTITION p_2024_q4 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01 00:00:00')),
    PARTITION p_2025_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2025-04-01 00:00:00')),
    PARTITION p_future  VALUES LESS THAN MAXVALUE
);

Eine kritische Einschränkung fällt hier sofort auf: Jeder eindeutige Index muss den Partitionsschlüssel enthalten. Das bedeutet, der PRIMARY KEY und alle UNIQUE-Constraints müssen created_at einschließen. Das ist ein fundamentaler Unterschied zur normalen sales_order-Tabelle und kann Einfluss auf Magento-Modelle haben, die nach entity_id ohne created_at suchen.

Für eine bestehende Produktionstabelle empfiehlt sich die Migration über pt-online-schema-change aus dem Percona Toolkit, um Downtime zu vermeiden. Ein direktes ALTER TABLE auf einer Tabelle mit Millionen von Zeilen sperrt die Tabelle für die gesamte Laufzeit des Schema-Änderungsvorgangs.

6. Einschränkungen und wann man nicht partitionieren sollte

MySQL Partitionierung hat mehrere harte Limits, die man kennen muss, bevor man ein Schema entwirft. Wer diese Grenzen ignoriert, baut technische Schulden auf, die später schwierig zu beseitigen sind.

Foreign Keys: Auf eine partitionierte Tabelle können keine Foreign Keys referenzieren. Magento nutzt zwar im Standard kaum FK-Constraints auf Datenbankebene, aber selbst erstellte Beziehungen in Custom-Modulen müssen entfernt werden, bevor eine Tabelle partitioniert werden kann. Das schränkt Referenzintegrität auf Datenbankebene erheblich ein.

Partitionsschlüssel in jedem Unique Index: Jeder UNIQUE-Index und der PRIMARY KEY müssen den Partitionsschlüssel enthalten. Das schränkt das Schemadesign erheblich ein und kann bestehende Abfragemuster aufbrechen, die nach einem einfachen Primärschlüssel filtern.

Maximum 8192 Partitionen: MySQL erlaubt maximal 8192 Partitionen pro Tabelle. Bei monatlicher Partitionierung sind das theoretisch über 680 Jahre — kein praktisches Problem. Bei tagesgenauen Partitionen ist das Limit jedoch innerhalb von etwa 22 Jahren erreichbar.

-- Pruning WORKS: direct filter on partition key
SELECT entity_id FROM sales_order
WHERE created_at >= '2024-10-01'
  AND created_at <  '2025-01-01';

-- Pruning does NOT work: filter derived from a JOIN
-- MySQL cannot determine relevant partitions at parse time
SELECT o.entity_id
FROM sales_order o
JOIN report_filters rf ON rf.start_date = o.created_at
WHERE rf.report_id = 42;

-- Pruning does NOT work: function wrapping the partition key
-- DATE() prevents index use and partition pruning
SELECT entity_id FROM sales_order
WHERE DATE(created_at) = '2024-10-15';

-- Correct: use a range instead
SELECT entity_id FROM sales_order
WHERE created_at >= '2024-10-15 00:00:00'
  AND created_at <  '2024-10-16 00:00:00';

Die Faustregel lautet: MySQL Partitionierung lohnt sich bei sehr großen Tabellen mit klar zeitbasiertem Zugriffspattern und regelmäßigem Archivierungsbedarf. Für Performance-Probleme bei kleinen bis mittelgroßen Tabellen ist ein gut gewählter Composite Index fast immer die bessere und wartungsärmere Lösung. Partitionierung ist kein Ersatz für gutes Indexdesign.

Mironsoft

MySQL Partitionierung für Magento-Shops sicher planen und umsetzen

Wir helfen dabei, Partitionsstrategien für große Magento-Tabellen zu evaluieren, sicher zu migrieren und langfristig zu warten — ohne Produktionsausfall.

Schema-Analyse

Bestehende Tabellen auf Partitionierungseignung prüfen und Zugriffspattern analysieren

Migration

Livemigration mit pt-osc ohne Downtime und ohne Datenverlust durchführen

Wartungsplan

Automatisierter Monatspartitions-Workflow und Archivierungsstrategie

8. Zusammenfassung

MySQL Partitionierung — Das Wichtigste auf einen Blick

Einsatzgebiet

Sehr große Tabellen mit zeitbasiertem Zugriffspattern und Archivierungsbedarf — z.B. Magento sales_order und Log-Tabellen.

Hauptvorteil

Partition Pruning reduziert I/O-Last; DROP PARTITION archiviert Millionen Zeilen in Millisekunden ohne langen Lock.

Kritische Einschränkungen

Keine FK-Referenzen auf partitionierte Tabellen; Partitionsschlüssel muss in jedem Unique Index enthalten sein; max. 8192 Partitionen.

Praxisregel

Vor der Partitionierung prüfen, ob WHERE-Filter den Partitionsschlüssel direkt enthält — sonst entfällt Pruning und die Abfrage wird langsamer.

9. FAQ: MySQL Partitionierung

1 Was ist MySQL Partitionierung und wozu dient sie?
MySQL Partitionierung teilt eine logische Tabelle intern in physische Segmente. Der Optimizer kann irrelevante Partitionen überspringen (Partition Pruning) und so I/O-Last massiv reduzieren.
2 Welcher Partitionstyp eignet sich für Magento sales_order?
RANGE-Partitionierung nach UNIX_TIMESTAMP(created_at) ist die natürliche Wahl für sales_order, da Abfragen fast immer Datumsbereiche filtern und alte Monate per DROP PARTITION archiviert werden können.
3 Was ist Partition Pruning und wann funktioniert es?
Partition Pruning bedeutet, dass MySQL nur die Partitionen liest, die laut WHERE-Bedingung relevant sind. Es funktioniert, wenn der Partitionsschlüssel direkt im Filter steht — nicht wenn er aus einem JOIN abgeleitet wird.
4 Wie archiviert man alte Partitionen effizient?
Mit ALTER TABLE ... DROP PARTITION wird eine ganze Partition in Millisekunden gelöscht — ohne zeilenweises DELETE und ohne langen Lock. Vorher kann man die Partition per EXCHANGE PARTITION in eine Archivtabelle verschieben.
5 Können Foreign Keys auf partitionierte Tabellen zeigen?
Nein. MySQL erlaubt keine Foreign Key Constraints, die auf eine partitionierte Tabelle referenzieren. Bestehende FK-Beziehungen müssen vor der Partitionierung entfernt werden.
6 Warum muss der Partitionsschlüssel im Primary Key enthalten sein?
MySQL benötigt den Partitionsschlüssel im Primary Key und allen Unique Indexes, um die Eindeutigkeit partitionsübergreifend garantieren zu können. Das ist eine harte Anforderung ohne Ausnahme.
7 Wie viele Partitionen sind maximal möglich?
MySQL erlaubt maximal 8192 Partitionen pro Tabelle. Bei monatlicher Partitionierung entspricht das über 680 Jahren — in der Praxis kein Limit, bei tagesgenauen Partitionen aber innerhalb von 22 Jahren erreichbar.
8 Wann sollte man NICHT partitionieren?
Wenn Abfragen regelmäßig JOIN-Bedingungen für den Datumsfilter nutzen, entfällt Pruning. Ein gut gewählter Composite Index ist dann effizienter und wartungsärmer als Partitionierung.
9 Wie migriert man eine bestehende Magento-Tabelle zu Partitionen?
Für eine Live-Migration ohne Downtime empfiehlt sich pt-online-schema-change aus dem Percona Toolkit. Direkte ALTER TABLE auf großen Produktionstabellen sperren die Tabelle für die gesamte Laufzeit.
10 Was zeigt EXPLAIN PARTITIONS?
EXPLAIN PARTITIONS zeigt im Feld 'partitions', welche Partitionen der Optimizer für eine Abfrage in Betracht zieht. Wenn alle Partitionen aufgeführt sind, funktioniert Pruning nicht und die Abfrage scannt die gesamte Tabelle.