MySQL запрос с ORDER BY занимает много времени для выполнения - PullRequest
2 голосов
/ 19 января 2020

У меня есть таблица с именем 'response_set' со следующими индексами (результат 'show create table response_set;'):

| response_set | CREATE TABLE `response_set` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `survey_id` int(11) NOT NULL DEFAULT '0',
  `respondent_id` int(11) DEFAULT NULL,
  `ext_ref` varchar(64) DEFAULT NULL,
  `email_addr` varchar(128) DEFAULT NULL,
  `ip` varchar(32) DEFAULT NULL,
  `t` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `time_taken` int(11) DEFAULT NULL,
  `category_id` int(11) DEFAULT NULL,
  `duplicate` int(1) DEFAULT '0',
  `email_group` varchar(30) DEFAULT NULL,
  `external_email_id` int(11) DEFAULT NULL,
  `geo_code_country` varchar(64) DEFAULT NULL,
  `geo_code_country_code` varchar(2) DEFAULT NULL,
  `terminated_survey` int(1) DEFAULT NULL,
  `geo_code_region` varchar(128) DEFAULT NULL,
  `geo_code_city` varchar(3) DEFAULT NULL,
  `geo_code_area_code` varchar(3) DEFAULT NULL,
  `geo_code_dma_code` varchar(3) DEFAULT NULL,
  `restart_url` varchar(255) DEFAULT NULL,
  `inset_list` varchar(1024) DEFAULT NULL,
  `custom1` varchar(1024) DEFAULT NULL,
  `custom2` varchar(1024) DEFAULT NULL,
  `custom3` varchar(1024) DEFAULT NULL,
  `custom4` varchar(1024) DEFAULT NULL,
  `panel_member_id` int(11) DEFAULT NULL,
  `external_id` int(11) DEFAULT NULL,
  `weight` float DEFAULT NULL,
  `custom5` varchar(1024) DEFAULT NULL,
  `quota_overlimit` int(1) DEFAULT '0',
  `panel_id` int(11) DEFAULT NULL,
  `referer_url` varchar(255) DEFAULT NULL,
  `referer_domain` varchar(64) DEFAULT NULL,
  `user_agent` varchar(255) DEFAULT NULL,
  `longitude` decimal(15,12) DEFAULT '0.000000000000',
  `latitude` decimal(15,12) DEFAULT '0.000000000000',
  `radius` decimal(7,2) DEFAULT '0.00',
  `cx_business_unit_id` int(11) DEFAULT '0',
  `survey_link_id` int(11) DEFAULT '0',
  `data_quality_flag` int(1) DEFAULT '0',
  `data_quality_score` double DEFAULT '0',
  `extended_info_json` json DEFAULT NULL,
  `updated_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `channel` int(1) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `panel_member_id` (`panel_member_id`),
  KEY `panel_member_id_2` (`panel_member_id`),
  KEY `email_group` (`email_group`),
  KEY `email_group_2` (`email_group`),
  KEY `survey_timestamp_idx` (`survey_id`,`t`),
  KEY `cx_business_unit_id_idx` (`cx_business_unit_id`),
  KEY `data_quality_flag_idx` (`data_quality_flag`),
  KEY `data_quality_score_idx` (`data_quality_score`),
  KEY `survey_timestamp_terminated_idx` (`survey_id`,`t`,`terminated_survey`),
  KEY `survey_idx` (`survey_id`)
) ENGINE=InnoDB AUTO_INCREMENT=39759 DEFAULT CHARSET=utf8 |

Теперь я выполняю следующий запрос на странице, чтобы получить строки response_set на основе survey_id и порядка по идентификатору:

SELECT * 
FROM response_set a 
WHERE a.survey_id = 1602673827 
ORDER BY a.id limit 100;

Проблема заключается в том, что запрос иногда требует более 30 секунд для выполнения, и такое поведение является противоречивым (как это иногда случается, когда порядок по a.id, а иногда при заказе по a.id DES C, так как пользователь может просматривать наборы ответов в порядке возрастания или убывания на странице) для другого survey_id.

В записи имеется около 6,2 миллиона записей таблица и для данного survey_id (1602673827) есть 45 800 записей. При использовании оператора EXPLAIN SELECT для понимания плана выполнения запроса я получил следующую информацию:

+----+-------------+-------+------------+-------+------------------------------------------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys                                        | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+------------------------------------------------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | index | survey_timestamp_idx,survey_timestamp_terminated_idx | PRIMARY | 4       | NULL | 6863 |     1.46 | Using where |
+----+-------------+-------+------------+-------+------------------------------------------------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

Теперь я не могу понять, что хотя indexes -> survey_timestamp_idx, survey_timestamp_terminated_idx ' присутствуют, почему MySQL не использует индексы и выбирает полное сканирование таблицы. Также, когда я изменяю запрос следующим образом:

SELECT * 
FROM response_set a USE INDEX (survey_timestamp_idx) 
WHERE a.survey_id = 1602673827 
ORDER BY a.id  limit 100;

Время выполнения запроса сокращается до 0,17 секунды. При выполнении EXPLAIN для измененного запроса я получаю следующую информацию:

+----+-------------+-------+------------+------+----------------------+----------------------+---------+-------+-------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys        | key                  | key_len | ref   | rows  | filtered | Extra                                 |
+----+-------------+-------+------------+------+----------------------+----------------------+---------+-------+-------+----------+---------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ref  | survey_timestamp_idx | survey_timestamp_idx | 4       | const | 87790 |   100.00 | Using index condition; Using filesort |
+----+-------------+-------+------------+------+----------------------+----------------------+---------+-------+-------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

Однако я не хочу явно использовать 'USE INDEX' в запросе, поскольку предложение where является динамическим c и может содержать следующие комбинации в предложении where в соответствии с выбором фильтра пользователем:

1. where survey_id = ?;
2. where survey_id = ? and t = ?; (t is timestamp)
3. where survey_id = ? and terminated_survey = ?;
4. where survey_id = ? and t = ? and terminated_survey = ?;

Кроме того, если я удаляю из запроса предложение ORDER BY, запрос всегда использует индекс и выполняется очень быстро.

Есть ли другой способ, чтобы механизм запросов MySQL выбирал правильный (более быстрый) план выполнения (используя правильные индексы), когда в запросе присутствует предложение ORDER BY?

Я использую MySQL версия: 5.7.22

Я прочитал MySQL официальную документацию для оптимизации запроса ORDER BY (https://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html ) и попытался добавить составной индекс для (id, survey_id) и (survey_id, id), но это не сработало. Может кто-нибудь помочь, пожалуйста?

1 Ответ

0 голосов
/ 20 января 2020
  1. где survey_id =?;
  2. где survey_id =? и т =?; (t является меткой времени)
  3. где survey_id =? и terminated_survey =?;
  4. где survey_id =? и т =? и terminated_survey =?;

Если у вас есть ORDER BY id ASC (or DESC), то вам нужно 4 индекса для оптимальной обработки всех из них. Начните с 1, 2 или 3 столбцов (в любом порядке), упомянутых в WHERE, затем заканчивайте sh с id.

Я не могу объяснить, почему KEY survey_idx (survey_id ) не использовался для рассматриваемого запроса, и этот индекс не являлся «возможным_ключем» в EXPLAIN. Как будто что-то изменилось между выполнением запросов и отправкой этого Вопроса. Пожалуйста, перепроверьте.

Кстати, INT(1) все еще занимает 4 байта; Вы, вероятно, хотели однобайтовый TINYINT UNSIGNED. Многие из других полей больше, чем необходимо. Размер играет на производительность, по крайней мере, немного.

0,17 с - может быть еще быстрее с FORCE INDEX(survey_idx)

Начиная с PRIMARY KEY (как в (id, survey_id)) почти всегда бесполезен. Индекс должен начинать вещи, которые тестируются с =, затем переходить к чему-то, что тестируется как диапазон или GROUP BY или (как в вашем случае), ORDER BY.

Кулинарная книга: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

...