MySQL Slow JOIN запрос при использовании ORDERBY - PullRequest
5 голосов
/ 12 марта 2019

У меня проблема с этим запросом:

SELECT a.*
FROM smartressort AS s
JOIN smartressort_to_ressort AS str
    ON s.id = str.smartressort_id
JOIN article_to_ressort AS atr
    ON str.ressort_id = atr.ressort_id
JOIN article AS a FORCE INDEX (source_created)
    ON atr.article_id = a.id    
WHERE
    s.id = 1
ORDER BY
    a.created_at DESC
LIMIT 25;

Это очень медленно, иногда это занимает 14 секунд.

ОБЪЯСНИТЕ показать это:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   1   Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   ALL NULL    NULL    NULL    NULL    146677  Using where; Using join buffer (flat, BNL join)

так что последний тип "все" действительно плох. Но я уже пытался использовать индекс безуспешно.

Таблица статей выглядит следующим образом:

CREATE TABLE `article` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`node_id` varchar(255) NOT NULL DEFAULT '',
`object_id` varchar(255) DEFAULT NULL,
`headline_1` varchar(255) NOT NULL DEFAULT '',
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
`teaser_text` longtext NOT NULL,
`content_text` longtext NOT NULL,
PRIMARY KEY (`id`),
KEY `article_nodeid` (`node_id`),
KEY `article_objectid` (`object_id`),
KEY `source_created` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=161116 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

Когда я удаляю FORCE INDEX, Объяснение улучшается, но запрос все еще медленный.

Объяснить без индекса силы:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   1   Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   eq_ref  PRIMARY PRIMARY 4   com.nps.lvz-prod.atr.article_id 1   

А для другого идентификатора smartressort (3) это выглядит так:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   13  Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   eq_ref  PRIMARY PRIMARY 4   com.nps.lvz-prod.atr.article_id 1   

Здесь у нас есть 13 Ressorts для одного Smartressort. Строки: 1x1x13x1262x1 = 16,406

1) Что я могу сделать, чтобы сделать этот запрос быстрее?

2) Что не так с индексом source_created?

Ответы [ 5 ]

4 голосов
/ 12 марта 2019

Значение SELECT *, которое у вас есть в запросе, ужасно, и это часто может быть убийцей индекса. Это может исключить использование индекса, поскольку большинство определяемых вами индексов не будут охватывать каждый столбец, требуемый SELECT *. Подход этого ответа состоит в том, чтобы проиндексировать все другие таблицы в вашем запросе, что, таким образом, стимулировало бы MySQL просто выполнить одно сканирование таблицы article.

CREATE INDEX idx1 ON article_to_ressort (article_id, ressort_id);
CREATE INDEX idx2 ON smartressort_to_ressort (ressort_id, smartressort_id);

Эти два индекса должны ускорить процесс присоединения. Обратите внимание, что я не определил индекс для таблицы smartressort, предполагая, что ее столбец id уже является первичным ключом. Я бы, вероятно, написал ваш запрос, начиная с таблицы article и соединяя ее снаружи, но это не должно иметь большого значения.

Кроме того, форсирование индекса в большинстве случаев является либо плохой идеей, либо не обязательным. Оптимизатор обычно может определить, когда лучше всего использовать индекс.

2 голосов
/ 12 марта 2019

SELECT many columns FROM tables ORDER BY something LIMIT few - известный антипаттерн производительности; он должен извлечь и упорядочить весь беспорядок строк и столбцов, просто чтобы отбросить все, кроме нескольких строк из набора результатов.

Хитрость заключается в том, чтобы выяснить, какие значения article.id вам нужны в наборе результатов, а затем получить только эти значения. Это называется отложенное соединение .

Это должно дать вам этот набор id значений. Вероятно, нет необходимости присоединяться к таблице smartressort, поскольку smartressort_to_ressort содержит необходимые значения id.

                 SELECT a.id
                   FROM article a
                   JOIN article_to_ressort atr ON a.id = atr.article_id
                   JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id
                  WHERE str.smartressort_id = 1
                  ORDER BY a.created_at DESC
                  LIMIT 25

Затем вы можете использовать это как подзапрос, чтобы получить нужные вам строки.

SELECT a.*
  FROM article a
 WHERE a.id IN (
                 SELECT a.id
                   FROM article a
                   JOIN article_to_ressort atr ON a.id = atr.article_id
                   JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id
                  WHERE str.smartressort_id = 1
                  ORDER BY a.created_at DESC
                  LIMIT 25
               )
 ORDER BY a.created_at DESC

Второй ORDER BY гарантирует, что строки из статьи находятся в предсказуемом порядке. Таким образом, ваша работа по оптимизации индекса должна применяться только к подзапросу.

0 голосов
/ 12 марта 2019

Так что для меня решение было так:

SELECT a.*
FROM article as a  USE INDEX (source_created)
where a.id in (
             SELECT atr.article_id
               from smartressort_to_ressort str 
               JOIN article_to_ressort atr  ON atr.ressort_id = str.ressort_id
              WHERE str.smartressort_id = 1
) 
ORDER BY a.created_at DESC
LIMIT 25;

Для этого нужно всего ~ 35 мс. Объясните это выглядит так:

1   PRIMARY a   index   NULL    source_created  7   NULL    1   
1   PRIMARY <subquery2> eq_ref  distinct_key    distinct_key    4   func    1
2   MATERIALIZED    str ref PRIMARY,ressort_id,idx1 PRIMARY 4   const   1   Using index
2   MATERIALIZED    atr ref PRIMARY,article_id,idx2 PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index

Несмотря на это, этот запрос Объяснение выглядит лучше для меня, но я не знаю, почему именно:

explain SELECT a.*, NOW()
FROM article as a  USE INDEX (source_created)
where a.id in (SELECT atr.article_id
    FROM smartressort AS s
    JOIN smartressort_to_ressort AS str
    ON s.id = str.smartressort_id
    JOIN article_to_ressort AS atr
    ON str.ressort_id = atr.ressort_id
    WHERE s.id = 1
) 
ORDER BY a.created_at DESC
LIMIT 25;

Выход:

1   PRIMARY s   const   PRIMARY PRIMARY 4   const   1   Using index
1   PRIMARY a   index   NULL    source_created  7   NULL    25  
1   PRIMARY str ref PRIMARY,ressort_id,idx1 PRIMARY 4   const   1   Using index
1   PRIMARY atr eq_ref  PRIMARY,article_id,idx2 PRIMARY 8   com.nps.lvz-prod.str.ressort_id,com.nps.lvz-prod.a.id   1   Using index; FirstMatch(a)
0 голосов
/ 12 марта 2019

Для начала: Вы можете удалить таблицу smartressort из вашего запроса, поскольку она ничего не добавляет к нему.

Ниже ваш запрос переписан.Мы хотим, чтобы все рессорты для умного рессорта №1, а затем все статьи для этих рессортов.Из них мы покажем самые новые 25.

SELECT *
FROM article
WHERE id IN
(
  SELECT article_id
  FROM article_to_ressort 
  WHERE ressort_id IN
  (
    SELECT ressort_id
    FROM smartressort_to_ressort
    WHERE smartressort_id = 1
  )
)
ORDER BY created_at DESC
LIMIT 25;

Какие индексы понадобятся, чтобы помочь СУБД в этом?Начните с внутренней таблицы (smartressort_to_ressort).Мы получаем доступ ко всем записям с указанным smartressort_id и хотим получить ассоциированное ressort_id.Таким образом, индекс должен содержать эти два столбца в этом порядке.То же самое для article_to_ressort и его ressort_id и article_id.Наконец, мы хотим выбрать статьи по найденным идентификаторам статей и упорядочить их по created_at.

CREATE INDEX idx1 ON smartressort_to_ressort (smartressort_id, ressort_id);
CREATE INDEX idx2 ON article_to_ressort (ressort_id, article_id);
CREATE INDEX idx3 ON article (id, created_at);

В любом случае, эти индексы являются просто предложением для СУБД.Это может решить против них.Это особенно верно для индекса в таблице article.Сколько строк ожидает доступ к СУБД для одного smartressort_id, т.е. сколько строк может быть в предложении IN?Если СУБД считает, что это может составлять около 10% всех идентификаторов статей, она может уже решить, что лучше читать таблицу последовательно, а не путаться в индексе для стольких строк.

0 голосов
/ 12 марта 2019

В дополнение к отличному ответу @TimBiegelsen, я бы порекомендовал изменить ваш source_created индекс:

...
KEY `source_created` (`id`, `created_at`)

Преимущество будет в том, что MySQL сможет использовать его для сортировки, и ему не нужно будет извлекать все 16406 строк. Это может или не может помочь, но стоит попробовать (возможно, с явной декларацией использовать его)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...