Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Wie kann ich einen vollständigen Tabellenscan bei dieser MySQL-Abfrage vermeiden?

Basierend auf EXPLAIN Ausgabe in Ihrer Frage haben Sie bereits alle Indizes, die die Abfrage sollte verwenden, nämlich:

CREATE INDEX idx_zip_from_distance
  ON zipcode_distances (zipcode_from, distance, zipcode_to);
CREATE INDEX idx_zipcode ON venues (zipcode, id);
CREATE INDEX idx_venue_id ON events (venue_id);

(Ich bin mir anhand Ihrer Indexnamen nicht sicher, ob idx_zip_from_distance enthält wirklich die zipcode_to Säule. Wenn nicht, sollten Sie es hinzufügen, um es zu einem überdeckenden Index zu machen . Außerdem habe ich die venues.id eingefügt Spalte in idx_zipcode Der Vollständigkeit halber, aber vorausgesetzt, es ist der Primärschlüssel für die Tabelle und Sie verwenden InnoDB, wird er sowieso automatisch eingefügt.)

Es sieht jedoch so aus, als würde MySQL einen anderen und möglicherweise suboptimalen Abfrageplan wählen, bei dem es alle Veranstaltungen durchsucht, ihre Veranstaltungsorte und Postleitzahlen findet und erst dann die Ergebnisse nach Entfernung filtert. Das könnte der optimale Abfrageplan sein, wenn die Kardinalität der Ereignistabelle niedrig genug war, aber aufgrund der Tatsache, dass Sie diese Frage stellen, gehe ich davon aus, dass dies nicht der Fall ist.

Ein Grund für den suboptimalen Abfrageplan könnte die Tatsache sein, dass Sie zu viele haben Indizes, die den Planer verwirren. Zum Beispiel, tun Sie das wirklich Benötigen Sie alle drei Indizes für die Postleitzahlentabelle, da die darin gespeicherten Daten vermutlich symmetrisch sind? Persönlich würde ich nur den oben beschriebenen Index vorschlagen, plus einen eindeutigen Index (der auch der Primärschlüssel sein kann, wenn Sie keinen künstlichen haben) auf (zipcode_to, zipcode_from) (am besten in dieser Reihenfolge, damit gelegentliche Abfragen auf zipcode_to=? kann davon Gebrauch machen).

Basierend auf einigen von mir durchgeführten Tests vermute ich jedoch, dass das Hauptproblem, warum MySQL den falschen Abfrageplan auswählt, einfach auf die relativen Kardinalitäten Ihrer Tabellen zurückzuführen ist. Vermutlich Ihre tatsächlichen zipcode_distances Tabelle ist riesig , und MySQL ist nicht intelligent genug, um zu erkennen, wie sehr die Bedingungen in WHERE Klausel wirklich eingrenzen.

Wenn dies der Fall ist, besteht die beste und einfachste Lösung darin, einfach erzwingen MySQL, um die gewünschten Indizes zu verwenden :

select
    *
from
    zipcode_distances z 
    FORCE INDEX (idx_zip_from_distance)
inner join
    venues v    
    FORCE INDEX (idx_zipcode)
    on z.zipcode_to=v.zipcode
inner join
    events e
    FORCE INDEX (idx_venue_id)
    on v.id=e.venue_id
where
    z.zipcode_from='92108' and
    z.distance <= 5

Mit dieser Abfrage sollten Sie tatsächlich den gewünschten Abfrageplan erhalten. (Sie benötigen FORCE INDEX hier, da mit nur USE INDEX Der Abfrageplaner könnte immer noch entscheiden, einen Tabellenscan anstelle des vorgeschlagenen Index zu verwenden, wodurch der Zweck verfehlt wird. Mir ist das passiert, als ich das zum ersten Mal getestet habe.)

PS. Hier ist eine Demo zu SQLize, sowohl mit und ohne FORCE INDEX , das das Problem demonstriert.