2009-10-20 10 views
9

Quindi ho una tabella con oltre 80.000 record, questo è chiamato sistema. Ho anche un altro tavolo chiamato segue.Ottimizzazione della mia istruzione mysql! - RAND() TOO SLOW

Ho bisogno che la mia istruzione selezioni casualmente i record dalla tabella di sistema, dove quell'id non è già elencato nella tabella seguente sotto l'id utente corrente.

Così qui è quello che ho:

SELECT system.id, 
      system.username, 
      system.password, 
      system.followed, 
      system.isvalid, 
      follows.userid, 
      follows.systemid 
     FROM system 
    LEFT JOIN follows ON system.id = follows.systemid 
        AND follows.userid = 2 
     WHERE system.followed = 0 
     AND system.isvalid = 1 
     AND follows.systemid IS NULL 
    ORDER BY RAND() 
     LIMIT 200 

Ora wotks perfettamente, se non che ci vuole circa un minuto intero prima che possa anche iniziare l'elaborazione del lavoro a portata di mano con i record essa scelto. A questo punto la sceneggiatura di solito dura e non succede nulla.

Qualcuno può mostrarmi come rielaborare questo, quindi la stessa idea è stata fatta, ma non sta usando l'ordine di Rand? Questo sembra rallentare le cose un bel po '.

Grazie!

+1

Quali indici hai nei tuoi campi JOIN? Quello può essere un grande collo di bottiglia. – dnagirl

+0

Non sono troppo sicuro di cosa intendi ... – Brandon

+0

@Brandon so che è un po 'tardi per questo, ma se ti piacerebbe un modo semi-semplicistico di farlo puoi semplicemente metterlo in una subquery ... vedi la mia risposta qui per maggiori dettagli http://stackoverflow.com/questions/25361158/mysql-select-random-on-large-table-order-by-score/25364339?noredirect=1#comment39644652_25364339 –

risposta

7

Non sono sicuro che esista una soluzione semplice per sostituire la query, ecco un articolo sulla correzione di questo tipo di problema.

http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

+0

Grazie, ma questa non è un'opzione praticabile per il modo questa query funziona. – Brandon

+0

Perché no? Ci sono molte soluzioni diverse in quell'articolo, alcune delle quali penso che potrebbero funzionare per te. Il tuo campo ID è un campo autoincrement?se è così, la soluzione per la selezione di ID casuali dovrebbe funzionare. –

2

È possibile generare un certo valore casuale pseudo sulla base dei ids e l'ora corrente:

ORDER BY 37*(UNIX_TIMESTAMP()^system.id) & 0xffff 

si mescoleranno morsi dalla id, e poi prenderà solo il più basso 16.

+0

Sembra essere altrettanto lento ... – Brandon

2

ci sono due ragioni principali per la lentezza:

  • SQL devono prima emettere un numero casuale per ciascuna delle righe
  • Le righe devono poi essere ordinate in base a questo numero per selezionare i migliori 200 quelli

C'è un trucco per aiutare questa situazione, richiede un po 'di il lavoro di preparazione e il modo di implementarlo (e il relativo interesse) dipendono dal tuo caso d'uso reale.

==> Introdurre una colonna in più con un valore di "categoria casuale" per filtrare-out maggior parte dei file

L'idea è quella di avere una colonna di valori interi con valori assegnati in modo casuale, una volta in fase di preparazione, con una valore compreso tra 0 e 9 (o 1 e 25 ... qualunque). Questa colonna deve quindi essere aggiunta all'indice utilizzato nella query. Infine, modificando la query per includere un filtro in questa colonna = un valore particolare (ad esempio 3), il numero di righe che SQL deve gestire viene quindi ridotto di 10 (o 25, in base al numero di valori distinti che abbiamo in categoria "casuale".

Assumendo questa nuova rubrica si chiama RandPreFilter, potremmo introdotto un indice come

CREATE [UNIQUE ?] INDEX 
ON system (id, RandPreFilter) 

e modificare la query come segue

SELECT system.id 
    , system.username 
    , system.password 
    , system.followed 
    , system.isvalid 
    , follows.userid 
    , follows.systemid 
FROM system 
LEFT JOIN follows ON system.id = follows.systemid 
    AND follows.userid = 2 
WHERE system.followed=0 AND system.isvalid=1 
    AND follows.systemid IS NULL 

    AND RandPreFilter = 1 -- or other numbers, or possibly 
     -- FLOOR(1 + RAND() * 25) 
ORDER BY RAND() 
LIMIT 200 
5

la ragione la query è lenta è che il database deve mantenere una rappresentazione di tutti i valori casuali generati e i loro rispettivi dati prima che possa restituire anche una singola riga dal database.È possibile limitare il numero di righe candidate da considerare prima utilizzando WHERE RAND() < x, in cui si seleziona x come numero che probabilmente restituirà almeno il numero di campioni necessario. Per ottenere un campione casuale vero bisognerebbe quindi ordinare di nuovo da RAND o eseguire il campionamento sul set di dati restituito.

L'utilizzo di questo approccio consente al database di elaborare la query in modalità streaming senza dover creare una rappresentazione intermedia di tutti i dati. Lo svantaggio è che non puoi mai essere sicuro al 100% di ottenere il numero di campioni necessario, quindi potresti dover ripetere la query fino a quando non lo fai, convivere con un set di campioni più piccolo o aggiungere campioni in modo incrementale (assicurandoti di evitare duplicati) finché non si ottiene il numero di campioni necessari.

Se non si richiede alla query di restituire risultati diversi per ogni chiamata, è inoltre possibile aggiungere una colonna di valori casuali pre-generata con un indice e combinare con la tecnica di cui sopra. Ti consentirebbe di ottenere un numero qualsiasi di campioni in maniera equa, anche se aggiungi o cancelli le righe, ma la stessa query sugli stessi dati ovviamente restituirebbe lo stesso set di risultati.

1

A seconda della casualità dei dati, potrebbe valere la pena di ordinare i dati e aggiungere una colonna "datetime" utilizzata per ultimo e aggiornarla una volta utilizzati i dati. Quindi selezionare l'ordinamento delle prime n righe in base all'ultimo campo utilizzato in ordine decrescente.

Se lo si avvolge in una dichiarazione preparata, è possibile selezionare un risultato (semi) casuale alla volta senza preoccuparsi della logica.

In alternativa, assegnare ad ogni riga un ID sequenziale e generare la casualità nel codice e richiamare solo le righe richieste. Il problema è che il recordset completo viene restituito prima di essere ordinato.

0

Forse un po 'tardi, ma almeno qui è una soluzione in più per il futuro considerazione:

SELECT minSystem.id, 
    minSystem.username, 
    minSystem.password, 
    minSystem.followed, 
    minSystem.isvalid, 
    randFollows.userid, 
    randFollows.systemid 
FROM 
(
    SELECT * 
    FROM system 
    WHERE system.followed = 0 AND system.isvalid = 1 
) as minSystem 
LEFT JOIN 
(
    SELECT * 
    FROM (
     SELECT * 
     FROM follows 
     WHERE follows.systemid IS NULL 
    ) as minFollows 
    WHERE rand() <= 200 * 1.5/(SELECT count(*) FROM follows WHERE systemid IS NULL) 
) as randFollows 
ON minSystem.id = randFollows.systemid 
LIMIT 200 

In primo luogo, eseguiamo una selezione sulla tabella di sistema per ridurre la dimensione tabella temporanea minSystem e minFollow. Quindi selezioniamo le righe casuali dalla tabella minFollows attraverso la probabilità calcolata. A questo punto avremo una tabella randFollows abbastanza casuale da SINISTRA UNIRE con minSystem. Infine eseguiamo LIMIT 200.

Se si utilizza MyISam, è possibile recuperare semplicemente le dimensioni della tabella. Ciò elimina la sottoquery aggiuntiva per calcolare la dimensione della tabella follows. In alternativa, è anche possibile codificare a secco il denominatore se le dimensioni della tabella non aumentano troppo velocemente (tuttavia è necessaria una maggiore manutenzione manuale).

Per ulteriori spiegazione approfondita, si prega di checkout la soluzione che ho postato su: MySQL: Alternatives to ORDER BY RAND()

Spero che questo aiuti (o almeno spero troverete interessante questo)!