2016-10-16 5 views
2

Я строю систему IoT для бытовой техники.Как решить проблему с производительностью GROUP BY в MySQL?

Моя таблица данных была создана как

mysql> SHOW CREATE TABLE DataM1\G 
*************************** 1. row *************************** 
    Table: DataM1 
Create Table: CREATE TABLE `DataM1` (
    `sensor_type` text, 
    `sensor_name` text, 
    `timestamp` datetime DEFAULT NULL, 
    `data_type` text, 
    `massimo` float DEFAULT NULL, 
    `minimo` float DEFAULT NULL, 
    KEY `timestamp_id` (`timestamp`) USING BTREE, 
    KEY `super_index_id` (`timestamp`,`sensor_name`(11),`data_type`(11)) USING BTREE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

и запрос

SELECT 
    sensor_type, sensor_name, timestamp, data_type, 
    MAX(massimo) as massimo, MIN(minimo) as minimo 
FROM DataM1 
    WHERE timestamp >= NOW() - INTERVAL 1 HOUR 
    GROUP BY timestamp, sensor_type, sensor_name, data_type; 

Теперь проблема в том, что, когда таблица достигает 4 миллионов (несколько дней) строки запроса занимает 50 + секунды.

Редактировать: РАЗЪЯСНЯЕМ результат заключается в следующем:

  id: 1 
    select_type: SIMPLE 
      table: DataM1 
    partitions: p0,p1,p2,p3,p4,p5,p6 
      type: range 
    possible_keys: timestamp_id,super_index_id 
      key: timestamp_id 
     key_len: 6 
      ref: NULL 
      rows: 1 
     filtered: 100.00 
      Extra: Using index condition; Using temporary; Using filesort 

Edit: образец строка ответа является:

*************************** 418037. row *************************** 
sensor_type: SEN 
sensor_name: SEN_N2 
    timestamp: 2016-10-16 17:28:48 
    data_type: flow_rate 
    massimo: 17533.8 
    minimo: 17533.5 

Изменить: Я нормализовал значения временной метки, SENSOR_TYPE, sensor_name и DATA_TYPE и создал _view для облегчения потребления данных:

CREATE VIEW `_view` AS (
    select (
    select `vtmp`.`timestamp` from `timestamp` `vtmp` where (`vtmp`.`no` = `pm`.`timestamp`)) AS `timestamp`,(
     select `vtmp`.`sensor_type` from `sensor_type` `vtmp` where (`vtmp`.`no` = `pm`.`sensor_type`)) AS `sensor_type`,(
     select `vtmp`.`sensor_name` from `sensor_name` `vtmp` where (`vtmp`.`no` = `pm`.`sensor_name`)) AS `sensor_name`,(
      select `vtmp`.`data_type` from `data_type` `vtmp` where (`vtmp`.`no` = `pm`.`data_type`)) AS `data_type`, 
      `pm`.`massimo` AS `massimo`, 
      `pm`.`minimo` AS `minimo` 
      from `datam1` `pm` order by `pm`.`timestamp` desc); 

Есть ли способ sp с индексированием, окантовкой и/или разбиением? Или лучше пересмотреть таблицу, разделяющую информацию в разных таблицах? Если да, может ли кто-нибудь предложить свою лучшую практику в такой ситуации?

+0

Вы должны опубликовать результат EXPLAIN. Некоторые другие данные, такие как количество строк за последний час, также будут полезны. И, может быть, некоторые образцы данных (всего несколько строк), чтобы увидеть, как выглядят ваши данные. –

+0

@PaulSpiegel здесь является EXPLAIN результат: ID: 1 SELECT_TYPE: SIMPLE стол: DataM1 перегородки: p0, p1, p2, p3, p4, p5, p6 Тип: Диапазон possible_keys: timestamp_id, super_index_id ключ: timestamp_id key_len: 6 ref: NULL строки: 1 отфильтрован: 100,00 Дополнительно: с использованием условия индекса; Использование временных; Использование filesort – sfiore

+0

@PaulSpiegel количество строк за последний час составляет 60 минут * 60 секунд * 8 датчиков * 4 типа данных = 115,200 – sfiore

ответ

2
  • Не используйте индексирование «префикс», например sensor_name(11); он редко помогает и иногда болит.
  • Если имя и тип датчика, а data_type не может превышать 255 символов, не используйте TEXT; вместо VARCHAR(...) с некоторым реалистичным пределом.
  • Нормализовать имя и тип датчика, а также data_type - я предполагаю, что они повторяются много. ENUM - разумная альтернатива.
  • КЛЮЧ (временная метка) и КЛЮЧ (временная метка, ...) являются избыточными; УБЕДИТЕСЬ предыдущий.
  • Вашему столику нужен PRIMARY KEY. Если ни один столбец (или набор столбцов) не является уникальным, используйте AUTO_INCREMENT.
  • Возможно, вы не хотите СтартGROUP BY с точной меткой времени. Может быть, урезать час? Например, CONCAT(LEFT(timestamp, 13), ':xx') даст что-то вроде 2016-10-16 20:xx.
  • Основная причина, по которой запрос занимает много времени, заключается в том, что он выводит строки 418K. Что вы будете делать с множеством строк? Я не вижу LIMIT, а не ORDER BY. Будет ли это продолжаться?
  • Разделение и разметка не помогут скорости.

Эти предложения помогут по-разному. После того как вы исправили большинство из них, мы можем обсудить, как использовать Сводные таблицы, чтобы получить 10-кратное ускорение.

+0

Спасибо, Рик, я думаю, что ваш пост дал мне много поводов для улучшения. «ENUM» не является жизнеспособным способом, однако, как обязательное поведение, необходимо принимать новые датчики в режиме plug-and-play. У вас есть время обсудить сводные таблицы? У вас хорошая ссылка? – sfiore

+0

Новые датчики - создайте еще один стол с идентификатором и именем датчика; «Нормализация». Сделайте идентификатор 'TINYINT UNSIGNED' (до 255 в 1 байт) или' SMALLINT UNSIGNED' (до 65K в 2 байтах). –

+0

Готово. Я сделал это: 'CREATE TABLE timestamp (нет BIGINT (20) NOT NULL AUTO_INCREMENT, timestamp datetime NOT NULL UNIQUE, PRIMARY KEY (no))', а затем I 'INSERT IGNORE INTO timestamp' перед каждой вставкой в ​​таблице DataM1. Это лучшая практика? – sfiore

2

Вы можете ускорить GROUP BY запроса путем добавления составного индекса по столбцам, используемых для сортировки:

GROUP BY timestamp, sensor_type, sensor_name, data_type; 

спичками:

ADD KEY `group_index` (`timestamp`, `sensor_type`(11), `sensor_name`(11), `data_type`(11)) 

Также обратите внимание (11) в выше показателя:

Для столбцов TEXT MySQL необходимо ограничить содержимое этих столбцов для индексирования. Вы также можете ускорить запрос гораздо больше, выбрав более подходящие типы данных, такие как INT для датчика и тип данных (у вас есть только несколько разных типов, не так ли?) И VARCHAR (128) для имени датчика.

Также да, изменение структуры данных также даст вам некоторые преимущества. Сохраните информацию о датчике (тип + имя) в другой таблице, а затем привяжите его с помощью sensor_id в таблице данных. Таким образом, нужно сортировать только один столбец INT (= сгруппированный), который работает намного лучше, чем сортировка двух столбцов TEXT.

+0

это не работает для меня, потому что, когда 'select count (*)' равно 20 000, такая группа group_index составляет 17 376. Поэтому никакого большого улучшения производительности. – sfiore

+0

Я думаю, что стоит попробовать ваше предложение по сортировке/группировке целого числа. – sfiore

+0

@sfiore - 17376 против 20000 - Это говорит о том, что временные метки почти уникальны. Итак, зачем делать «GROUP BY»? –

-1

Я думаю, что это такие прецеденты, когда у вас так много данных, возможно, лучшим решением было бы использовать базу данных noSQL и выполнить некоторую агрегацию перед сохранением данных. Вы можете посмотреть на Google Big Query и Cloud Data Flow

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

0

В этом ответе обсуждается, как построить Сводная таблица.

CREATE TABLE Summary (
    -- The primary key: 
    hr DATETIME NOT NULL COMMENT "Start of hour", 
    sensor_type ..., 
    sensor_name ..., 
    -- The aggregates being collected: 
    num_readings SMALLINT UNSIGNED NOT NULL, 
    sum_reading FLOAT NOT NULL, -- (maybe) 
    min_reading FLOAT NOT NULL, 
    max_reading FLOAT NOT NULL, 
    PRIMARY KEY(hr, sensor_type, sensor_name), 
    INDEX(sensor_name, hour) -- Maybe you want to look up by sensor? 
) ENGINE=InnoDB; 

Каждый час заселить его с чем-то вроде

INSERT INTO Summary 
    (hr, sensor_type, sensor_name, num_readings, 
    sum_reading, min_reading, max_reading) 
    SELECT 
     FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP()/3600) - 1)), -- start of prev hour 
     sensor_type, 
     sensor_name, 
     COUNT(*), -- how many readings were taken in the hour. 
     SUM(??), -- maybe this is not practical, since you seem to have pairs of readings 
     MAX(massimo), 
     MIN(minimo) 
    FROM DataM1 
    WHERE `timestamp` >= FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP()/3600) - 1)) 
     AND `timestamp` < FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP()/3600))); 

Это предполагает, что вы принимаете ЧТЕНИЯ каждый, скажем, минуту. Если вы читаете только один раз в час, было бы разумнее суммировать с часом.

Другие обсуждения: Summary Tables.

Чтобы быть более надежным, суммирование INSERT-SELECT, возможно, должно быть более сложным - что, если вы пропустите час. (И другие вещи, которые могут пойти не так.)

Caveat: Эта сводная таблица будет намного быстрее, чем чтение из таблицы «Факт», но она может отображать только диапазоны времени, основанные на целых часах. Если вам нужны «последние 60 минут», вам нужно будет зайти в таблицу фактов.

Еще одно замечание: Вы должны нормализуют громоздкие repititous, такие вещи, как sensor_name в том, но вы могли (возможно, должны) денормализовать при построении итоговой таблицы. (Я ушел из этих шагов в этом примере.)

Для выборки данные за вчера:

SELECT sensor_type, sensor_name, data_type, 
     MAX(massimo) as massimo, 
     MIN(minimo) as minimo 
    FROM Summary 
    WHERE timestamp >= CURRENT_DATE() - INTERVAL 1 DAY 
     AND timestamp < CURRENT_DATE() 
    GROUP BY sensor_type, sensor_name, data_type; 

Для всех июня:

WHERE timestamp >= '2016-06-01' 
     AND timestamp < '2016-06-01' + INTERVAL 1 MONTH 

Примечание: Простой способ получить среднее значение - среднее значение средних значений . Но математически правильным способом является суммирование сумм и деление на сумму отсчетов.Следовательно, мое включение sum_reading и num_readings. С другой стороны, при усреднении таких вещей, как показания погоды, обычно получается среднее значение для каждого дня, а затем среднее значение за эти дни. Я оставлю это вам, чтобы решить, что правильно.

+0

Попытка «вставить в сводку» Я получил эту ошибку: 'это несовместимо с sql_mode = only_full_group_by'. Должен ли я отключить этот флаг? – sfiore

+0

Я вытащил 'timestamp' из' SELECT'; это не принадлежало. (Предостережение: возможны и другие ошибки.) –