Оптимизация индексов при длинном запросе MySQL

#mysql #performance #indexing

#mysql #Производительность #индексирование

Вопрос:

Извините, я не мог быть более конкретным в названии.

Итак, я получил этот запрос:

 CREATE TABLE RecordPoints AS (
SELECT competitionId, personId, personCountryId, eventId, year, date,
if(regionalAverageRecord = 'WR',
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date AND rd.average > 0), 0) wrAveragePoints,
if(regionalSingleRecord = 'WR',
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date), 0) wrSinglePoints,
if(NOT regionalAverageRecord in('WR', 'NR'),
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date AND average > 0 AND rd.personCountryId in
(SELECT Countries.id FROM Countries JOIN Continents on Countries.continentId=Continents.id where recordName = rd2.regionalAverageRecord)), 0) crAveragePoints,
if(NOT regionalAverageRecord in('WR', 'NR'),
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date AND rd.personCountryId in
(SELECT Countries.id FROM Countries JOIN Continents on Countries.continentId=Continents.id where recordName = rd2.regionalSingleRecord)), 0) crSinglePoints,
if(regionalAverageRecord = 'NR',
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date AND rd.personCountryId=rd2.personCountryId AND rd.average > 0 ), 0) nrAveragePoints,
if(regionalSingleRecord = 'NR',
(SELECT COUNT(DISTINCT personId) FROM ResultDates rd
WHERE rd.eventId=rd2.eventId AND rd.date <= rd2.date AND rd.personCountryId=rd2.personCountryId), 0) nrSinglePoints
FROM ResultDates rd2 WHERE (NOT regionalAverageRecord='' OR NOT regionalSingleRecord = ''));
 

И для завершения потребовалось 9 часов. Чтобы разбить это, я создаю таблицу, в которой 6 столбцов представляют собой целые подзапросы, чтобы подсчитать, сколько раз PersonID появляется в той же таблице до того, как произошло первое, что я просматриваю, на основе даты и нескольких других столбцов.
Я думаю, что создание индекса по дате с использованием CREATE INDEX date ON ResultDates (date) немного ускорило его, но это все равно занимает чудовищное количество времени.

Строки в ResultDates выглядят так

  ------------ ----------------- --------------- --------- --------- ----- --------- ---------------------- ----------------------- ------- ----- ------ ------------ 
| personId   | personCountryId | competitionId | eventId | roundId | pos | average | regionalSingleRecord | regionalAverageRecord | month | day | year | date       |
 ------------ ----------------- --------------- --------- --------- ----- --------- ---------------------- ----------------------- ------- ----- ------ ------------ 
| 1982THAI01 | USA             | WC1982        | 333     | f       |   1 |       0 | WR                   |                       |     6 |   5 | 1982 | 1982-06-05 |
 ------------ ----------------- --------------- --------- --------- ----- --------- ---------------------- ----------------------- ------- ----- ------ ------------ 
 

Где regionalSingleRecord и regionalAverageRecord могут быть любыми из этих «имен записей»: WR, NR, большую часть времени ничего, или AfR, AsR, ER, NAR, OcR или SAR, которые я затем использую для поиска идентификатора страны на основе того, с каким континентом связаны эти имена записей.

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

Запуск EXPLAIN на нем возвращает мне это:

  ---- -------------------- ------------ ------------ ------ ------------------- -------------- --------- ---------------------------------- -------- ---------- --------------------------------------------------------------- 
| id | select_type        | table      | partitions | type | possible_keys     | key          | key_len | ref                              | rows   | filtered | Extra                                                         |
 ---- -------------------- ------------ ------------ ------ ------------------- -------------- --------- ---------------------------------- -------- ---------- --------------------------------------------------------------- 
|  1 | PRIMARY            | rd2        | NULL       | ref  | idx_personId      | idx_personId | 32      | const                            |    567 |    99.00 | Using where                                                   |
|  9 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date,idx_personId | NULL         | NULL    | NULL                             | 992294 |     0.33 | Range checked for each record (index map: 0x3)                |
|  8 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date,idx_personId | NULL         | NULL    | NULL                             | 992294 |     0.11 | Range checked for each record (index map: 0x3)                |
|  6 | DEPENDENT SUBQUERY | Continents | NULL       | ref  | P_id,recordIndex  | recordIndex  | 9       | cubing.rd2.regionalSingleRecord  |      1 |   100.00 | Using index; Start temporary                                  |
|  6 | DEPENDENT SUBQUERY | Countries  | NULL       | ALL  | NULL              | NULL         | NULL    | NULL                             |    203 |    10.00 | Using where; Using join buffer (Block Nested Loop)            |
|  6 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date              | NULL         | NULL    | NULL                             | 992294 |     0.33 | Range checked for each record (index map: 0x1); End temporary |
|  4 | DEPENDENT SUBQUERY | Continents | NULL       | ref  | P_id,recordIndex  | recordIndex  | 9       | cubing.rd2.regionalAverageRecord |      1 |   100.00 | Using index; Start temporary                                  |
|  4 | DEPENDENT SUBQUERY | Countries  | NULL       | ALL  | NULL              | NULL         | NULL    | NULL                             |    203 |    10.00 | Using where; Using join buffer (Block Nested Loop)            |
|  4 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date              | NULL         | NULL    | NULL                             | 992294 |     0.11 | Range checked for each record (index map: 0x1); End temporary |
|  3 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date,idx_personId | NULL         | NULL    | NULL                             | 992294 |     3.33 | Range checked for each record (index map: 0x3)                |
|  2 | DEPENDENT SUBQUERY | rd         | NULL       | ALL  | date,idx_personId | NULL         | NULL    | NULL                             | 992294 |     1.11 | Range checked for each record (index map: 0x3)                |
 ---- -------------------- ------------ ------------ ------ ------------------- -------------- --------- ---------------------------------- -------- ---------- --------------------------------------------------------------- 
 

Я немного погуглил о том, как повысить его скорость. Основываясь на моем поиске в Google, я знаю, что это выглядит не очень хорошо. Особенно с 992294 строками, которые находятся в моей исходной таблице, на которую я смотрю.

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

Ответ №1:

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

У меня нет времени давать подробный ответ, но мое общее впечатление от просмотра запроса заключается в том, что вы, вероятно, можете реорганизовать его, чтобы ПРИСОЕДИНИТЬ ResultDates к самому себе, один раз, в основном запросе; а затем использовать условную агрегацию в предложении SELECT . Что-то вроде этого…

 SELECT rd.competitionId, rd.personId, rd.personCountryId, rd.eventId
   , rd.year, rd.date
   , COUNT(DISTINCT IF(rd.regionalAverageRecord = 'WR' AND rdPrev.average > 0, rdPrev.person_id, NULL) AS wrAveragePoints
   , COUNT(DISTINCT IF(regionalSingleRecord = 'WR', rdPrev.person_id, NULL) AS wrSinglePoints
   , [etc....]
FROM ResultDates AS rd 
LEFT JOIN ResultDates AS rdPrev 
   ON rd.eventId=rdPrev.eventId 
   AND rdPrev.date <= rd.date
WHERE (NOT rd.regionalAverageRecord='' OR NOT rd.regionalSingleRecord = '')
;
 

Редактировать: для подзапросов / полей, включающих таблицы Countries и Continents , вы также можете обращаться только LEFT JOIN к этим таблицам и использовать объединенные значения аналогично тому, как я продемонстрировал rdPrev.average , что они используются при wrAveragePoints вычислении.

Примечание: COUNT() и большинство других функций агрегирования игнорируют значения NULL.