MySql - Come posso velocizzare questa query
-
16-10-2019 - |
Domanda
Ho le seguenti tabelle:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`account_data` text COLLATE utf8_unicode_ci,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`twitter_username` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`crypted_password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`password_salt` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`persistence_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`single_access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`perishable_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`login_count` int(11) NOT NULL DEFAULT '0',
`failed_login_count` int(11) NOT NULL DEFAULT '0',
`last_request_at` datetime DEFAULT NULL,
`current_login_at` datetime DEFAULT NULL,
`last_login_at` datetime DEFAULT NULL,
`current_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`last_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`is_admin` tinyint(1) DEFAULT '0',
`referrer_id` int(11) DEFAULT NULL,
`partner` tinyint(1) DEFAULT '0',
`subscription_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'free',
`workflow_state` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`persona_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `persona_index` (`persona_id`)
) ENGINE=InnoDB
e la tabella:
CREATE TABLE `user_actions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`action_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`module` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`data` text COLLATE utf8_unicode_ci,
`timestamp` datetime DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id_index` (`user_id`),
KEY `action_type_index` (`action_type`),
KEY `user_action_type_index` (`user_id`,`action_type`),
KEY `timestamp_index` (`timestamp`),
KEY `user_id_timestamp_index` (`user_id`,`timestamp`)
) ENGINE=InnoDB
il problema è con la seguente query:
SELECT user_actions.*, users.twitter_username, users.email FROM `user_actions`
INNER JOIN users ON (user_actions.user_id=users.id) ORDER BY timestamp DESC LIMIT 0, 30
qui è la spiega:
user_actions
The table was retrieved with this index: user_id_timestamp_index
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 76 rows of this table were scanned.
users
This table was retrieved with a full table scan, which is often quite bad for performance, unless you only retrieve a few rows.
The table was retrieved with this index:
No index was used in this part of the query.
A temporary table was created to access this part of the query, which can cause poor performance. This typically happens if the query contains GROUP BY and ORDER BY clauses that list columns differently.
MySQL had to do an extra pass to retrieve the rows in sorted order, which is a cause of poor performance but sometimes unavoidable.
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 3445 rows of this table were scanned.
questa query richiede molto tempo per l'esecuzione, tutte le idee su come migliorare?
Soluzione
Ecco la vostra query originale:
SELECT
user_actions.*,
users.twitter_username,
users.email
FROM
`user_actions`
INNER JOIN users
ON (user_actions.user_id=users.id)
ORDER BY timestamp
DESC LIMIT 0, 30
;
Per prima cosa che ho notato è che si stanno unendo due tabelle intere. Dal momento che avete solo bisogno twitter_username
e email
dalla tabella users
, si dovrebbe aderire solo dal users
utilizzando tre colonne:. id
, twitter_username
e email
La seconda cosa è la clausola LIMIT
. Viene eseguito dopo il join. Si dovrebbe eseguirlo prima del join. Nel tuo caso, si richiede per i 30 la maggior parte delle azioni dell'utente recenti. Se si può garantire che solo il 30 righe sono retreived dal user_actions
, l'unione dovrebbe operare molto più veloce.
Se Leggi la risposta da @DTest , i suoi primi due bulletpoints già dirvi che cosa c'è che non va la query a causa delle azioni mysql prenderanno nella raccolta di dati da ogni tabella. La chiave è capire che cosa le tabelle temporanee sarà simile mentre la query è in fase di elaborazione e dove i dati risiederanno (memoria o disco).
Quello che dovete fare è refactoring la query per ingannare il MySQL Query Optimizer. Forzare la query per produrre tabelle temporanee più piccoli. Nella maggior parte dei casi, le modifiche di configurazione in my.cnf dovrebbero fare la differenza dramamtic. In altri casi, come questo, refactoring l'interrogazione può essere sufficiente.
Ecco la mia proposta di modifica alla tua richiesta che dovrebbe funzionare più velocemente:
SELECT
ua.*,
u.twitter_username,
u.email
FROM
(SELECT * FROM `user_actions`
ORDER BY timestamp DESC LIMIT 30) ua
LEFT JOIN
(SELECT id,twitter_username,email FROM `users`) u
ON (ua.user_id=u.id)
;
Ecco le ragioni per il refactoring la query:
Motivo # 1
Se si guarda al tavolo in linea ua
, ho recuperare solo 30 righe utilizzando LIMIT
. Questo accadrà non importa quanto grande sia il tavolo user_actions
ottiene . E 'già ordinato perché la ORDER BY timestamp DESC
avviene prima che il LIMIT
.
Motivo # 2
Se si guarda da tavolo in linea u
, ha id
, twitter_username
, email
. Il id
è necessario per implementare il join.
Motivo # 3
Io uso LEFT JOIN
invece di INNER JOIN
per due (2) motivi:
- mantenere l'ordine della query basata su
ua
- Visualizza tutte le azioni dell'utente nel caso in cui l'user_id nella
ua
non esiste più nelle tabelleusers
.
Fare queste cose costringerà le tabelle temporanee ad essere più piccoli. Tuttavia, sarà ancora bisogno attuare bulletpoint # 3 da @ risposta di dtest per prevenire avere tabelle temporanee atterrano sul disco.
Altri suggerimenti
Bene il problema principale è che, poiché la query non ha alcun filtro su di esso (nessuna dichiarazione WHERE
), si pone tutte le righe con le colonne user_actions.*, twitter_username, email
in una tabella temporanea per fare l'ordinamento.
Quindi, la prima cosa che vorrei fare è tentare di limitare il numero di righe che vanno nella tua set di risultati. Ad esempio, vorrei dire l'aggiunta di un WHERE timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
per ottenere solo i risultati entro ultimi 7 giorni (se questo è accettabile per il tuo caso d'uso).
Avanti, vorrei modificare la query solo tirare le colonne necessarie da user_actions
per ridurre la quantità di informazioni necessarie per mettere in una tabella temporanea.
Ora che si può o non può aver rimosso le righe / colonne che devono essere collocati nella tabella temporanea da ordinare, diamo un'occhiata a come MySQL gestisce le tabelle temporanee. Dalla documentazione sulla variabile tmp_table_size
(enfasi aggiunta):
La dimensione massima di tabelle interne in memoria temporanea. (Il limite effettivo è determinato come il minimo di tmp_table_size e max_heap_table_size.) 1 Se una tabella temporanea in memoria supera il limite, MySQL converte automaticamente in un su disco MyISAM tavolo.
In primo luogo, vorrei sottolineare l'avvertenza rappresentata dal apice 1 : La dimensione della tabella temporanea creata in memoria è il minimo di una tmp_table_size
o max_heap_table_size
, quindi se si aumenta uno, assicuratevi per aumentare l'altra.
Se la quantità dei dati supera la dimensione del minimo di queste due variabili, verrà posizionato sul disco. Il disco è lenta. Non fare disco se si può evitare!
Per ricapitolare:
-
limitare la quantità di file che si sta utilizzando l'ordinamento su,
WHERE
. Anche se si sta facendo unLIMIT
, tutte le righe vengono ancora messi in tabella temporanea per tipo. -
limitare il numero di colonne si richiede. Se non ne hanno bisogno, non chiedere per loro.
-
Last resort, aumentare le dimensioni del
tmp_table_size
emax_heap_table_size
se la query è in aumento il tuoCreated_tmp_disk_tables
variabile di stato. Inoltre, non aumentare questo drasticamente. Si potrebbe avere impatto sulle prestazioni, a seconda dell'hardware e la quantità di RAM presente sul vostro server.