Медленный SQL-запрос с оптимизацией GROUP BY - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть БД MySQL. Полученные данные хранятся в таблицах raw_data_headers, raw_data_rows и raw_data_row_details.

raw_data_row_details имеет внешний ключ, который ссылается на raw_data_rows.ID, то же самое для raw_data_rows и raw_data_headers.

В raw_data_headers хранятся заголовки данных, в raw_data_rows хранятся все этапы программы сбора данных, а в raw_data_row_details хранятся подробности для каждого этапа программы сбора данных.

Это запрос:

SELECT
    q1.ProcessTypeID,
    q1.TestTypeID,
    q1.ComponentID,
    q1.TestResultID,
    COUNT(*) AS Counter
FROM (
    SELECT
        raw_data_headers.batch_id AS BatchID,
        raw_data_test_outputs.test_output_type_id AS TestOutputTypeID,
        raw_data_test_types.process_type_id AS ProcessTypeID,
        raw_data_test_types.ID AS TestTypeID,
        raw_data_row_details.component_id AS ComponentID,
        raw_data_test_results.ID AS TestResultID
    FROM raw_data_row_details
    INNER JOIN raw_data_rows ON raw_data_rows.ID = raw_data_row_details.row_id
    INNER JOIN raw_data_headers ON raw_data_headers.ID = raw_data_rows.header_id
    INNER JOIN raw_data_test_results ON raw_data_test_results.ID = raw_data_row_details.Value
    INNER JOIN raw_data_test_outputs ON raw_data_test_outputs.ID = raw_data_row_details.test_output_id
    INNER JOIN raw_data_test_types ON raw_data_test_types.ID = raw_data_test_outputs.test_type_id
    HAVING TestOutputTypeID = 2 AND BatchID = 1
) AS q1
GROUP BY q1.ProcessTypeID, q1.TestTypeID, q1.ComponentID, q1.TestResultID

raw_data_headers имеет 989'180 записей, row_data_rows имеет 2'967'540 записей, а raw_data_row_details имеет 13'848'520 записей.

Подзапрос q1 занимает около 3 минут, но окончательный запрос занимает около 25 минут. Я думаю, что дело в GROUP BY. Как я могу улучшить производительность?

РЕДАКТИРОВАТЬ 1:

SELECT
    gnuhmi.raw_data_test_types.process_type_id AS ProcessTypeID,
    gnuhmi.raw_data_test_types.ID AS TestTypeID,
    gnuhmi.raw_data_row_details.component_id AS ComponentID,
    gnuhmi.raw_data_test_results.ID AS TestResultID,
    COUNT(*) AS Counter
FROM gnuhmi.raw_data_row_details
INNER JOIN gnuhmi.raw_data_rows ON gnuhmi.raw_data_rows.ID = gnuhmi.raw_data_row_details.row_id
INNER JOIN gnuhmi.raw_data_headers ON gnuhmi.raw_data_headers.ID = gnuhmi.raw_data_rows.header_id
INNER JOIN gnuhmi.raw_data_test_results ON gnuhmi.raw_data_test_results.ID = gnuhmi.raw_data_row_details.Value
INNER JOIN gnuhmi.raw_data_test_outputs ON gnuhmi.raw_data_test_outputs.ID = gnuhmi.raw_data_row_details.test_output_id
INNER JOIN gnuhmi.raw_data_test_types ON gnuhmi.raw_data_test_types.ID = gnuhmi.raw_data_test_outputs.test_type_id
WHERE gnuhmi.raw_data_test_outputs.test_output_type_id = 2 AND gnuhmi.raw_data_headers.batch_id = 1
GROUP BY
    gnuhmi.raw_data_test_results.ID,
    gnuhmi.raw_data_row_details.component_id,
    gnuhmi.raw_data_test_types.ID,
    gnuhmi.raw_data_test_types.process_type_id

Это новый запрос, без подзапроса и WHERE. Это увеличило производительность (спасибо @Yogesh Sharma).

это raw_data_headers структура:

CREATE TABLE `raw_data_headers` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Univocal record key',
  `ProductID` int(11) NOT NULL COMMENT 'Product numeric ID',
  `Datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Univocal record creation date',
  `batch_id` int(11) DEFAULT NULL COMMENT 'Univocal batch key',
  `RecipeName` varchar(80) DEFAULT NULL COMMENT 'Used recipe name',
  `RecipeVersion` smallint(6) DEFAULT NULL COMMENT 'Used recipe version',
  `process_result_id` smallint(6) DEFAULT NULL COMMENT 'Process result key',
  `invalidated` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'invalidation after counters reset',
  PRIMARY KEY (`ID`),
  KEY `FK_raw_data_headers_batches_ID` (`batch_id`),
  KEY `FK_raw_data_headers_process_re` (`process_result_id`),
  CONSTRAINT `FK_raw_data_headers_batches_ID` FOREIGN KEY (`batch_id`) REFERENCES `batches` (`ID`) ON UPDATE CASCADE,
  CONSTRAINT `FK_raw_data_headers_process_re` FOREIGN KEY (`process_result_id`) REFERENCES `process_result` (`ID`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Stores raw data headers'

Это картинка raw_dato_rows:

CREATE TABLE `raw_data_rows` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Univocal record key',
  `Datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Univocal record creation date',
  `header_id` int(11) unsigned NOT NULL COMMENT 'Univocal raw data header key',
  `process_type_id` smallint(6) NOT NULL COMMENT 'Univocal process type key',
  `process_result_id` smallint(6) NOT NULL COMMENT 'Univocal process result key',
  PRIMARY KEY (`ID`),
  KEY `FK_raw_data_rows_header_id` (`header_id`),
  KEY `FK_raw_data_rows_process_resu2` (`process_result_id`),
  KEY `FK_raw_data_rows_process_resul` (`process_type_id`),
  CONSTRAINT `FK_raw_data_rows_header_id` FOREIGN KEY (`header_id`) REFERENCES `raw_data_headers` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `FK_raw_data_rows_process_resu2` FOREIGN KEY (`process_result_id`) REFERENCES `process_result` (`ID`) ON DELETE NO ACTION ON UPDATE CASCADE,
  CONSTRAINT `FK_raw_data_rows_process_resul` FOREIGN KEY (`process_type_id`) REFERENCES `process_types` (`ID`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2967541 DEFAULT CHARSET=utf8 COMMENT='Stores row data rows'

и, наконец, это raw_data_row_details один:

CREATE TABLE `raw_data_row_details` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Univocal row detail  key',
  `row_id` int(11) unsigned NOT NULL COMMENT 'Univocal row key',
  `test_output_id` int(11) NOT NULL COMMENT 'Univocal test output key',
  `component_id` int(11) NOT NULL COMMENT 'The component that take the measurement',
  `Value` double NOT NULL COMMENT 'Output value',
  PRIMARY KEY (`ID`),
  KEY `FK_raw_data_row_details_row_id` (`row_id`),
  KEY `FK_raw_data_rows_raw_data_test_outputs_ID` (`test_output_id`),
  KEY `raw_data_row_details_components_FK` (`component_id`),
  CONSTRAINT `FK_raw_data_row_details_row_id` FOREIGN KEY (`row_id`) REFERENCES `raw_data_rows` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `FK_raw_data_rows_raw_data_test_outputs_ID` FOREIGN KEY (`test_output_id`) REFERENCES `raw_data_test_outputs` (`ID`) ON UPDATE CASCADE,
  CONSTRAINT `raw_data_row_details_components_FK` FOREIGN KEY (`component_id`) REFERENCES `components` (`ID`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=13848521 DEFAULT CHARSET=utf8 COMMENT='Stores raw data rows details'

Ответы [ 3 ]

0 голосов
/ 05 сентября 2018

Вам не нужно использовать subquery, просто используйте where предложение с group by:

SELECT  raw_data_test_types.process_type_id AS ProcessTypeID,
        raw_data_test_types.ID AS TestTypeID,
        raw_data_row_details.component_id AS ComponentID,
        raw_data_test_results.ID AS TestResultID, COUNT(*) AS Counter
FROM raw_data_row_details INNER JOIN 
     raw_data_rows 
     ON raw_data_rows.ID = raw_data_row_details.row_id INNER JOIN 
     raw_data_headers 
     ON raw_data_headers.ID = raw_data_rows.header_id INNER JOIN 
     raw_data_test_results 
     ON raw_data_test_results.ID = raw_data_row_details.Value INNER JOIN 
     raw_data_test_outputs 
     ON raw_data_test_outputs.ID = raw_data_row_details.test_output_id INNER JOIN 
     raw_data_test_types 
     ON raw_data_test_types.ID = raw_data_test_outputs.test_type_id
WHERE raw_data_headers.batch_id = 1 AND raw_data_test_outputs.test_output_type = 2
GROUP BY raw_data_test_types.process_type_id, raw_data_test_types.ID,
         raw_data_row_details.component_id, raw_data_test_results.ID; 
0 голосов
/ 20 сентября 2018
HAVING TestOutputTypeID = 2 AND BatchID = 1

Измените его с HAVING на WHERE и добавьте индексы в каждом из этих столбцов.

Также есть эти индексы:

raw_data_row_details:  (row_id)
raw_data_rows:         (header_id)
raw_data_row_details:  (test_output_id)
raw_data_test_outputs: (test_type_id)

Избавиться от raw_data_ из имен таблиц; это просто загромождает запросы.

Если это не поможет, пожалуйста, укажите EXPLAIN SELECT ... и SHOW CREATE TABLE.

0 голосов
/ 05 сентября 2018

Добавить индексы. TestOutputTypeID и BatchID должны быть покрыты и, вероятно, нет.

Чтобы увидеть, что в данный момент происходит, используйте EXPLAIN в консоли MySQL. Вы, вероятно, увидите признак того, что происходит полное сканирование таблицы, то есть тип объединения помечен как ALL.

Часто бывает так, что оптимизатор запросов будет использовать один и тот же план выполнения для разных запросов, например расширяя подзапрос, как если бы вы его не использовали. Только EXPLAIN покажет вам, что к чему.

Вот документы о том, как интерпретировать вывод EXPLAIN: https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

...