Indici per has_many: through
-
03-07-2019 - |
Domanda
Supponi di avere due modelli, Utente e Città, uniti da un CityPermission di terzo modello:
class CityPermission < ActiveRecord::Base
belongs_to :city
belongs_to :user
end
class City < ActiveRecord::Base
has_many :city_permissions
has_many :users, :through => :city_permissions
end
class User < ActiveRecord::Base
has_many :city_permissions
has_many :cities, :through => :city_permissions
end
Attualmente, creo la tabella di join e l'indice per la tabella, utilizzando il seguente frammento di codice di migrazione:
create_table :city_permissions do |t|
t.integer :user_id, :city_id
t.other_fields ...
end
add_index(:city_permissions, :user_id)
add_index(:city_permissions, :city_id)
Sono questi gli indici ottimali da creare? Questi indici consentiranno un accesso rapido avanti e indietro attraverso la tabella di join, nonché ricerche rapide all'interno della tabella stessa o esiste un altro modo migliore? Per ribadirlo in modo leggermente diverso, questi indici, dati city
e user
sono variabili di istanza della classe City e User, consentono city.users
, city.city_permissions
, user.cities
e user.city_permissions
si comportano allo stesso modo?
Soluzione
Mi sembra bello.
I join generati dovrebbero essere solo sugli ID PK delle tabelle delle entità o sugli ID FK nella tabella dei join, che sono entrambi indici.
Probabilmente sarebbe bene guardare l'SQL ActiveRecord generato e confrontarlo con gli indici.
A seconda del database su cui ti trovi potresti quindi eseguire quell'SQL attraverso un piano Explain (o qualunque strumento esista, sto pensando Oracle qui)
Per semplificare il tuo codice, puoi utilizzare has_and_belongs_to_many
pure. Ciò ti consentirebbe di eliminare l'oggetto CityPermission (a meno che tu non voglia utilizzarlo per archiviare i dati in sé)
Altri suggerimenti
Ecco l'SQL che ActiveRecord genera per user.cities
:
SELECT `cities`.* FROM `cities` INNER JOIN city_permissions ON (cities.id = city_permissions.city_id) WHERE (city_permissions.user_id = 1 )
ESPLORA i risultati di seguito:
+----+-------------+------------------+--------+---------------------------------------------------------------------+-----------------------------------+---------+-------------------------------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+--------+---------------------------------------------------------------------+-----------------------------------+---------+-------------------------------------------------+------+-------------+
| 1 | SIMPLE | city_permissions | ref | index_city_permissions_on_user_id,index_city_permissions_on_city_id | index_city_permissions_on_user_id | 5 | const | 1 | Using where |
| 1 | SIMPLE | cities | eq_ref | PRIMARY | PRIMARY | 4 | barhopolis_development.city_permissions.city_id | 1 | |
+----+-------------+------------------+--------+---------------------------------------------------------------------+-----------------------------------+---------+-------------------------------------------------+------+-------------+
Ed ecco l'SQL che ActiveRecord genera per user.city_permissions
:
SELECT * FROM `city_permissions` WHERE (`city_permissions`.user_id = 1)
Con i risultati EXPLAIN per quella query:
+----+-------------+------------------+------+-----------------------------------+-----------------------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+------+-----------------------------------+-----------------------------------+---------+-------+------+-------------+
| 1 | SIMPLE | city_permissions | ref | index_city_permissions_on_user_id | index_city_permissions_on_user_id | 5 | const | 1 | Using where |
+----+-------------+------------------+------+-----------------------------------+-----------------------------------+---------+-------+------+-------------+
Sembra che funzioni davvero correttamente . Dal manuale di MySQL:
eq_ref
Una riga viene letta da questa tabella per ogni combinazione di righe delle tabelle precedenti. Oltre al tipo di sistema e const, questo è il miglior tipo di join possibile. Viene utilizzato quando tutte le parti di un indice vengono utilizzate dal join e l'indice è un PRIMARY KEY o un indice UNIQUE.
ref
Tutte le righe con valori di indice corrispondenti vengono lette da questa tabella per ogni combinazione di righe delle tabelle precedenti. ref viene utilizzato se il join utilizza solo un prefisso più a sinistra della chiave o se la chiave non è un PRIMARY KEY o un indice UNIQUE (in altre parole, se il join non può selezionare una singola riga in base al valore della chiave). Se la chiave utilizzata corrisponde solo a poche righe, questo è un buon tipo di join.