Около 90% вашего времени выполнения будет использовано для выполнения GROUP BY store_id, date_format(epl.registration, '%m')
.
К сожалению, вы не можете использовать индекс для group by
производного значения, и, поскольку это жизненно важно для вашего отчета, вам необходимо предварительно рассчитать его. Вы можете сделать это, добавив это значение в вашу таблицу, например, используя сгенерированный столбец:
alter table table_1 add md varchar(2) as (date_format(registration, '%m')) stored
Я сохранил формат varchar
, который вы использовали в этом месяце, вы также можете использовать число (например, tinyint
) для месяца.
Для этого требуется MySQL 5.7, в противном случае вы можете использовать триггеры для достижения того же:
alter table table_1 add md varchar(2) null;
create trigger tri_table_1 before insert on table_1
for each row set new.md = date_format(new.registration,'%m');
create trigger tru_table_1 before update on table_1
for each row set new.md = date_format(new.registration,'%m');
Затем добавьте индекс, предпочтительно индекс покрытия, начиная с store_id
и md
, например,
create index idx_table_1_storeid_md on table_1
(store_id, md, invoice_num, paid_amount, profit_amount, cost_amount)
Если у вас есть другие аналогичные отчеты, вы можете проверить, используют ли они дополнительные столбцы и могут ли они извлечь выгоду из покрытия большего количества столбцов. Для индекса потребуется около 1,5 ГБ дискового пространства (и то, сколько времени потребуется вашему диску для чтения 1,5 ГБ, в основном в одиночку определяет время выполнения, за исключением кэширования).
Затем измените запрос на группировку по этому новому проиндексированному столбцу, например,
...
SUM(cost_amount) AS costs,
store_id,
md -- instead of date_format(epl.registration, '%m') md
FROM table_1 epl
GROUP BY store_id, md -- instead of date_format(epl.registration, '%m')
)t2 ...
Этот индекс также позаботится о других 9% вашего времени выполнения, SELECT DISTINCT store_id FROM table_1
, которые получат прибыль от индекса, начинающегося с store_id
.
Теперь, когда о 99% вашего запроса позаботились, некоторые дальнейшие замечания:
подзапрос b
и ваш диапазон дат where a.Date >='2019-01-01' and a.Date <= '2019-01-14'
могут не выполнять то, о чем вы думаете. Вы должны запустить деталь SELECT DATE_FORMAT(a.DATE, "%m") as md, ... group by md
отдельно, чтобы увидеть, что она делает. В своем текущем состоянии он даст вам одну строку с кортежем '01', 0
, представляющим «январь», так что это в основном сложный способ выполнения select '01', 0
. Если сегодня не 15-й или более поздний период, он ничего не возвращает (что, вероятно, непреднамеренно).
В частности, не ограничивает даты выставления счетов этим конкретным диапазоном, но учитывает все накладные, относящиеся к (всему) январю любого года. Если это именно то, что вы намеревались, вы должны (дополнительно) добавить этот фильтр напрямую, например, используя FROM table_1 epl where epl.md = '01' GROUP BY ...
, сократив время выполнения на дополнительный коэффициент около 12. Итак (кроме 15-го и проблем с увеличением), с вашим текущим диапазоном вы должны получить тот же результат, если вы используете
...
SUM(cost_amount) AS costs,
store_id,
md
FROM table_1 epl
WHERE md = '01'
GROUP BY store_id, md
)t2 ...
Для разных диапазонов дат вам придется корректировать этот срок. И чтобы подчеркнуть мою точку зрения, это существенно отличается от фильтрации счетов по дате, например,
...
SUM(cost_amount) AS costs,
store_id,
md
FROM table_1 epl
WHERE epl.registration >='2019-01-01'
and epl.registration <= '2019-01-14'
GROUP BY store_id, md
)t2 ...
, что вы, возможно, (или не можете) пытались сделать. Однако в этом случае вам потребуется другой индекс (и это будет немного другой вопрос).
могут быть некоторые дополнительные оптимизации, упрощения или улучшения в остальной части вашего запроса, например, group BY t1.md, t1.store_id
выглядит избыточно и / или неправильно (указывает на то, что вы на самом деле не в MySQL 5.7), а b
- подзапрос может давать только значения от 1 до 12, поэтому создание 1000 дат и их повторное сокращение может быть упрощено. Но так как они работают на 100-рядных строках, они не окажут существенного влияния на время выполнения, и я не проверял их подробно. Возможно, это происходит из-за получения правильного выходного формата или обобщения (хотя, если вы динамически группируете по другим форматам, а не по месяцам, вам нужны другие индексы / столбцы, но это будет другой вопрос).
Альтернативным способом предварительного расчета ваших значений будет сводная таблица, в которой вы, например, запускайте свой внутренний запрос (дорогой group by
) один раз в день и сохраняйте результат в таблице, а затем используйте его повторно (выбирая из этой таблицы вместо того, чтобы группировать по). Это особенно актуально для таких данных, как счета-фактуры, которые никогда не меняются (хотя в противном случае вы можете использовать триггеры для обновления сводных таблиц). Это также становится более жизнеспособным, если у вас есть несколько сценариев, например, если ваш пользователь может решить сгруппировать по дням недели, году, месяцу или знаку зодиака, так как в противном случае вам нужно будет добавить индекс для каждого из них. Он становится менее жизнеспособным, если вам необходимо динамически ограничить диапазон счетов (например, 2019-01-01 ... 2019-01-14). Если вам нужно включить в отчет текущий день, вы все равно можете предварительно рассчитать, а затем добавить значения для текущей даты из таблицы (которая должна включать только очень ограниченное количество строк, что быстро, если у вас есть индекс, начинающийся с столбец даты), или используйте триггеры для обновления сводной таблицы на лету.