<h1>MySQL-Leistungsvergleich: Arbeiten mit und ohne Indizes</h1>
<p>In diesem Artikel untersuchen wir, wie sich Indizes auf die Abfrageleistung in MySQL auswirken, indem wir die Ausführung von Filter- und JOIN-Operationen mit und ohne Indizes vergleichen.</p>
<h2>Was sind Indizes und warum werden sie benötigt?</h2>
<p>Indizes in MySQL sind spezielle Datenstrukturen, die die Datensuche und -abfrage aus Tabellen beschleunigen. Ohne Indizes muss MySQL einen vollständigen Tabellenscan durchführen, was dem Lesen eines ganzen Buches gleicht, um ein einziges Wort zu finden. Mit Indizes wird die Suche vergleichbar mit der Nutzung des Inhaltsverzeichnisses eines Buches.</p>
<h3>Beispiel 1: Datenfilterung nach einem einzelnen Feld</h3>
<p><strong>Einrichten der Testumgebung</strong></p>
<p>Erstellen wir eine Testtabelle und füllen sie mit Daten:</p>
<div class="code">-- Testdatenbank erstellen
CREATE DATABASE test_indexes;
USE test_indexes;
-- Tabelle ohne Indizes erstellen
CREATE TABLE users_no_index (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(100),
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Ähnliche Tabelle mit Index erstellen
CREATE TABLE users_with_index (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(100),
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_age (age)
);
-- Tabellen mit Testdaten füllen (je 100.000 Datensätze)
DELIMITER $$
CREATE PROCEDURE GenerateTestData()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 100000 DO
INSERT INTO users_no_index (name, email, age)
VALUES (CONCAT('User', i), CONCAT('user', i, '@example.com'), FLOOR(RAND() * 100));
INSERT INTO users_with_index (name, email, age)
VALUES (CONCAT('User', i), CONCAT('user', i, '@example.com'), FLOOR(RAND() * 100));
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
CALL GenerateTestData();</div>
<h3>Vergleich der Filterleistung</h3>
<p><strong>Abfrage ohne Index:</strong></p>
<div class="code">-- Abfrage ohne Index analysieren
EXPLAIN SELECT * FROM users_no_index WHERE age = 25;</div>
<p><strong>EXPLAIN-Ergebnisse:</strong></p>
<ul>
<li>type: ALL (vollständiger Tabellenscan)</li>
<li>rows: 100000 (100.000 Zeilen geprüft)</li>
<li>Extra: Using where</li>
</ul>
<p><strong>Ausführungszeit:</strong> ~120 ms</p>
<p><strong>Abfrage mit Index:</strong></p>
<div class="code">-- Abfrage mit Index analysieren
EXPLAIN SELECT * FROM users_with_index WHERE age = 25;</div>
<p><strong>EXPLAIN-Ergebnisse:</strong></p>
<ul>
<li>type: ref (Indexsuche)</li>
<li>rows: ~1000 (nur ~1000 Zeilen geprüft)</li>
<li>key: idx_age (Index verwendet)</li>
<li>Extra: Using index condition</li>
</ul>
<p><strong>Ausführungszeit:</strong> ~5 ms</p>
<p><strong>Schlussfolgerungen zur Filterung</strong></p>
<ul>
<li><strong>Ohne Index:</strong> MySQL führt einen vollständigen Tabellenscan durch und prüft jede Zeile</li>
<li><strong>Mit Index:</strong> MySQL verwendet den B-Baum-Index, um die benötigten Zeilen schnell zu finden</li>
<li><strong>Leistungsunterschied:</strong> 20–30 Mal schneller</li>
</ul>
<h3>Beispiel 2: JOIN-Operationen mit und ohne Indizes</h3>
<p><strong>Vorbereitung verwandter Tabellen</strong></p>
<div class="code">-- Bestelltabelle ohne Indizes
CREATE TABLE orders_no_index (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
amount DECIMAL(10,2),
order_date DATE,
status VARCHAR(20)
);
-- Bestelltabelle mit Index
CREATE TABLE orders_with_index (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
amount DECIMAL(10,2),
order_date DATE,
status VARCHAR(20),
INDEX idx_user_id (user_id)
);
-- Bestelltabellen füllen
DELIMITER $$
CREATE PROCEDURE GenerateOrderData()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 50000 DO
INSERT INTO orders_no_index (user_id, amount, order_date, status)
VALUES (FLOOR(RAND() 100000) + 1, RAND() 1000,
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY),
'completed');
INSERT INTO orders_with_index (user_id, amount, order_date, status)
VALUES (FLOOR(RAND() 100000) + 1, RAND() 1000,
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY),
'completed');
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
CALL GenerateOrderData();</div>
<h3>Vergleich der JOIN-Operationen</h3>
<p><strong>JOIN ohne Indizes:</strong></p>
<div class="code">-- JOIN ohne Indizes
EXPLAIN
SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_amount
FROM users_no_index u
JOIN orders_no_index o ON u.id = o.user_id
WHERE u.age BETWEEN 25 AND 35
<p><strong>EXPLAIN-Ergebnisse:</strong></p>
<ul>
<li>Für beide Tabellen: type: ALL</li>
<li>rows: 100000 * 50000 = 5.000.000.000 potenzielle Vergleiche</li>
<li>Extra: Using where; Using temporary; Using filesort</li>
</ul>
<p><strong>Ausführungszeit:</strong> ~4500 ms</p>
<p><strong>JOIN mit Indizes:</strong></p>
<div class="code">-- JOIN mit Indizes
EXPLAIN
SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_amount
FROM users_with_index u
JOIN orders_with_index o ON u.id = o.user_id
WHERE u.age BETWEEN 25 AND 35
<p><strong>EXPLAIN-Ergebnisse:</strong></p>
<ul>
<li>Für users: type: range (verwendet age-Index)</li>
<li>Für orders: type: ref (verwendet user_id-Index)</li>
<li>rows: deutlich weniger</li>
<li>Effizientere Nutzung temporärer Tabellen</li>
</ul>
<p><strong>Ausführungszeit:</strong> ~150 ms</p>
<p><strong>Schlussfolgerungen zu JOIN-Operationen</strong></p>
<ul>
<li><strong>Ohne Indizes:</strong> MySQL ist gezwungen, verschachtelte Schleifen mit vollständigen Tabellenscans durchzuführen</li>
<li><strong>Mit Indizes:</strong> MySQL verwendet Indizes effizient, um verwandte Datensätze schnell zu finden</li>
<li><strong>Leistungsunterschied:</strong> 30 Mal oder mehr</li>
</ul>
<h3>Wann sollten Indizes verwendet werden?</h3>
<p><strong>Empfohlen, Indizes zu erstellen für:</strong></p>
<ul>
<li>Felder, die häufig in WHERE-Bedingungen verwendet werden</li>
<li>Felder, die an JOIN-Operationen beteiligt sind</li>
<li>Felder, die zum Sortieren verwendet werden (ORDER BY)</li>
<li>Felder, die in GROUP BY verwendet werden</li>
<li>Eindeutige Felder oder Primärschlüssel</li>
</ul>
<p><strong>Wann Indizes ineffizient sein können</strong>:</p>
<ul>
<li>Tabellen mit häufigen INSERT/UPDATE/DELETE-Operationen</li>
<li>Kleine Tabellen (weniger als 1000 Zeilen)</li>
<li>Spalten mit geringer Selektivität (wenige eindeutige Werte)</li>
</ul>
<h3>Best Practices für die Arbeit mit Indizes</h3>
<ul>
<li><strong>Bewusst indizieren</strong> — jeder Index verlangsamt Schreiboperationen</li>
<li><strong>Zusammengesetzte Indizes verwenden</strong> für häufig verwendete Feldkombinationen</li>
<li><strong>Selektivität überwachen</strong> — Indizes auf Feldern mit wenigen eindeutigen Werten sind weniger effektiv</li>
<li><strong>Indexnutzung regelmäßig analysieren</strong>:</li>
</ul>
<div class="code">-- Indexnutzung analysieren
SELECT * FROM sys.schema_unused_indexes;</div>
<p>5. <strong>Bestehende Indizes optimieren</strong>:</p>
<div class="code">-- Abfrageleistung analysieren
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age = 25;</div>
<div class="code">-- Indexfragmentierung prüfen
ANALYZE TABLE users_with_index;</div>
<h2>Fazit</h2>
<p>Indizes sind ein leistungsstarkes Werkzeug zur Optimierung der MySQL-Leistung. Bei richtiger Verwendung können sie die Abfrageausführung um ein Vielfaches beschleunigen, insbesondere bei Filter- und JOIN-Operationen. Es ist jedoch wichtig, die Balance zu wahren — übermäßige Indizierung kann Schreiboperationen verlangsamen. Regelmäßige Überwachung und Leistungsanalyse helfen dabei, die optimale Indexkonfiguration für Ihre Anwendung zu finden.</p>
<p>Testen Sie mit Ihren eigenen Daten, da die Effektivität von Indizes stark von den spezifischen Eigenschaften Ihrer Daten und den Abfragemustern in Ihrer Anwendung abhängt.</p>