Как я могу оптимизировать запрос, который выполняет ORDER BY для производного столбца в MySQL? - PullRequest
0 голосов
/ 25 августа 2010

У меня проблемы с оптимизацией относительно простого запроса, включающего GROUP BY, ORDER BY и LIMIT. В таблице чуть более 300 000 записей. Вот схема (я добавил несколько дополнительных индексов для экспериментов):

CREATE TABLE `scrape_search_results` (
  `id` int(11) NOT NULL auto_increment,
  `creative_id` int(11) NOT NULL,
  `url_id` int(11) NOT NULL,
  `access_date` datetime NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `creative_url_index` (`creative_id`,`url_id`),
  KEY `access_date_index` (`access_date`),
  KEY `access_date_creative_id_index` (`access_date`,`creative_id`),
  KEY `creative_id_access_date_index` (`creative_id`,`access_date`),
  KEY `test_index` USING HASH (`creative_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4252725 DEFAULT CHARSET=latin1

В таблице один creative_id может появляться несколько (сотни) раз. Вопрос, на который я пытаюсь ответить, является относительно простым; дай мне первые 20 creative_ids по заказу access_date. Вот мой SQL:

SELECT `ScrapeSearchResult`.`creative_id`, 
        MAX(`ScrapeSearchResult`.`access_date`) AS `latest_access_date` 
FROM `scrape_search_results` AS `ScrapeSearchResult` 
WHERE 1 = 1 
GROUP BY `ScrapeSearchResult`.`creative_id` 
ORDER BY `latest_access_date` DESC 
LIMIT 20;

Вот результаты выполнения этого запроса, где мы видим, что 20-е место по величине access_date равно 2010-08-23 11: 03: 25:

+-------------+---------------------+
| creative_id | latest_access_date  |
+-------------+---------------------+
|         550 | 2010-08-23 11:07:49 | 
|        4568 | 2010-08-23 11:07:49 | 
|         552 | 2010-08-23 11:07:49 | 
|        2109 | 2010-08-23 11:07:49 | 
|        5221 | 2010-08-23 11:07:49 | 
|        1544 | 2010-08-23 11:07:49 | 
|        1697 | 2010-08-23 11:07:49 | 
|         554 | 2010-08-23 11:07:12 | 
|         932 | 2010-08-23 11:05:48 | 
|       11029 | 2010-08-23 11:05:37 | 
|       11854 | 2010-08-23 11:05:27 | 
|       11856 | 2010-08-23 11:05:05 | 
|         702 | 2010-08-23 11:03:56 | 
|        4319 | 2010-08-23 11:03:56 | 
|        7159 | 2010-08-23 11:03:56 | 
|       10610 | 2010-08-23 11:03:46 | 
|        5540 | 2010-08-23 11:03:46 | 
|           1 | 2010-08-23 11:03:46 | 
|       11942 | 2010-08-23 11:03:35 | 
|        7900 | 2010-08-23 11:03:25 | 
+-------------+---------------------+

Если бы я собирался написать этот алгоритм вручную, я бы построил b-дерево, упорядоченное по (access_date, creative_id). Я начинаю с MAX(access_date) и продолжаю ходить по дереву, пока не найду 20 уникальных creative_ids, которые затем вернусь в том порядке, в котором я их нашел.

Используя этот алгоритм, мне нужно было бы рассмотреть только 94 строки (есть 94 строки, для которых access_date >= 2010-08-23 11:03:25, что является нашим 20-м по величине access_date, как показано выше).

Однако MySQL решает использовать creative_url_index при ответе на этот запрос, чего я не понимаю. При этом учитывается более 10000 строк.

ANALYZE TABLE scrape_search_results;
SELECT ...;
+----+-------------+--------------------+-------+---------------+--------------------+---------+------+-------+---------------------------------+
| id | select_type | table              | type  | possible_keys | key                | key_len | ref  | rows  | Extra                           |
+----+-------------+--------------------+-------+---------------+--------------------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | ScrapeSearchResult | index | NULL          | creative_url_index | 8       | NULL | 10687 | Using temporary; Using filesort | 
+----+-------------+--------------------+-------+---------------+--------------------+---------+------+-------+---------------------------------+

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

1 Ответ

4 голосов
/ 25 августа 2010

Некоторое время в MySQL я такого не делал (давно перешел на PostgtreSQL), но обычно я бы справился с этим с помощью концентрических выборок, чтобы обмануть планировщик запросов и дать хороший план.

SELECT * FROM 
(SELECT `ScrapeSearchResult`.`creative_id`, 
        MAX(`ScrapeSearchResult`.`access_date`) AS `latest_access_date` 
FROM `scrape_search_results` AS `ScrapeSearchResult` 
WHERE 1 = 1 
GROUP BY `ScrapeSearchResult`.`creative_id` 

) as inner
ORDER BY `latest_access_date` DESC 
LIMIT 20;

Успех этого будет зависеть только от разумного количества общих строк во внутреннем.

Я только что посмотрел документы для MySQL 5.6, и он выглядит вот так должно работать ... даже в MySQL;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...