2016-12-08 17 views
1

приводится таблица MySQL содержит следующие две таблицы таблицы (упрощенный):использованием диапазона с композитным ключом

(~13000)   (~7000000 rows)  
---------------  -------------------- 
| packages |  | packages_prices | 
---------------  -------------------- 
| id (int) |<- ->| package_id (int) | 
| state (int) |  | variant_id (int) | 
- - - - - - -  | for_date (date) | 
        | price (float) | 
        - - - - - - - - - 

Каждая комбинация package_id/for_date только несколько (в среднем 3) варианты. И state - 0 (неактивный) или 1 (активный). Около 4000 из 13000 активных.

Сначала я просто хочу знать, какие пакеты имеет ценовой набор (независимо от изменения), поэтому я добавляю составной ключ, охватывающий (1) for_date и (2) pid и я запрос:

select distinct package_id from packages_prices where for_date > date(now()) 

Этих запрос занимает 1 секунду, чтобы вернуть 3500 строк, что слишком много. Объяснение говорит мне, что составной ключ используется с key_len 3, а 2000000 строк проверяются с фильтром 100% с типом. Using where; Using index; Using temporary. Различный возвращает его к 3500 рядам.

Если я вывожу distinct, то больше не упоминается Using temporary, но затем запрос возвращает 1000000 строк и занимает 1 секунду.

вопрос 1: почему этот запрос настолько медленный и как я могу ускорить его, не добавляя или не изменяя столбцы в таблице? Я ожидал бы, что, учитывая составной ключ, этот запрос должен стоить менее 0,01.

Теперь я хочу знать, какие активные пакеты, у которых есть цена.

Поэтому я добавляю ключ на state и добавляю новый составной ключ, как и выше, но в обратном порядке. И я пишу свой запрос следующим образом:

select distinct packages.id from packages 
inner join packages_prices on id = package_id and for_date > date(now()) 
where state = 1 

Запрос сейчас занимает 2 секунды. Объяснение говорит мне, что для таблицы packages ключ на state используется с key_len 4, исследует 4000 строк и фильтрует 100% тип типа ref. Using index; Using temporary. А для таблицы packages_prices новый составной ключ используется с key_len 4, рассматривается 1000 строк и фильтров 33.33% с типом ref. Using where; Using index; Distinct. Четкость возвращает его к 3000 рядам.

Если я вывожу distinct, Using temporary и Distinct больше не упоминаются, а запрос возвращает 850000 строк и занимает 3 секунды.

вопрос 2: Почему запрос намного медленнее сейчас? Почему диапазон больше не используется в соответствии с Объяснением? И почему фильтрация с новым составным ключом снизилась до 33,33%? Я ожидал, что составной ключ снова будет фильтровать 100% -ный экземпляр.

Все это кажется очень простым и тривиальным, но это стоило мне часов и часов, и я до сих пор не понимаю, что происходит на самом деле под капотом.

+0

Прежде всего, вы должны, вероятно, задать два отдельных вопроса. Затем, пожалуйста, укажите таблицу создания, создайте индекс и покажите нам полный план объяснения. –

+0

, когда вы сказали «варианты», вы имеете в виду разные 'packages_prices'? –

+0

@HoneyBadger Это две разделенные таблицы. Но он поставил его бок о бок. –

ответ

1

Ваши наблюдения согласуются с тем, как работает MySQL. Для вашего первого запроса, используя индекс (for_date, package_id), MySQL запустится в указанную дату (используя индекс, чтобы найти эту позицию), но затем должен перейти в конец индекса, потому что каждая следующая запись может открыть неизвестный package_id. Конкретный package_id может, например, только что были использованы на последних for_date. Этот поиск добавит до 2000000 проверенных строк.Соответствующие данные извлекаются из индекса, но все равно потребуется время.

Что делать?

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

select package_id from packages_prices 
group by package_id 
having max(for_date) > date(now()); 

Это даст тот же результат, как ваш первый запрос: если есть по крайней мере один for_date > date(now()) (что сделает его часть вашего набора результатов), что будет верно и для max(for_date). Но это нужно будет проверить только на одну строку на package_id (тот, у кого есть max(for_date)), все остальные строки с for_date > date(now()) могут быть пропущены.

MySQL сделает это на using index for group-by -оптимизация (этот текст должен отображаться в вашем explain). Для этого потребуется индекс (package_id, for_date) (который у вас уже есть), и вам нужно изучить только 13000 строк. Поскольку список упорядочен, MySQL может перейти непосредственно к последней записи для каждого package_id, который будет иметь значение для max(for_date); а затем продолжить со следующего package_id.

На самом деле MySQL может использовать этот метод для оптимизации distinct (и, вероятно, это сделает, если вы удалите условие на for_date), но не всегда можете найти способ; действительно умный оптимизатор мог бы переписать ваш запрос так же, как я, но мы еще не были.

И в зависимости от вашего распределения данных этот метод может быть плохой идеей: если у вас есть, например, 7000000 package_id, но только 20 из них в будущем, проверяя каждый package_id на максимум for_date будет намного медленнее, чем просто проверка 20 строк, которые вы можете легко найти по индексу на for_date. Поэтому знание ваших данных будет играть важную роль в выборе лучшей (и, возможно, оптимальной) стратегии.

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