Tenho as seguintes tabelas:
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 a mesa:
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
o problema está na seguinte consulta:
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
aqui está a explicação:
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.
esta consulta demora muito para ser executada, alguma ideia de como melhorar?
Aqui está sua consulta original:
A primeira coisa que noto é que você está juntando duas tabelas inteiras. Como você só precisa de
twitter_username
eemail
dausers
tabela, você só deve ingressarusers
usando três colunasid
:twitter_username
eemail
.A segunda coisa é a
LIMIT
cláusula. Ele está sendo executado após a junção. Você deve executá-lo antes da junção. No seu caso, você está solicitando as 30 ações de usuário mais recentes. Se você puder garantir que apenas 30 linhas sejam recuperadas deuser_actions
, a junção deverá operar muito mais rapidamente.Se você ler a resposta de @DTest , seus dois primeiros marcadores já informam o que há de errado na consulta por causa das ações que o mysql executará na coleta de dados de cada tabela. A chave é entender como serão as tabelas temporárias enquanto a consulta está sendo processada e onde os dados residirão (memória ou disco).
O que você precisa fazer é refatorar a consulta para enganar o MySQL Query Optimizer. Force a consulta a produzir tabelas temporárias menores. Na maioria dos casos, as alterações de configuração em my.cnf devem fazer uma diferença dramática. Em outros casos, como este, refatorar a consulta pode ser suficiente.
Aqui está minha proposta de mudança para sua consulta que deve funcionar mais rápido:
Aqui estão os motivos para refatorar a consulta:
MOTIVO Nº 1
Se você olhar a tabela embutida
ua
, eu recupero apenas 30 linhas usandoLIMIT
. Isso acontecerá não importa o tamanho dauser_actions
mesa . Já está ordenado porque oORDER BY timestamp DESC
acontece antes doLIMIT
.MOTIVO Nº 2
Se você olhar a tabela inline ,
u
ela temid
,,, . O é necessário para implementar a junção.twitter_username
email
id
MOTIVO Nº 3
Eu uso
LEFT JOIN
em vez deINNER JOIN
por dois (2) motivos:ua
ua
não exista mais nasusers
tabelas.Fazer essas coisas forçará as tabelas temporárias a serem menores. No entanto, você ainda precisará implementar o marcador nº 3 da resposta do @DTest para evitar que as tabelas temporárias cheguem ao disco.
Bem, o principal problema é que, como sua consulta não possui nenhuma filtragem (sem
WHERE
instrução), ela coloca todas as linhas com colunasuser_actions.*, twitter_username, email
em uma tabela temporária para fazer a classificação.Então, a primeira coisa que eu faria é tentar limitar o número de linhas que vão para o seu conjunto de resultados. Por exemplo, eu diria adicionar um
WHERE timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
para obter apenas resultados nos últimos 7 dias (se isso for aceitável para o seu caso de uso).Em seguida, eu alteraria a consulta para obter apenas as colunas necessárias
user_actions
para reduzir a quantidade de informações necessárias para colocar em uma tabela temporária.Agora que você pode ou não ter removido linhas/colunas que precisam ser colocadas na tabela temporária para serem classificadas, vamos ver como o MySQL lida com tabelas temporárias. Da documentação sobre a
tmp_table_size
variável (ênfase adicionada):Primeiro, deixe-me apontar a ressalva representada pelo sobrescrito 1 : O tamanho da tabela temporária criada na memória é o mínimo de
tmp_table_size
oumax_heap_table_size
, portanto, se você aumentar um, certifique-se de aumentar o outro.Se a quantidade de seus dados exceder o tamanho mínimo dessas duas variáveis, eles serão colocados no disco. O disco está lento. Não faça disco se puder evitá-lo!
Para recapitular:
Limite a quantidade de linhas que você está classificando, usando
WHERE
. Mesmo que você esteja fazendo umLIMIT
, todas as linhas ainda estão sendo colocadas na tabela temporária para classificação.Limite o número de colunas que você está solicitando. Se você não precisa deles, não os peça.
Último recurso, aumente o tamanho de
tmp_table_size
emax_heap_table_size
se a consulta estiver aumentando suaCreated_tmp_disk_tables
variável de status. Além disso, não aumente isso drasticamente. Pode ter impacto no desempenho, dependendo do seu hardware e da quantidade de RAM que você possui no seu servidor.