2017-12-22 2 views
1

Ich möchte alle Benutzer und den Status des Newsletter-Abonnements auflisten. Da jemand muss kein Benutzer sein, wenn Sie den Newsletter abonniert haben, mache ich so etwas wie:MySql: Unterabfrage mit index_merge optimieren

SELECT user.id, user.email, 
(SELECT newsletter.status FROM newsletter 
     WHERE newsletter.email=user.email OR newsletter.user = user.id) AS status 
FROM user 
WHERE... 

Indizes sind user.id, user.email, newsletter.email, newsletter.user.

Das OR macht die Abfrage unglaublich langsam. Ich habe hier Union as sub query MySQL gefunden, dass Sie eine "Indexzusammenführung" durchführen können, die die Abfrage beschleunigen wird. Aber ich bin nicht sicher, wie MySQL in meinem Fall eine Indexzusammenführung erzwingt. Irgendwelche Ideen?

Hinzugefügt: 2. Zeile von EXPLAIN verwendet keinen Schlüssel.

DROP TABLE if exists user; 
DROP TABLE if exists newsletter; 

create table user (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, 
`email` varchar(255) DEFAULT NULL, INDEX email(email) 
) ENGINE=InnoDB; 

create table newsletter (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, 
`status` enum('subscribed','unsubscribed') DEFAULT NULL, 
`email` varchar(255) DEFAULT NULL, INDEX email(email), 
`user` int(10) unsigned DEFAULT NULL, INDEX user(user) 
) ENGINE=InnoDB; 

EXPLAIN SELECT user.id, user.email, 
(SELECT newsletter.status FROM newsletter 
     WHERE newsletter.email=user.email OR newsletter.user = user.id) AS status 
FROM user; 

+----+--------------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 
| id | select_type  | table  | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra           | 
+----+--------------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 
| 1 | PRIMARY   | user  | NULL  | index | NULL   | email | 768  | NULL | 1 | 100.00 | Using index         | 
| 2 | DEPENDENT SUBQUERY | newsletter | NULL  | ALL | email,user | NULL | NULL | NULL | 1 | 100.00 | Range checked for each record (index map: 0x6) | 
+----+--------------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 

EXPLAIN SELECT user.id, user.email, newsletter.status 
FROM user 
LEFT JOIN newsletter ON (newsletter.email=user.email OR newsletter.user = user.id); 

+----+-------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 
| id | select_type | table  | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra           | 
+----+-------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 
| 1 | SIMPLE  | user  | NULL  | index | NULL   | email | 768  | NULL | 1 | 100.00 | Using index         | 
| 1 | SIMPLE  | newsletter | NULL  | ALL | email,user | NULL | NULL | NULL | 1 | 100.00 | Range checked for each record (index map: 0x6) | 
+----+-------------+------------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------------------+ 
+0

Haben Sie versucht, beitreten? –

+0

Ja, dasselbe Problem: LINKER JOIN-Newsletter EIN (newsletter.user = users.id ODER newsletter.email = users.email) => langsam. – Werner

+0

Sie sollten Ihre Tabellendefinitionen zusammen mit einem EXPLAIN-Plan veröffentlichen. –

Antwort

1

Es sei denn, Sie switched off index_merge haben, wird MySQL es verwenden, wenn er denkt, dass es von Nutzen sein wird. Aber ich finde, dass es weniger oft einsetzt, als man es erwarten könnte, und selbst wenn es das tut, ist es nicht sehr hilfreich - die Abfrage ist immer noch viel langsamer als die Verwendung eines Indexes auf herkömmliche Weise.

Die typische Lösung für diese Art von Abfragen besteht darin, eine UNION aus zwei einfacheren Abfragen zu erstellen.

explain select u.id, u.email, n.status 
from user u left join newsletter n on u.email=n.email 
union 
select u.id, u.email, n.status 
from user u left join newsletter n on u.id=n.user; 

+----+--------------+------------+-------+---------------+-------+---------+--------------+------+-----------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref   | rows | Extra   | 
+----+--------------+------------+-------+---------------+-------+---------+--------------+------+-----------------+ 
| 1 | PRIMARY  | u   | index | NULL   | email | 403  | NULL   | 1 | Using index  | 
| 1 | PRIMARY  | n   | ref | email   | email | 403  | test.u.email | 1 | NULL   | 
| 2 | UNION  | u   | index | NULL   | email | 403  | NULL   | 1 | Using index  | 
| 2 | UNION  | n   | ref | user   | user | 5  | test.u.id | 1 | NULL   | 
| NULL | UNION RESULT | <union1,2> | ALL | NULL   | NULL | NULL | NULL   | NULL | Using temporary | 
+----+--------------+------------+-------+---------------+-------+---------+--------------+------+-----------------+ 

würden Sie besser dran, Ihre Daten Fixierung, so dass Sie nur auf der Benutzer-ID zu verbinden, nicht auf der E-Mail.

+0

Ja, index_merge in auf. UNION funktioniert gut, also vielen Dank. Ich habe nur nach https://stackoverflow.com/questions/1159384/union-as-sub-query-mysql gelernt, wäre es möglich, MySql index_merge zu machen, indem man einen speziellen Index (vielleicht einen kombinierten?) Definiert, ohne jeden neu zu schreiben Abfrage. Ja, die Datenstruktur ist nicht sehr gut, leider ist es eine 3rd-Party-Sache, also ist es schwer zu ändern. – Werner