Очень низкая производительность запросов MySQL - PullRequest
0 голосов
/ 21 ноября 2018

У меня запрос, который занимает около 18 секунд до завершения:

ЗАПРОС:

SELECT YEAR(c.date), MONTH(c.date), p.district_id, COUNT(p.owner_id)
FROM commission c
  INNER JOIN partner p ON c.customer_id = p.id
WHERE (c.date BETWEEN '2018-01-01' AND '2018-12-31')
  AND (c.company_id = 90)
  AND (c.source = 'ACTUAL')
  AND (p.id IN (3062, 3063, 3064, 3065, 3066, 3067, 3068, 3069, 3070, 3071,
    3072, 3073, 3074, 3075, 3076, 3077, 3078, 3079, 3081, 3082, 3083, 3084,
    3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094, 3095, 3096,
    3097, 3098, 3099, 3448, 3449, 3450, 3451, 3452, 3453, 3454, 3455, 3456,
    3457, 3458, 3459, 3460, 3461, 3471, 3490, 3491, 6307, 6368, 6421))
  GROUP BY YEAR(c.date), MONTH(c.date), p.district_id

commissionтаблица содержит около 2,8 миллионов записей, из которых 860 000 + относится к текущему 2018 году. В таблице partner на данный момент насчитывается 8600+ записей.

РЕЗУЛЬТАТ

| `YEAR(c.date)` | `MONTH(c.date)` | district_id | `COUNT(c.id)` | 
|----------------|-----------------|-------------|---------------| 
| 2018           | 1               | 1           | 19154         | 
| 2018           | 1               | 5           | 9184          | 
| 2018           | 1               | 6           | 2706          | 
| 2018           | 1               | 12          | 36296         | 
| 2018           | 1               | 15          | 13085         | 
| 2018           | 2               | 1           | 21231         | 
| 2018           | 2               | 5           | 10242         | 
| ...            | ...             | ...         | ...           | 

55 rows retrieved starting from 1 in 18 s 374 ms 
(execution: 18 s 368 ms, fetching: 6 ms)

ОБЪЯСНЕНИЕ:

| id | select_type | table | partitions | type  | possible_keys                                                                                        | key                  | key_len | ref             | rows | filtered | extra                                        | 
|----|-------------|-------|------------|-------|------------------------------------------------------------------------------------------------------|----------------------|---------|-----------------|------|----------|----------------------------------------------| 
| 1  | SIMPLE      | p     | null       | range | PRIMARY                                                                                              | PRIMARY              | 4       |                 | 57   | 100      | Using where; Using temporary; Using filesort | 
| 1  | SIMPLE      | c     | null       | ref   | UNIQ_6F7146F0979B1AD62FC0CB0F5F8A7F73,IDX_6F7146F09395C3F3,IDX_6F7146F0979B1AD6,IDX_6F7146F0AA9E377A | IDX_6F7146F09395C3F3 | 5       | p.id            | 6716 | 8.33     | Using where                                  | 

DDL:

create table if not exists commission (
    id int auto_increment
        primary key,
    date date not null,
    source enum('ACTUAL', 'EXPECTED') not null,
    customer_id int null,
    transaction_id varchar(255) not null,
    company_id int null,
    constraint UNIQ_6F7146F0979B1AD62FC0CB0F5F8A7F73 unique (company_id, transaction_id, source),
    constraint FK_6F7146F09395C3F3 foreign key (customer_id) references partner (id),
    constraint FK_6F7146F0979B1AD6 foreign key (company_id) references companies (id)
) collate=utf8_unicode_ci;
create index IDX_6F7146F09395C3F3 on commission (customer_id);
create index IDX_6F7146F0979B1AD6 on commission (company_id);
create index IDX_6F7146F0AA9E377A on commission (date);

Я заметил, что при удалении партнера IN условие MySQL занимает всего 3 с.Я пытался заменить это, делая что-то сумасшедшее, как это:

AND (',3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461,3471,3490,3491,6307,6368,6421,'
     LIKE CONCAT('%,', p.id, ',%')) 

, и результат был около 5 с ... отлично!но это хак.

ПОЧЕМУ этот запрос занимает очень много времени, когда я использую оператор IN?обходные пути, советы, ссылки и т. д. Спасибо!

Ответы [ 3 ]

0 голосов
/ 21 ноября 2018

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

Я думаю, что реальной проблемой в вашем случае является вторая строка объяснения: сервер выполняет несколько функций (MONTH, YEAR) для 6716 строк, а затем пытается сгруппировать все этистрок.В течение этого времени все эти 6716 строк должны быть сохранены (в памяти или на диске, который основан на конфигурации вашего сервера).

SELECT COUNT(*) FROM commission WHERE (date BETWEEN '2018-01-01' AND '2018-12-31') AND company_id = 90 AND source = 'ACTUAL';

=> Сколько строк мы говорим?

Если число в запросе выше, чем 6716, я бы попытался добавить индекс покрытия по столбцам customer_id, company_id, source и date.Не уверен насчет лучшего порядка, поскольку он зависит от имеющихся у вас данных (проверьте количество элементов в этих столбцах).Я начал с индекса (дата, идентификатор компании, источник, идентификатор клиента).Кроме того, я бы добавил уникальный индекс (id, district_id, owner_id) для партнера.

Также возможно добавить дополнительные сгенерированные сохраненные столбцы _year и _month (если ваш сервер немного стар, вы можете добавить обычные столбцыи заполните их триггером), чтобы избавиться от выполнения нескольких функций.

0 голосов
/ 21 ноября 2018

Вот что оптимизатор видит в вашем запросе.

Проверка, использовать ли индекс для GROUP BY:

  • функций (YEAR()) в GROUP BY, поэтому №
  • Упомянуто несколько таблиц (c и p), поэтому №

Для JOIN Оптимизатор будет (почти всегда) начинаться с одной, а затем добраться до другого.Итак, давайте рассмотрим два варианта:

Если начинается с p:

Если у вас есть PRIMARY KEY(id), думать не о чем.Он будет просто использовать этот индекс.

Для каждой строки, выбранной из p, он будет затем смотреть на c, и любое изменение этого INDEX будет оптимальным.

c: INDEX(company_id, source, customer_id,  -- in any order (all are tested "=")
         date)       -- last, since it is tested as a range

Если начинается с c:

c: INDEX(company_id, source,  -- in any order (all are tested "=")
         date)       -- last, since it is tested as a range
-- slightly better:
c: INDEX(company_id, source,  -- in any order (all are tested "=")
         date,       -- last, since it is tested as a range
         customer_id)  -- really last -- added only to make it "covering".

Оптимизатор будет смотреть на «статистику», чтобы грубо решить, с какой таблицы начинать.Итак, добавьте все предложенные мной индексы.

"Покрывающий" индекс - это индекс, который содержит все столбцы, необходимые в любом месте в запросе. иногда целесообразно расширить «хороший» индекс большим количеством столбцов, чтобы он стал «покрывающим».

Но здесь есть обезьяна.c.customer_id = p.id означает, что customer_id IN (...) фактически существует.Но теперь есть два ограничения типа «диапазон»: одно - IN, другое - «диапазон».В некоторых более новых версиях Оптимизатор будет радостно прыгать, потому что IN и все еще смогут сканировать «на расстоянии».Итак, я рекомендую этот порядок:

  1. Тест (ы) column = constant
  2. Тест (ы) с IN
  3. Один тест 'range' (BETWEEN, >=, LIKE с конечным подстановочным знаком и т. д.)
  4. Возможно, добавьте больше столбцов, чтобы сделать его "покрывающим" - но не выполняйте этот шаг, если выв итоге в индексе будет больше, скажем, 5 столбцов.

Следовательно, для c следующее оптимально для WHERE и оказывается "охватывающим".

INDEX(company_id, source,  -- first, but in any order (all "=")
      customer_id,  -- "IN"
      date)       -- last, since it is tested as a range

p: (same as above)

Поскольку существовал IN или "диапазон", нет смысла видеть, может ли индекс также обрабатывать GROUP BY.

Примечание к COUNT(x) - этопроверяет, что x равно NOT NULL.Это обычно , точно так же, как правильно сказать COUNT(*), который подсчитывает количество строк без какой-либо дополнительной проверки.

Это не стартер, поскольку он скрывает индексированный столбец (id) в функции:

AND (',3062,3063,3064,3065,3066,...6368,6421,'
     LIKE CONCAT('%,', p.id, ',%'))
0 голосов
/ 21 ноября 2018

MySQL может использовать один индекс за раз.Для этого запроса вам нужен составной индекс, охватывающий аспекты поиска.Постоянные аспекты предложения WHERE должны использоваться перед такими аспектами диапазона, как:

ALTER TABLE commission
DROP INDEX IDX_6F7146F0979B1AD6,
ADD INDEX IDX_6F7146F0979B1AD6 (company_id, source, date)
...