Как я могу оптимизировать группу дат mysql по скорости?

#mysql #sql #query-optimization #aggregate-functions #where-clause

#mysql #sql #оптимизация запросов #агрегатные функции #where-предложение

Вопрос:

 CREATE TABLE `device_m1000` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`sensor_id` MEDIUMINT(9) NOT NULL,
`ctime` DATETIME NOT NULL,
`now_data` FLOAT(12) NOT NULL,
`total_data` FLOAT(12) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `sensor_id` (`sensor_id`) USING BTREE,
INDEX `ctime` (`ctime`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=296533;
 

введите описание изображения здесь

В таблице выше содержится 300 000 данных.
Столбец total_data всегда используется в накопленном объеме. (Структура, которая всегда увеличивается)
Если я хочу узнать использование «сегодня», я беру минимальное значение и максимальное значение на основе сегодняшней даты и вычитаю его.

попробуйте sql

 SELECT ROUND((max.v - min.v), 2) total
FROM   (SELECT
               DATE(ctime) `date`,
               sum(total_data)  v
        FROM   device_m1000
        WHERE  ( sensor_id, ctime ) IN (SELECT sensor_id,
                                               MAX(ctime) AS dt
                                        FROM   device_m1000
                                        WHERE  ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
                                        GROUP  BY sensor_id
                                        ORDER  BY null)
        group by `date`) max
       INNER JOIN (SELECT
                          DATE(ctime) `date`,
                          sum(total_data)  v
                   FROM   device_m1000
                   WHERE  ( sensor_id, ctime ) IN (SELECT sensor_id,
                                                          MIN(ctime) AS dt
                                                   FROM   device_m1000
                                                   WHERE  ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
                                                   GROUP  BY sensor_id
                                                   ORDER  BY null)
                   group by `date`) min
               ON min.`date` = max.`date`;
 

Используйте следующий запрос для получения данных.
Однако это просто задерживает код более чем на 10 секунд при максимальном времени (ctime).
Как я могу это оптимизировать?

Ответ №1:

  • «Конструкторы строк» работают плохо. ( WHERE (a,b) IN ... )
  • FLOAT(12) не дает вам 12 значащих цифр, вы получаете только около 7.
  • id Полезно? Или является (sensor_id, ctime) уникальным? Если он уникален, сделайте его уникальным PRIMARY KEY и удалите id .
  • Если вы не можете сделать это PK, имейте INDEX(sensor_id, ctime) .

Вернемся к вашему вопросу. Откажитесь от попытки поместить промежуточный итог в каждую строку. Практически любой подход будет выполнять более миллиарда операций в секунду — из-за необходимости суммировать около 30 тыс. элементов за день для каждого датчика.

Вместо этого создайте отдельную «сводную таблицу» с датой, датчиком и итогом за день. Затем, когда вы INSERT перейдете к подробной таблице (например, к тому, что у вас есть), также выполните

 INSERT INTO daily_summary (date, sensor, tot)
    VALUES (?, ?, ?)
    ON DUPLICATE KEY UPDATE
        tot = VALUES(tot)   ?
 

И используйте DOUBLE для tot . И иметь PRIMARY KEY(sensor, date)

Это позволяет избежать вашего вычитания и обеспечивает большую эффективность.

Подробнее о сводных таблицах: http://mysql.rjweb.org/doc.php/summarytables

Если вам нужны почасовые промежуточные итоги, сделайте это почасовой таблицей, а не ежедневной. Вы можете эффективно получить ежедневную сумму, добавив 24 строки из почасовой сводной таблицы.

Комментарии:

1. Спасибо, Рик. У меня вопрос. Каков эффективный способ создания сводной таблицы? Как вы можете видеть из рисунка, данные датчика будут накапливаться один раз каждые 10 секунд. 1. Подходит ли триггер? 2. Создайте процедуры и запускайте события в соответствующее время (одна минута, пять минут?) 3. Создайте отдельную программу для ВСТАВКИ

2. @Devbreaker — Вручную CREATE TABLE с (возможно) 3 столбцами для вашего случая. Используйте IODKU, который я изложил. Он заботится о создании новой строки для каждого датчика на каждый день. (Я предполагаю, что вы передаете его DATE(now_date) для date .) Прочитайте мою ссылку; также найдите мои ответы в «сводной таблице».

Ответ №2:

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

 select sensor_id, sum(case when rn_desc = 1 then total_data else - total_data end) as total_diff
from (
    select d.*, 
        row_number() over(partition by sensor_id order by ctime) rn_asc,
        row_number() over(partition by sensor_id order by ctime desc) rn_desc
    from device_m1000
    where ctime >= current_date and ctime < current_date   interval 1 day
) t
where 1 in (rn_asc, rn_desc)
group by sensor_id
 

На самом деле, если total_data всегда увеличивается, это проще (и работает во всех версиях MySQL):

 select sensor_id, max(total_data) - min(total_data) as total_diff
from device_m1000
where ctime >= current_date and ctime < current_date   interval 1 day
group by sensor_id
 

Тогда я бы рекомендовал следующий индекс: (ctime, sensor_id, total_data) .

Комментарии:

1. Спасибо за ответ. Однако этот запрос занял 30 секунд…

2. @Devbreaker: вы имеете в виду первый запрос или второй запрос?

3. @Devbreaker: я добавил предложение по индексу к ответу.