MySQL: Оптимізуйте UNION за допомогою "ЗАМОВЛЕННЯ ПО" у внутрішніх запитах


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


1
Найкращим показником для цього буде сполука (account_id, zeitpunkt). У вас є такий показник? Другим найкращим був би (я думаю) сингл, (zeitpunkt)але ефективність, якщо це використовується, залежить від того, наскільки часто account_id=730з’являються рядки .
ypercubeᵀᴹ

2
А чому UNION DISTINCT? Немає необхідності форсувати та розрізнювати там, оскільки результати будуть різними для підзапитів, завдяки додатковому стовпчику ідентифікації. Використовуйте UNION ALL.
ypercubeᵀᴹ

1
Окрім пропозиції @ ypercube, у мене є питання: чи не було б краще, щоб усі ці журнали були в одній таблиці з додаванням sourceстовпця? Таким чином ви можете уникнути UNIONs та використовувати індекс (и) для всіх своїх даних.
dezso

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 рядів швидко, і жодних хитрощів не потрібно.


Ваша модифікація виявила бажані результати. Дякую! Як бічна примітка: на даний момент я не впевнений, який індекс буде кращим. Я могла навіть використовувати і те, і інше. Мені доведеться перевірити, як log entries / userмасштабується кількість користувачів та воля.
Лукас

Якщо вам потрібні запити з і без запитів account_id=?, збережіть обидва.
ypercubeᵀᴹ

@ypercube, +1 це дуже розумно і теж працювало в моїй (подібній) ситуації! Чи можете ви пояснити, чому вкручування об'єднаних запитів у фіктивну SELECT * FROMхитрість MySQL використовувати індекси?
dkamins

@dkamins: Оптимізатор MySQL не дуже розумний, зазвичай, коли є похідна таблиця, наприклад, тут (SELECT ...) AS a, він намагається оцінити та оптимізувати отриману таблицю окремо від інших похідних таблиць, а потім і весь запит.
ypercubeᵀᴹ

@Lukas, насправді, оскільки вам потрібно забезпечити використання індексу, використання / додавання force indexдасть вам краще рішення.
Печер'є
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.