2009-04-03 17 views
34

Ho una tabella con circa 100.000 post di blog, collegata a una tabella con 50 feed tramite una relazione 1: n. Quando eseguo una query su entrambe le tabelle con un'istruzione select, ordinata da un campo datetime della tabella dei post, MySQL utilizza sempre filesort, risultando in tempi di query molto lenti (> 1 secondo). Ecco lo schema della tabella postings (semplificato):Ottimizzazione delle prestazioni MySQL: ordine per data/ora

+---------------------+--------------+------+-----+---------+----------------+ 
| Field    | Type   | Null | Key | Default | Extra   | 
+---------------------+--------------+------+-----+---------+----------------+ 
| id     | int(11)  | NO | PRI | NULL | auto_increment | 
| feed_id    | int(11)  | NO | MUL | NULL |    | 
| crawl_date   | datetime  | NO |  | NULL |    | 
| is_active   | tinyint(1) | NO | MUL | 0  |    | 
| link    | varchar(255) | NO | MUL | NULL |    | 
| author    | varchar(255) | NO |  | NULL |    | 
| title    | varchar(255) | NO |  | NULL |    | 
| excerpt    | text   | NO |  | NULL |    | 
| long_excerpt  | text   | NO |  | NULL |    | 
| user_offtopic_count | int(11)  | NO | MUL | 0  |    | 
+---------------------+--------------+------+-----+---------+----------------+ 

Ed ecco il tavolo feed:

+-------------+--------------+------+-----+---------+----------------+ 
| Field  | Type   | Null | Key | Default | Extra   | 
+-------------+--------------+------+-----+---------+----------------+ 
| id   | int(11)  | NO | PRI | NULL | auto_increment | 
| type  | int(11)  | NO | MUL | 0  |    | 
| title  | varchar(255) | NO |  | NULL |    | 
| website  | varchar(255) | NO |  | NULL |    | 
| url   | varchar(255) | NO |  | NULL |    | 
+-------------+--------------+------+-----+---------+----------------+ 

Ed ecco la domanda che prende> 1 secondo per eseguire. Si prega di notare che il campo post_date ha un indice, ma MySQL non è utilizzarlo per ordinare la tabella messaggi:

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    (`postings`) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

Il risultato del comando explain extended su questa query mostra che MySQL sta usando filesort:

+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 
| id | select_type | table | type | possible_keys       | key  | key_len | ref      | rows | Extra      | 
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 
| 1 | SIMPLE  | postings | ref | feed_id,is_active,user_offtopic_count | is_active | 1  | const     | 30996 | Using where; Using filesort | 
| 1 | SIMPLE  | feeds | eq_ref | PRIMARY,type       | PRIMARY | 4  | feedian.postings.feed_id |  1 | Using where     | 
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 

Quando rimuovo la parte order by, MySQL smette di utilizzare filesort. Per favore fatemi sapere se avete qualche idea su come ottimizzare questa query per ottenere MySQL per ordinare e selezionare i dati utilizzando gli indici. Ho già provato alcune cose, come la creazione di un indice combinato su tutti i campi in cui ordinare/per campo, come suggerito da alcuni post di blog, ma anche questo non ha funzionato.

+7

Mi piace molto il modo chiaro e dettagliato che hai chiesto. – tpdi

risposta

34

Creare un indice composito su postings (is_active, post_date) (in questo ordine).

Verrà utilizzato sia per il filtraggio in is_active che per l'ordine post_date.

MySQL deve mostrare il metodo di accesso REF su questo indice in EXPLAIN EXTENDED.

Si noti che si ha una condizione di filtro RANGE su user_offtopic_count, ecco perché non è possibile utilizzare un indice su questo campo sia nel filtraggio che nell'ordinamento in altro campo.

A seconda di come selettiva è la vostra user_offtopic_count (i. E. Il numero di righe soddisfano user_offtopic_count < 10), può essere più utile per creare un indice su user_offtopic_count e lasciare che i post_dates essere ordinati.

Per fare ciò, creare un indice composito su postings (is_active, user_offtopic_count) e assicurarsi che sia utilizzato il metodo di accesso RANGE su questo indice.

Quale indice sarà più veloce dipende dalla distribuzione dei dati. Creare entrambi gli indici, FORCE loro e vedere che è più veloce:

CREATE INDEX ix_active_offtopic ON postings (is_active, user_offtopic_count); 
CREATE INDEX ix_active_date ON postings (is_active, post_date); 

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    `postings` FORCE INDEX (ix_active_offtopic) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

/* This should show RANGE access with few rows and keep the FILESORT */ 

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    `postings` FORCE INDEX (ix_active_date) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

/* This should show REF access with lots of rows and no FILESORT */ 
+0

Questo ha fatto il trucco per me, grazie mille! Ho dovuto usare l'indice di forza per ottenere il miglior indice utilizzato. Ora utilizziamo più indici combinati per le diverse query. –

3

MySQL ha due algoritmi filesort: un vecchio fileort che ordina i record sul disco e una nuova versione che funziona in memoria.

Se non è possibile utilizzare un indice sulla prima tabella del join per ordinare la query, dovrà eseguire un fileort. Se il set di risultati prima dell'ordinamento convertito in formato a larghezza fissa è maggiore del buffer di ordinamento O se contiene campi di testo, dovrà utilizzare l'algoritmo filesort su disco più lento (la seconda condizione è soddisfatta poiché la query ha un testo campo).

MySQL sta scegliendo di utilizzare la colonna is_active, apparentemente perché ritiene che la colonna sia più selettiva nell'eliminare le righe prima che continui con gli altri join e le condizioni. La prima cosa che suggerirei sarebbe provare a creare indici compositi con post_date, feed_id e le colonne nella condizione where, ad es. (è_attivo, user_offtopic_count, post_date, feed_id).

+0

Grazie per la spiegazione! –

3

Inoltre, è importante ricordare che MySQL non utilizzare un indice, se la colonna si ordinano da ha una funzione applicata ad esso.

Si dovrebbe anche provare aliasing postings.post_date come qualcos'altro. Questo dirà a MySQL di ordinare dalla colonna inalterata, e dovrai comunque selezionare la data e l'ora di unix.

Problemi correlati