2016-05-28 10 views
0

Für Informationen in den folgenden Beispielen besteht big_table aus Millionen von Zeilen und small_table von Hunderten.wo like und Reihenfolge von auf verschiedenen Tabellen/Spalten

Hier ist die grundlegende Abfrage ich zu tun bin versucht:

SELECT b.id 
    FROM big_table b 
    LEFT JOIN small_table s 
    ON b.small_id=s.id 
    WHERE s.name like 'something%' 
    ORDER BY b.name 
    LIMIT 10, 10; 

Dies ist langsam und ich kann verstehen, warum beiden Indices nicht verwendet werden können.

Meine ursprüngliche Idee war, die Abfrage in Teile zu teilen.

Das ist schnell:

SELECT id FROM small_table WHERE name like 'something%'; 

Dies ist auch schnell:

SELECT id FROM big_table WHERE small_id IN (1, 2) ORDER BY name LIMIT 10, 10; 

Aber zusammen, es wird langsam:

SELECT id FROM big_table 
    WHERE small_id 
    IN (
     SELECT id 
     FROM small_table WHERE name like 'something%' 
    ) 
    ORDER BY name 
    LIMIT 10, 10; 

Es sei denn, die Unterabfrage neu bewertet wird Für jede Zeile sollte es nicht langsamer sein, als beide Abfragen getrennt auszuführen, richtig?

Ich suche nach Hilfe bei der Optimierung der ersten Abfrage und Verständnis, warum die zweite nicht funktioniert.


ERKLÄREN Ergebnis für die letzte Abfrage:

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra 
| 1 | PRIMARY | small_table | range | PRIMARY, ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index; Using temporary; Using filesort | 
| 1 | PRIMARY | big_table | ref | ix_join_foreign_key | ix_join_foreign_key | 9 | small_table.id | 11870 | | 

temporäre Lösung:

SELECT id FROM big_table ignore index(ix_join_foreign_key) 
    WHERE small_id 
    IN (
     SELECT id 
     FROM small_table ignore index(PRIMARY) 
     WHERE name like 'something%' 
    ) 
    ORDER BY name 
    LIMIT 10, 10; 

(Folge & erklären ist das gleiche mit einem statt IN VORHANDEN)

ERKLÄREN Ausgang wird:

| 1 | PRIMARY | big_table | index | NULL | ix_big_name | 768 | NULL | 20 | | 
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 8 | func | 1 | | 
| 2 | MATERIALIZED | small_table | range | ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index | 

wenn jemand eine bessere Lösung hat, ich bin immer noch interessiert.

+0

Wie sieht der Ausführungsplan für die letzte Abfrage aus? – MaxU

+0

ist 'ix_small_name' ein Index basierend auf' big_table.small_id'? – MaxU

+0

ON b.small_ids.id? und deine Tische, wie sind sie? – e4c5

Antwort

1

Das Problem, dem Sie gegenüberstehen, ist, dass Sie Bedingungen auf der kleinen Tabelle haben, aber versuchen, eine Sortierung in der großen Tabelle zu vermeiden. In MySQL denke ich, dass Sie mindestens einen vollständigen Tabellenscan durchführen müssen.

Ein Schritt ist die Abfrage mit exists zu schreiben, wie andere schon erwähnt haben:

SELECT b.id 
FROM big_table b 
WHERE EXISTS (SELECT 1 
       FROM small_table s 
       WHERE s.name LIKE 'something%' AND s.id = b.small_id 
      ) 
ORDER BY b.name; 

Die Frage ist: Können Sie MySQL Trick in den ORDER BY tun einen Index? Eine Möglichkeit besteht darin, den entsprechenden Index zu verwenden. In diesem Fall lautet der entsprechende Index: big_table(name, small_id, id) und small_table(id, name). Die Reihenfolge der Schlüssel im Index ist wichtig. Da der erste ein überdeckender Index ist, könnte MySQL den Index in der Reihenfolge seines Namens durchlesen und die entsprechenden IDs auswählen.

+0

das funktioniert, solange ich die Verwendung von 'small_table (id, name)' erzwinge. Andernfalls wird die 'small_table (name)' verwendet, und wenn sie entfernt wird, verwendet big table den Fremdschlüssel als Index und nicht 'big_table (name, small_id, id)'. Würde es Ihnen etwas ausmachen, meine andere Lösung in der ursprünglichen Frage zu überprüfen? Es erfordert nicht mehr Indizes zu erstellen, aber ich bin mir nicht sicher, was besser ist (oder warum). – user1278743

0

Sie können dies versuchen:

SELECT b.id 
FROM big_table b 
JOIN small_table s 
    ON b.small_id = s.id 
WHERE s.name like 'something%' 
ORDER BY b.name; 

oder

SELECT b.id FROM big_table b 
WHERE EXISTS(SELECT 1 FROM small_table s 
      WHERE s.name LIKE 'something%' AND s.id = b.small_id) 
ORDER BY b.name; 

HINWEIS: Sie scheinen nicht LEFT JOIN zu müssen. LEFT OUTER JOIN führt fast immer in vollständiger Tabellenscan des big_table

PS stellen Sie sicher, Sie haben einen Index für big_table.small_id

+0

Sie haben die ORDER BY entfernt, was die Abfrage verlangsamt. Ich brauche es. Sie haben Recht mit dem LINKEN JOIN, aber das Entfernen hilft nicht wirklich. – user1278743

+0

@ user1278743, ich denke 'links beitreten' - machte Ihre Abfrage langsam – MaxU

+0

Ich entfernte es, es ändert nichts. mysql macht immer noch einen filesort auf big_table.name (obwohl ein Index verfügbar ist). – user1278743

1

Sie suchen eine EXISTS oder IN Abfrage. Da MySQL auf IN schwach bekannt ist, würde ich EXISTS versuchen, trotz IN besser wegen seiner Einfachheit.

select id 
from big_table b 
where exists 
(
    select * 
    from small_table s 
    where s.id = b.small_id 
    and s.name = 'something%' 
) 
order by name 
limit 10, 10; 

Es wäre hilfreich, einen guten Index über big_table zu haben. Es sollte zuerst die small_id enthalten, um die Übereinstimmung zu finden, dann die name für die Sortierung. Die ID ist, soweit ich weiß, automatisch in MySQL-Indizes enthalten (ansonsten sollte sie auch dem Index hinzugefügt werden). Sie hätten also einen Index, der alle von big_table benötigten Felder (also einen Deckungsindex) in der gewünschten Reihenfolge enthält, so dass alle Daten aus dem Index gelesen werden können und auf die Tabelle selbst nicht zugegriffen werden muss.

create index idx_big_quick on big_table(small_id, name); 
+0

versuchte es, aber es hilft nicht. Das Problem ist immer noch die 'order by name'. Mysql verwendet Ihren idx_big_quick-Index für das where exists/where in und führt dann einen filesort durch, ohne den richtigen Index zu verwenden. Was offensichtlich extrem langsam ist. – user1278743

+0

Das ist seltsam. Warum liest MySQL die Tabelle, wenn sich alles im Index befindet? Vielleicht lag ich falsch, weil die ID automatisch Teil des Indexes war. Können Sie es bitte hinzufügen? 'create index idx_big_quick auf big_table (small_id, name, id);' –

+0

immer noch gleich. Überprüfen Sie das Erklärungsergebnis in meiner Frage (es ist dasselbe mit 'idx_big_quick' anstelle von' ix_join_foreign_key'). Was ich seltsam finde ist, warum ist 'using filesort' auf der' small_table' Zeile und nicht 'big table'? – user1278743

0

Plan A

SELECT b.id 
    FROM big_table b 
    JOIN small_table s ON b.small_id=s.id 
    WHERE s.name like 'something%' 
    ORDER BY b.name 
    LIMIT 10, 10; 

(Hinweis Entfernung von LEFT.)

Sie benötigen

small_table: INDEX(name, id) 
big_table: INDEX(small_id), or, for 'covering': INDEX(small_id, name, id) 

Es wird die s Index verwenden 'something%' und zu Fuß durch zu finden. Aber es muss alle solche Zeilen finden, und JOIN bis b, um alle solche Zeilen dort zu finden. Nur dann kann es die ORDER BY, OFFSET und LIMIT tun. Dort wird ein Filesort sein (welcher kann in RAM passieren).

Die Reihenfolge der Spalten in den Indizes ist wichtig.

Plan B

Der andere Vorschlag kann Arbeit gut; es hängt von verschiedenen Dingen ab.

SELECT b.id 
    FROM big_table b 
    WHERE EXISTS 
     (SELECT * 
      FROM small_table s 
      WHERE s.name LIKE 'something%' 
       AND s.id = b.small_id 
    ) 
    ORDER BY b.name 
    LIMIT 10, 10; 

, dass diese Bedürfnisse:

big_table: INDEX(name), or for 'covering', INDEX(name, small_id, id) 
small_table: INDEX(id, name), which is 'covering' 

(Caveat:. Wenn Sie etwas anderes als SELECT b.id, meine Kommentare über Abdeckung falsch machen kann)

Welche ist schneller (A oder B) Kann nicht vorhersagen, ohne die Häufigkeit von "etwas%" zu verstehen und wie "viele" das Viele-zu-1-Mapping ist.

Einstellungen

Wenn diese Tabellen InnoDB sind, dann sicher sein, dass innodb_buffer_pool_size bis etwa 70% der verfügbar RAM eingestellt ist.

Paginierung

Ihre Nutzung von OFFSET bedeutet, dass Sie 'Paging' durch die Daten sind? OFFSET ist eine ineffiziente Art und Weise, dies zu tun. Siehe my blog auf solche, aber beachten Sie, dass nur Plan B damit arbeiten wird.

Verwandte Themen