Sto lavorando a un progetto ( Rails 3.0.15, ruby 1.9.3-p125-perf ) in cui il db è in localhost e la tabella degli utenti ha un po 'più di 100K record .
utilizzando
ordina per RAND ()
è abbastanza lento
User.order ( "RAND (id)"). Prima
diventa
SELEZIONA users
. * users
DALL'ORDINE PER RAND (id) LIMIT 1
e impiega da 8 a 12 secondi per rispondere !!
Registro delle rotaie:
Carico utente (11030,8 ms) SELEZIONA users
. * DA users
ORDINE PER RAND () LIMIT 1
da mysql's spiega
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Si può vedere che non viene utilizzato alcun indice ( possible_keys = NULL ), viene creata una tabella temporanea ed è necessario un passaggio aggiuntivo per recuperare il valore desiderato ( extra = Uso temporaneo; Uso di filesort ).
D'altra parte, suddividendo la query in due parti e usando Ruby, abbiamo un ragionevole miglioramento dei tempi di risposta.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; zero per console)
Registro delle rotaie:
User Load (25.2ms) SELEZIONA ID DA users
User Load (0.2ms) SELEZIONA
users
. * DA users
DOVE users
. id
= 106854 LIMITE 1
e mysql spiega spiega perché:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 1 | SIMPLE | users | index | NULL | index_users_on_user_type | 2 | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
ora possiamo usare solo gli indici e la chiave primaria e fare il lavoro circa 500 volte più velocemente!
AGGIORNARE:
come sottolineato da icantbecool nei commenti, la soluzione di cui sopra presenta un difetto se nella tabella sono presenti record eliminati.
Una soluzione in questo può essere
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
che si traduce in due query
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
e funziona in circa 500ms.