Bietet Rezepte, Lösungen und ausführliche Beispiele rund um gesundes SQL und zufriedene Datenbanken.
Bei einer flexiblen Suche müssen häufig mehrere Tabellenspalten mit der selben Benutzereingabe durchsucht werden und dann auch noch mit LIKE '%suchbegriff%'. Für gewöhnlich bedeutet dies für die Datenbank einen "Full Table Scan" über die ganze Tabelle. Welche Möglichkeiten gibt es eigentlich, diese Abfrage irgendwie zu beschleunigen?
* MySQL 5.1.30
CREATE TABLE adresse (
id INT NOT NULL,
vorname VARCHAR(50) NOT NULL,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
ort VARCHAR(100) NOT NULL,
PRIMARY KEY(id)
);
INSERT INTO adresse VALUES
( 1, 'Vorname-1','Nachname-1','[email protected]', 'Ort-1'),
( 2, 'Vorname-2','Nachname-2','[email protected]', 'Ort-2'),
( 3, 'Vorname-3','Nachname-3','[email protected]', 'Ort-3'),
( 4, 'Vorname-4','Nachname-4','[email protected]', 'Ort-4'),
( 5, 'Vorname-5','Nachname-5','[email protected]', 'Ort-5'),
( 6, 'Vorname-6','Nachname-6','[email protected]', 'Ort-6'),
( 7, 'Vorname-7','Nachname-7','[email protected]', 'Ort-7'),
( 8, 'Vorname-8','Nachname-8','[email protected]', 'Ort-8'),
( 9, 'Vorname-9','Nachname-9','[email protected]', 'Ort-9');
CREATE INDEX sx_adresse_name ON adresse (name);
CREATE INDEX sx_adresse_vorname ON adresse (vorname);
CREATE INDEX sx_adresse_email ON adresse (email);
Ziel ist es nun, den in Listing 2: rot markierte Datensatz (ID=6) zu finden. Wobei für den Anwender erst einmal unklar ist, in welcher Tabellenspalte der Suchbegriff steht. Im Vorname oder Namen oder vielleicht in der E-Mail.
Mit dieser Abfrage werden gleich drei Spalten nach einem bestimmten Eingabewert durchsucht.
SELECT id
FROM adresse
WHERE vorname LIKE '%-6%'
OR name LIKE '%-6%'
OR email LIKE '%-6%';
+----+
| id |
+----+
| 6 |
+----+
1 row in set (0.03 sec)
EXPLAIN
SELECT id
FROM adresse
WHERE vorname LIKE '%-6%'
OR name LIKE '%-6%'
OR email LIKE '%-6%';
+-...-+---------+------+---------------+------+-...-+-------------+
| ... | table | type | possible_keys | key | ... | Extra |
+-...-+---------+------+---------------+------+-...-+-------------+
| ... | adresse | ALL | NULL | NULL | ... | Using where |
+-...-+---------+------+---------------+------+-...-+-------------+
1 row in set (0.02 sec)
Funktioniert wie erwartet, aber der Zugriffspfad (Listing 4:) ist leider auch so schlecht wie erwartet. In der EXPLAIN-Spalte type steht ALL (also wird die komplette Tabelle durchsucht).
Die bisher angelegten Indices helfen MySQL nicht weiter. Dann entfernen wir diese Indices auch wieder (Listing 5:) und legen einen neuen zusammengesetzten Super-Index an (Listing 6:).
DROP INDEX sx_adresse_name ON adresse;
DROP INDEX sx_adresse_vorname ON adresse;
DROP INDEX sx_adresse_email ON adresse;
CREATE INDEX sx_adresse_for_like ON adresse (vorname, name, email, id);
Alle(!) Felder der obigen Abfrage (Listing 3:) werden in den Index genommen. Auch die Spalte ID. Wichtig ist dabei die Reihenfolge der Spalten. ID muss ganz nach hinten (also mit der geringsten Bedeutung für den Suchvorgang)
SELECT id
FROM adresse
WHERE CONCAT(vorname,name,email) LIKE '%-6%';
+----+
| id |
+----+
| 6 |
+----+
1 row in set (0.02 sec)
Funktioniert wie erhofft. Mit CONCAT werden erst alle drei relevanten Spalten zu einer einzigen verknüpft und diese auf einmal mit LIKE durchsucht. Und wie sieht nun der MySQL Zugriffspfad aus? (Listing 8:)
EXPLAIN
SELECT id
FROM adresse
WHERE CONCAT(vorname,name,email) LIKE '%-6%';
+-...-+---------+-------+---------------+---------------------+-...-+--------------------------+
| ... | table | type | possible_keys | key | ... | Extra |
+-...-+---------+-------+---------------+---------------------+-...-+--------------------------+
| ... | adresse | index | NULL | sx_adresse_for_like | ... | Using where; Using index |
+-...-+---------+-------+---------------+---------------------+-...-+--------------------------+
1 row in set (0.00 sec)
Perfekt! MySQL nutzt den neuen Index. Interessanterweise mit dem Hinweis possible_key NULL und key dann unser Super-Index sx_adresse_for_like.
Soll jetzt aber eine weitere Spalte (ort) angezeigt werden, wird der neue Index nicht mehr genutzt und wir sind wieder beim Full table scan.
EXPLAIN
SELECT id, ort
FROM adresse
WHERE CONCAT(vorname,name,email) LIKE '%-6%';
+-...-+---------+------+---------------+-...-+-------------+
| ... | table | type | possible_keys | ... | Extra |
+-...-+---------+------+---------------+-...-+-------------+
| ... | adresse | ALL | NULL | ... | Using where |
+-...-+---------+------+---------------+-...-+-------------+
1 row in set (0.00 sec)
Durch einen weiteren Umbau der SQL-Abfrage schaffen wir es aber wieder, MySQL zu überreden, den kombinieren Index doch zu verwenden.
EXPLAIN
SELECT a.id, a.ort
FROM adresse a
JOIN (SELECT id
FROM adresse
WHERE CONCAT(vorname,name,email) LIKE '%-6%') rs
ON a.id = rs.id
+----+-------------+------------+--------+---------------+---------------------+-...-+--------------------------+
| id | select_type | table | type | possible_keys | key | ... | Extra |
+----+-------------+------------+--------+---------------+---------------------+-...-+--------------------------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | ... | |
| 1 | PRIMARY | a | const | PRIMARY | PRIMARY | ... | |
| 2 | DERIVED | adresse | index | NULL | sx_adresse_for_like | ... | Using where; Using index |
+----+-------------+------------+--------+---------------+---------------------+-...-+--------------------------+
3 rows in set (0.02 sec)
Für manche SQL-Abfragen können spezielle kombinierte Indices helfen die Abarbeitung zu beschleunigen. Allerdings ist so ein kombinierter Index ziemlich groß und benötigt unter Umständen einiges an Plattenplatz. Und bei INSERT, UPDATE und DELETE-Befehlen eine zeitlich aufwändigere (interne) Aktualisierung des Index.