MySQL не использует индексы при объединении с другой таблицей

11

У меня есть две таблицы, первая таблица содержит все статьи / сообщения в блоге в CMS. Некоторые из этих статей могут также появиться в журнале, и в этом случае они связаны внешним ключом с другой таблицей, которая содержит специфическую для журнала информацию.

Вот упрощенная версия синтаксиса создания таблицы для этих двух таблиц с некоторыми несущественными строками:

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;

CMS содержит около 250 000 статей, и я написал простой скрипт на Python, который можно использовать для заполнения тестовой базы данных образцами данных, если они хотят повторить эту проблему локально.

Если я выберу одну из этих таблиц, у MySQL не будет проблем с выбором подходящего индекса или быстрым поиском статей. Однако, когда две таблицы объединены в один простой запрос, такой как:

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

MySQL не может подобрать подходящий запрос и производительность резко падает. Вот соответствующее расширенное объяснение (время выполнения которого больше секунды):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • РЕДАКТИРОВАТЬ SEPT 30: я могу удалить WHEREпредложение из этого запроса, но оно EXPLAINвсе равно выглядит так же, и запрос все еще медленный.

Одним из возможных решений является форсирование индекса. Выполнение того же запроса с FORCE INDEX (base_articel_date_published)результатами приводит к запросу, который выполняется примерно за 1,6 миллисекунды.

+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
| 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    |             |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+

Я бы предпочел не использовать индекс по этому запросу, если я могу избежать его по нескольким причинам. В частности, этот базовый запрос может быть отфильтрован / изменен различными способами (например, фильтрацией по issue_slug), после чего он base_article_date_publishedможет перестать быть лучшим индексом для использования.

Кто-нибудь может предложить стратегию повышения производительности для этого запроса?

Joshmaker
источник
если столбец «is_published» содержит только два или три значения, вы действительно можете удалить этот индекс KEY base_article_is_published( is_published) .. мне кажется, это логический тип ..
Рэймонд Найлэнд
отредактировал ответ
Raymond Nijland

Ответы:

5

Что об этом, это должно устранить необходимость «Использование временного; Использование файловой сортировки», потому что данные уже в правильной сортировке.

Вам нужно знать хитрость, почему MySQL требует «Использование временного; Использование сортировки файлов», чтобы устранить эту потребность.

Смотрите второй sqlfriddle для объяснения об устранении необходимости

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

см. http://sqlfiddle.com/#!2/302710/2

Работает довольно хорошо, мне это тоже нужно было некоторое время назад, для таблиц стран / городов см. Демо здесь с примерами данных http://sqlfiddle.com/#!2/b34870/41

Отредактированный вы также можете захотеть проанализировать этот ответ, если base_article.is_published = 1 всегда возвращает 1 запись, как объяснено в вашем объяснении, и таблица доставки INNER JOIN может дать лучшую производительность, как запросы в ответе ниже

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

Раймонд Нейланд
источник
Спасительный ответ! Я использовал JOINтолько, но MySQL не собирал индекс. Большое спасибо Рэймонд
Максимус
4

РЕФАКТОР ЗАПРОСА

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;

или же

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;

ИЗМЕНИТЬ ВАШИ ИНДЕКСЫ

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

ДАЙТЕ ЭТО ПОПРОБУЙТЕ !!!

RolandoMySQLDBA
источник
Рефакторинг: Боюсь, не работает, потому что LIMIT 30находится в подзапросе (не все эти 30 строк также будут в mag_articlesтаблице). Если я перемещу LIMITвнешний запрос, производительность будет такой же, как в моем оригинале. Изменить индексы: MySQL также не использует этот индекс. Удаление WHEREпункта из моего исходного запроса, кажется, не имеет значения.
Джошмейкер
Второй метод рефакторинга сработал невероятно хорошо, время запроса было значительно сокращено с 8 секунд до 0,3 секунд в моей таблице ... спасибо, сэр!
andreszs