MySQL não usa índices ao ingressar em outra tabela

11

Eu tenho duas tabelas, a primeira tabela contém todos os artigos / postagens de blog em um CMS. Alguns desses artigos também podem aparecer em uma revista; nesse caso, eles têm um relacionamento de chave estrangeira com outra tabela que contém informações específicas da revista.

Aqui está uma versão simplificada da sintaxe de criação de tabela para essas duas tabelas com algumas linhas não essenciais removidas:

CREATE TABLE `base_article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_published` datetime DEFAULT NULL,
  `title` varchar(255) NOT NULL,
  `description` text,
  `content` longtext,
  `is_published` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `base_article_date_published` (`date_published`),
  KEY `base_article_is_published` (`is_published`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `mag_article` (
    `basearticle_ptr_id` int(11) NOT NULL,
    `issue_slug` varchar(8) DEFAULT NULL,
    `rubric` varchar(75) DEFAULT NULL,
    PRIMARY KEY (`basearticle_ptr_id`),
    KEY `mag_article_issue_slug` (`issue_slug`),
    CONSTRAINT `basearticle_ptr_id_refs_id` FOREIGN KEY (`basearticle_ptr_id`) REFERENCES `base_article` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

O CMS contém cerca de 250.000 artigos no total e eu escrevi um script Python simples que pode ser usado para preencher um banco de dados de teste com dados de amostra, se eles quiserem replicar esse problema localmente.

Se eu selecionar uma dessas tabelas, o MySQL não terá problemas para escolher um índice apropriado ou recuperar artigos rapidamente. No entanto, quando as duas tabelas são unidas em uma consulta simples, como:

SELECT * FROM `base_article` 
INNER JOIN `mag_article` ON (`mag_article`.`basearticle_ptr_id` = `base_article`.`id`)
WHERE is_published = 1
ORDER BY `base_article`.`date_published` DESC
LIMIT 30

O MySQL falha ao escolher uma consulta e desempenho apropriados. Aqui está a explicação relevante estendida (cujo tempo de execução é superior a um segundo):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| id | select_type |    table     |  type  |           possible_keys           |   key   | key_len |                  ref                   | rows  | filtered |              Extra              |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
|  1 | SIMPLE      | mag_article  | ALL    | PRIMARY                           | NULL    | NULL    | NULL                                   | 23830 | 100.00   | Using temporary; Using filesort |
|  1 | SIMPLE      | base_article | eq_ref | PRIMARY,base_article_is_published | PRIMARY | 4       | my_test.mag_article.basearticle_ptr_id |     1 | 100.00   | Using where                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • EDITAR SETEMBRO 30: Posso remover a WHEREcláusula desta consulta, mas a EXPLAINaparência ainda é a mesma e a consulta ainda é lenta.

Uma solução potencial é forçar um índice. A execução da mesma consulta com FORCE INDEX (base_articel_date_published)resultados em uma consulta que é executada em cerca de 1,6 milissegundos.

+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
| id | select_type |    table     |  type  | possible_keys |             key             | key_len |           ref           | rows | filtered  |    Extra    |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
|  1 | SIMPLE      | base_article | index  | NULL          | base_article_date_published |       9 | NULL                    |   30 | 833396.69 | Using where |
|  1 | SIMPLE      | mag_article  | eq_ref | PRIMARY       | PRIMARY                     |       4 | my_test.base_article.id |    1 | 100.00    |             |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+

Eu preferiria não precisar forçar um índice nessa consulta, se eu puder evitá-la, por vários motivos. O mais notável é que essa consulta básica pode ser filtrada / modificada de várias maneiras (como a filtragem por issue_slug), após as quais base_article_date_publishedpode não ser mais o melhor índice a ser usado.

Alguém pode sugerir uma estratégia para melhorar o desempenho desta consulta?

Joshmaker
fonte
Se a coluna "is_published" só mantém dois ou três valores que você poderia realmente deixar cair esta chave de índice base_article_is_published( is_published) .. olha para mim, é um tipo de boolean ..
Raymond Nijland
editou a resposta
Raymond Nijland

Respostas:

5

Que tal isso deve remover a necessidade de um "Uso temporário; Usando arquivosort" porque os dados já estão na classificação correta.

Você precisa saber o truque porque o MySQL precisa "Usando temporário; Usando filesort" para remover essa necessidade.

Consulte o segundo sqlfriddle para obter uma explicação sobre como remover a necessidade

SELECT
      *
    FROM base_article

    STRAIGHT_JOIN 
      mag_article
    ON
      (mag_article.basearticle_ptr_id = base_article.id)

    WHERE
      base_article.is_published = 1

    ORDER BY
      base_article.date_published DESC

consulte http://sqlfiddle.com/#!2/302710/2

Funciona muito bem. Eu também precisava disso há algum tempo para tabelas de país / cidade. Veja a demonstração aqui com dados de exemplo http://sqlfiddle.com/#!2/b34870/41

Editado, você também pode querer analisar esta resposta se base_article.is_published = 1 sempre retornar 1 registro, como sua explicação explicou, uma tabela de entrega interna INNER JOIN pode oferecer melhor desempenho, como as consultas na resposta abaixo

/programming/18738483/mysql-slow-query-using-filesort/18774937#18774937

Raymond Nijland
fonte
Resposta para salvar vidas! Eu estava usando JOINapenas mas o MySQL não estava pegando o índice. Muito obrigado Raymond
Maximus
4

REFator A Consulta

SELECT * FROM
(SELECT * FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
INNER JOIN mag_article B
ON A.id = B.basearticle_ptr_id;

ou

SELECT B.*,C.* FROM
(SELECT id FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
LEFT JOIN base_article ON A.id = B.id
LEFT JOIN mag_article C ON B.id = C.basearticle_ptr_id;

MODIFIQUE SEUS ÍNDICES

ALTER TABLE base_article DROP INDEX base_article_is_published;
ALTER TABLE base_article ADD INDEX ispub_datepub_index (is_published,date_published);

DE UMA CHANCE !!!

RolandoMySQLDBA
fonte
Refatorador: não funciona, receio, porque LIMIT 30está na subconsulta (nem todas as 30 linhas também estarão na mag_articlestabela). Se eu mover a LIMITconsulta externa, o desempenho será o mesmo do original. Modificar índices: O MySQL também não usa esse índice. Remover a WHEREcláusula da minha consulta original não parece fazer diferença.
precisa saber é o seguinte
O segundo método de refatoração funcionou incrivelmente bem, o tempo de consulta foi reduzido drasticamente de 8 segundos para 0,3 segundos na minha tabela ... obrigado Senhor !!
andreszs