Mysql
 sql >> Database >  >> RDS >> Mysql

Come posso evitare una scansione completa della tabella su questa query MySQL?

Basato su EXPLAIN output nella tua domanda, hai già tutti gli indici che la query dovrebbe utilizzare, ovvero:

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);

(Non sono sicuro dai nomi dei tuoi indici se idx_zip_from_distance include davvero il zipcode_to colonna. In caso contrario, dovresti aggiungerlo per renderlo un indice di copertura . Inoltre, ho incluso venues.id colonna in idx_zipcode per completezza, ma, supponendo che sia la chiave primaria per la tabella e che tu stia utilizzando InnoDB, verrà comunque inclusa automaticamente.)

Tuttavia, sembra che MySQL stia scegliendo un piano di query diverso, e forse non ottimale, in cui esegue la scansione di tutti gli eventi, trova le sedi e i codici postali e solo successivamente filtra i risultati in base alla distanza. Questo potrebbe essere il piano di query ottimale, se la cardinalità della tabella degli eventi fosse sufficientemente bassa, ma dal fatto che stai ponendo questa domanda suppongo che non lo sia.

Uno dei motivi per cui il piano di query non ottimale potrebbe sia il fatto che ne hai troppi indici che confondono il pianificatore. Ad esempio, davvero hai bisogno di tutti e tre questi indici sulla tabella del codice postale, dato che i dati che memorizza sono presumibilmente simmetrici? Personalmente, suggerirei solo l'indice che ho descritto sopra, più un indice univoco (che può anche essere la chiave primaria, se non ne hai una artificiale) su (zipcode_to, zipcode_from) (preferibilmente in quest'ordine, in modo che eventuali query occasionali su zipcode_to=? può farne uso).

Tuttavia, sulla base di alcuni test che ho eseguito, sospetto che il problema principale per cui MySQL sta scegliendo il piano di query sbagliato dipenda semplicemente dalle cardinalità relative delle tabelle. Presumibilmente, le tue attuali zipcode_distances la tabella è enorme e MySQL non è abbastanza intelligente da rendersi conto di quanto siano le condizioni nel WHERE clausola davvero restringere il campo.

In tal caso, la soluzione migliore e più semplice potrebbe essere semplicemente forzare MySQL per utilizzare gli indici desiderati :

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

Con quella query, dovresti effettivamente ottenere il piano di query desiderato. (Hai bisogno di FORCE INDEX qui, poiché con solo USE INDEX il pianificatore di query potrebbe comunque decidere di utilizzare una scansione della tabella invece dell'indice suggerito, vanificando lo scopo. Mi è successo quando l'ho testato per la prima volta.)

Sal. Ecco una demo su SQLize, entrambi con e senza FORCE INDEX , a dimostrazione del problema.