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?

È stato utile?

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:

  1. mantenere l'ordine della query basata su ua
  2. Visualizza tutte le azioni dell'utente nel caso in cui l'user_id nella ua non esiste più nelle tabelle users.

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 un LIMIT, 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 e max_heap_table_size se la query è in aumento il tuo Created_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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top