MySQL: оптимизируйте UNION с помощью «ORDER BY» во внутренних запросах

9

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

Существует одна таблица для каждого источника данных.

Для просмотра журнала я хочу

  • UNION все журнальные таблицы ,
  • фильтровать их по аккаунту ,
  • добавить псевдостолбец для идентификации источника,
  • сортировать их по времени ,
  • и ограничить их для нумерации страниц .

Все таблицы содержат поле с именем, zeitpunktкоторое является индексированным столбцом даты / времени.

Моя первая попытка была:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

Оптимизатор не может использовать индексы здесь, потому что все строки из обеих таблиц возвращаются подзапросами и сортируются после UNION.

Мой обходной путь был следующим:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Я ожидал, что механизм запросов будет использовать индексы здесь, так как оба подзапроса должны быть отсортированы и ограничены уже до UNION, который затем объединяет и сортирует строки.

Я действительно думал, что это так, но выполнение EXPLAINзапроса говорит мне, что подзапросы все еще ищут обе таблицы.

EXPLAINingСами подзапросы показывают мне желаемую оптимизацию, а UNIONingих вместе - нет.

Я что-то пропустил?

Я знаю, что ORDER BYпредложения внутри UNIONподзапросов игнорируются без LIMIT, но есть предел.

Изменить:
На самом деле, вероятно, также будут запросы безaccount_idусловия.

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

Я должен держать своего рода слой между читателями журнала и фактическими таблицами.

Вот планы выполнения для всего запроса и первого подзапроса, а также детализация таблицы:

https://gist.github.com/ca8fc1093cd95b1c6fc0

Lukas
источник
1
Лучший показатель для этого будет составным (account_id, zeitpunkt). У вас есть такой индекс? Вторым лучшим будет (я думаю) сингл (zeitpunkt)- но эффективность, если он используется, зависит от того, как часто account_id=730появляются строки с .
ypercubeᵀᴹ
2
А почему UNION DISTINCT? Там нет необходимости навязывать сортировку и различение, так как результаты будут разными для подзапросов из-за дополнительного столбца идентификации. Использование UNION ALL.
ypercubeᵀᴹ
1
В дополнение к предложению @ ypercube у меня есть вопрос: не лучше ли было бы иметь все эти журналы в одной таблице с добавлением sourceстолбца? Таким образом, вы можете избежать UNIONs и использовать индексы для всех ваших данных.
Дезсо
1
@ypercube На самом деле, вероятно, также будут запросы без условия account_id . DISTINCT флаг является реликтом предыдущих попыток и на самом деле бесполезно , потому что результаты всегда будут отличаться и потому , что DISTINCT является поведением dafualt. Таблицы уже существуют и заполнены данными. В любом случае, могут быть изменения в макете в зависимости от источника, поэтому я хочу разделить их. Кроме того, клиенты журналирования используют разные учетные данные по причине. Я должен держать своего рода слой между читателями журнала и фактическими таблицами.
Лукас
Хорошо, но проверьте, если изменение UNION ALLприводит к другому плану выполнения.
ypercubeᵀᴹ

Ответы:

8

Просто из любопытства, вы можете попробовать эту версию? Оптимизатор может обмануть использование тех же индексов, которые подзапросы будут использовать отдельно:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Я все еще думаю, что лучший индекс, который вы могли бы иметь, - это сложный (account_id, zeitpunkt). Это дало бы 10 рядов быстро, и никаких трюков не понадобилось бы.

ypercubeᵀᴹ
источник
Ваша модификация принесла желаемые результаты. Спасибо! Как примечание: сейчас я не уверен, какой индекс будет лучше. Я мог бы даже использовать оба. Я должен проверить, как количество пользователей и log entries / userмасштаб будет.
Лукас
Если вам нужны запросы с запросами и без них account_id=?, сохраните оба.
ypercubeᵀᴹ
@ypercube, +1 это очень умно и работает в моей (похожей) ситуации тоже! Можете ли вы объяснить, почему обертка объединенных запросов в фиктивную хитрость заставляет SELECT * FROMMySQL использовать индексы?
Dkamins
@dkamins: оптимизатор MySQL не очень умен, обычно, когда есть производная таблица, как здесь (SELECT ...) AS a, он пытается оценить и оптимизировать производную таблицу отдельно от других производных таблиц, а затем весь запрос.
ypercubeᵀᴹ
@Lukas, на самом деле, так как вам нужно убедиться, что индекс используется, использование / добавление force indexдаст вам лучшее решение.
Pacerier