Запрос MySQL с JOIN без использования INDEX

У меня есть следующие две таблицы в MySQL (упрощенный).

  • clicks (InnoDB)
    • Contains around about 70,000,000 records
    • Имеет индекс в столбце date_added
    • Имеет столбец link_id, который ссылается на запись в таблице links
  • links (MyISAM)
    • Contains far fewer records, around about 65,000

Я пытаюсь выполнить некоторые аналитические запросы, используя эти таблицы. Мне нужно получить некоторые данные о кликах, которые произошли в течение двух указанных дат, при применении некоторых других выбранных пользователем фильтров с использованием других таблиц и объединении их в таблицу ссылок.

Однако мой вопрос вращается вокруг использования индексов. Когда я запускаю следующий запрос:

SELECT
    COUNT(1)
FROM
    clicks
WHERE
    date_added >= '2016-11-01 00:00:00'
AND date_added <= '2016-11-03 23:59:59';

Я получаю ответ через 1,40 секунды. Используя EXPLAIN, я обнаружил, что MySQL использует индекс столбца date_added, как и ожидалось.

EXPLAIN SELECT COUNT(1) FROM clicks WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-11-16 23:59:59';
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+
| id | select_type | table  | type  | possible_keys | key        | key_len | ref  | rows    | Extra                    |
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+
|  1 | SIMPLE      | clicks | range | date_added    | date_added | 4       | NULL | 1559288 | Using where; Using index |
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+

Однако, когда я LEFT JOIN в своей таблице links обнаружил, что выполнение запроса занимает гораздо больше времени:

SELECT
    COUNT(1) AS clicks
FROM
    clicks AS c
LEFT JOIN links AS l ON l.id = c.link_id
WHERE
    c.date_added >= '2016-11-01 00:00:00'
AND c.date_added <= '2016-11-16 23:59:59';

Который завершился за 6,50 сек. Используя EXPLAIN, я обнаружил, что индекс не использовался для столбца date_added:

EXPLAIN SELECT COUNT(1) AS clicks FROM clicks AS c LEFT JOIN links AS l ON l.id = c.link_id WHERE c.date_added >= '2016-11-01 00:00:00' AND c.date_added <= '2016-11-16 23:59:59';
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+
| id | select_type | table | type   | possible_keys | key        | key_len | ref           | rows    | Extra       |
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+
|  1 | SIMPLE      | c     | range  | date_added    | date_added | 4       | NULL          | 6613278 | Using where |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY       | PRIMARY    | 4       | c.link_id     |       1 | Using index |
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+

Как видите, индекс не используется для столбца date_added в большей таблице и, кажется, занимает гораздо больше времени. Кажется, это становится еще хуже, когда я присоединяюсь к другим столам.

Кто-нибудь знает, почему это происходит, или я могу что-нибудь сделать, чтобы заставить его использовать индекс в столбце date_added в таблице кликов?


Изменить

Я только что попытался получить свою статистику из базы данных, используя другой метод. Первый шаг в моем методе заключается в извлечении определенного набора link_ids из таблицы кликов. Я обнаружил, что снова вижу ту же проблему, без JOIN. Индекс не используется:

Мой запрос:

SELECT
    DISTINCT(link_id) AS link_id
FROM
    clicks
WHERE
    date_added >= '2016-11-01 00:00:00'
AND date_added <= '2016-12-05 10:16:00'

Этот запрос занял почти минуту. Я выполнил EXPLAIN для этого и обнаружил, что запрос не использует индекс, как я ожидал:

+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows     | Extra       |
+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+
|  1 | SIMPLE      | clicks  | index | date_added    | link_id  | 4       | NULL | 79786609 | Using where |
+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+

Я ожидал, что он будет использовать индекс date_added для фильтрации результирующего набора, а затем извлечет отдельные значения link_id. Любая идея, почему это происходит? У меня есть индекс link_id, а также date_added.


person Jonathon    schedule 02.12.2016    source источник
comment
пожалуйста, добавьте index для link_id в таблицы кликов, что может помочь сократить время вывода запроса   -  person Suman EStatic    schedule 02.12.2016
comment
@SumanEStatic - INDEX(link_id) не поможет.   -  person Rick James    schedule 03.12.2016
comment
Похоже, вы используете MyISAM. Пожалуйста, предоставьте SHOW CREATE TABLE.   -  person Rick James    schedule 03.12.2016
comment
@RickJames Я использую InnoDB для своей таблицы clicks и MyISAM для своей таблицы links.   -  person Jonathon    schedule 05.12.2016
comment
@ Джонатон, до сих пор не заметил, но что??? ваша таблица кликов и таблица ссылок находятся на разных движках? это действительно странно, и я сомневаюсь, что проблема здесь.   -  person Rahul    schedule 05.12.2016
comment
@Rahul Да, я не могу вспомнить, почему, но некоторое время назад было принято решение переключить таблицу кликов с MyISAM на InnoDB. Я думаю, это потому, что InnoDB лучше подходит для частых вставок и избегает блокировки таблицы, что больше подходит для таблицы кликов. Было бы относительно безболезненно переключиться на InnoDB в моей таблице ссылок, если вы думаете, что это может помочь?   -  person Jonathon    schedule 05.12.2016
comment
@Jonathon, я не уверен, что схожу с ума, но я бы провел небольшой тест и подтвердил то же самое, если бы я был тобой :)   -  person Rahul    schedule 05.12.2016
comment
Объяснение показывает, что индекс date_added использовался даже в случае 2-го запроса. То, что ref равно нулю, не означает, что индекс не использовался. На самом деле, это совершенно нормально для индекса, который используется для поиска диапазона.   -  person Shadow    schedule 05.12.2016
comment
Смешивание двигателей в порядке; это было основным изменением в версии 4.1. Сегодня InnoDB является почти повсеместно предпочтительным движком. Редко есть какое-либо оправдание для использования MyISAM. MyISAM будет удален в версии 8.0.   -  person Rick James    schedule 05.12.2016


Ответы (2)


Не совсем уверен, но подумайте о том, чтобы переместить условие из условия WHERE в условие JOIN ON, поскольку вы выполняете внешнее соединение (LEFT JOIN), это имеет значение в производительности, в отличие от inner join, где условие, будь то в предложении where или join on, эквивалентно.

SELECT COUNT(1) AS clicks 
FROM clicks AS c 
LEFT JOIN links AS l ON l.id = c.link_id 
AND (c.date_added >= '2016-11-01 00:00:00' 
AND c.date_added <= '2016-11-16 23:59:59');
person Rahul    schedule 02.12.2016
comment
Спасибо за Ваш ответ. Я попытался переместить условия из предложения WHERE в JOIN ON, как было предложено, но я все еще вижу ту же проблему. - person Jonathon; 02.12.2016
comment
Используйте ON, чтобы сказать, как связаны таблицы; используйте WHERE для фильтрации. Оптимизатор может обрабатывать их одинаково. Это видно из EXPLAIN EXTENDED SELECT ...; SHOW WARNINGS; - person Rick James; 03.12.2016
comment
@Rahul Я отредактировал свой вопрос, указав что-то еще, что я нашел, что потенциально может помочь диагностировать проблему. Спасибо :) - person Jonathon; 05.12.2016

Вы хотите использовать обычный JOIN вместо LEFT JOIN? LEFT JOIN сохраняет все строки справа, поэтому оно даст то же значение COUNT(), что и несвязанная таблица. Если вы хотите подсчитать только те строки из правой таблицы, которым соответствуют строки в левой таблице, используйте JOIN, а не LEFT JOIN.

Попробуйте удалить свой индекс на date_added и заменить его составным индексом на (date_added, link_id). Такой тип индекса называется индексом покрытияx. Когда планировщик запросов знает, что он может получить все, что ему нужно, из индекса, ему не нужно возвращаться к таблице. В этом случае планировщик запросов может произвольно получить доступ к индексу до начала вашего диапазона дат, а затем выполнить сканировать диапазон индекса до конца диапазона. Тем не менее, ему все равно придется ссылаться на другую таблицу.

(Изменить) В целях эксперимента попробуйте более узкий диапазон дат. Посмотрите, изменится ли EXPLAIN. В этом случае планировщик запросов может неправильно угадать кардинальность вашего столбца date_added.

Вы можете попробовать использовать подсказку индекса. Например, попробуйте

SELECT COUNT(1) AS clicks
  FROM clicks AS c USE INDEX (date_added)
  LEFT JOIN links AS l ON l.id = c.link_id
 WHERE etc

Но, судя по вашему выводу EXPLAIN, вы уже выполняете сканирование диапазона на date_added. Ваш следующий шаг, нравится вам это или нет, — составной индекс покрытия.

Убедитесь, что на links(id) есть индекс. Вероятно, потому что это, вероятно, ПК.

Попробуйте использовать COUNT(*) вместо COUNT(1). Вероятно, это не будет иметь никакого значения, но стоит попробовать. COUNT(*) просто подсчитывает строки, а не оценивает что-то для каждой подсчитываемой строки.

(Nitpick) Ваш диапазон дат забавно пахнет. Используйте < в конце вашего диапазона для достижения наилучших результатов, например так.

 WHERE c.date_added >= '2016-11-01'
   AND c.date_added <  '2016-11-17';

Правка: смотрите, планировщик запросов MySQL использует много внутренних знаний о том, как структурированы таблицы. Кроме того, по состоянию на конец 2016 года он может использовать только один индекс для каждой таблицы. Это ограничение.

SELECT DISTINCT column на самом деле является довольно сложным запросом, потому что он должен устранить дублирование рассматриваемого column. Если в этом столбце есть индекс, планировщик запросов, скорее всего, будет его использовать. Выбор этого индекса означает, что он не может выбрать какой-либо другой индекс.

Составные индексы (покрывающие индексы) иногда, но не всегда, разрешают такую ​​дилемму выбора индекса и позволяют использовать двойное использование индекса. Вы можете прочитать обо всем этом на http://use-the-index-luke.com/

Но если ваши операционные ограничения не позволяют добавлять составные индексы, вам придется смириться с односекундным запросом. Это не так уж плохо.

Конечно, заявление о том, что вы не можете добавлять составные индексы для выполнения своей работы, звучит примерно так:

A: из моего грузовика на автостраде что-то падает.

B: накройте вещи брезентом и привяжите их.

A: мой босс не позволяет мне накрыть грузовик брезентом.

Б: ну тогда езжай помедленнее.

person O. Jones    schedule 02.12.2016
comment
Спасибо за Ваш ответ. Я пробовал использовать JOIN вместо LEFT JOIN безуспешно, я также пробовал COUNT(*) безрезультатно. Я могу подтвердить, что links(id) является первичным ключом, а столбец clicks(link_id) также индексируется. В данный момент я не хочу вносить какие-либо изменения в таблицу из-за ее огромного размера, поэтому я не смог удалить индекс и повторно добавить покрывающий индекс, как было предложено. Хотя еще раз спасибо! - person Jonathon; 02.12.2016
comment
Вы можете добавить новый индекс, не удаляя предварительно существующий. - person O. Jones; 02.12.2016
comment
Используя ALTER TABLE, вы можете одновременно добавлять и удалять любое количество индексов. - person Rick James; 03.12.2016
comment
COUNT(*) и COUNT(1) являются синонимами; COUNT(col) отличается тем, что проверяет, является ли col NULL. - person Rick James; 03.12.2016
comment
С 5.6.7 и InnoDB вы можете ALTER TABLE ... ALGORITHM=INPLACE не беспокоиться о том, сколько времени займет ADD INDEX. - person Rick James; 03.12.2016
comment
(Придирка) -- Если date_added равно DATETIME(6), вы можете получить другой ответ. < работает "правильно" во всех случаях. - person Rick James; 03.12.2016
comment
@O.Jones Я отредактировал свой вопрос, указав что-то еще, что я нашел, что потенциально может помочь диагностировать проблему. Спасибо :) - person Jonathon; 05.12.2016
comment
Ваше предложение имеет смысл, но вы видели комментарий OP вверху, в котором говорится, что я использую InnoDB для моей таблицы clicks и MyISAM для моей таблицы links, вызывает ли это какие-либо подозрения? - person Rahul; 05.12.2016