SQL-запрос работает медленно и не использует индексы - PullRequest
0 голосов
/ 14 апреля 2019
SELECT `productTitle`, `orderCnt`, `promPCPriceStr`,
  `productImgUrl`, `oriPriceStr`, `detailUrl`,
  (SELECT count(id) FROM orders t4 
   WHERE t4.productId = t1.productId 
     AND DATE( t4.`date`) > DATE_SUB(CURDATE(), INTERVAL 2 DAY)
  ) as ordertoday
FROM `products` t1
WHERE `orderCnt` > 0 
 AND `orderCnt` < 2000 
 AND `promPCPriceStr` > 0 
 AND `promPCPriceStr` < 2000 
HAVING ordertoday > 5 AND ordertoday < 2000 
order by ordertoday desc limit 150

Этот запрос занимает 18 секунд, чтобы завершиться, когда я запускаю команду объяснения, он показывает, что

a busy cat

он не использует индексные ключи!

Используемые таблицы

Таблица продуктов

CREATE TABLE `products` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `productId` bigint(20) NOT NULL,
 `detailUrl` text CHARACTER SET utf32 NOT NULL,
 `belongToDSStore` int(11) NOT NULL,
 `promPCPriceStr` float NOT NULL DEFAULT '-1',
 `oriPriceStr` float NOT NULL DEFAULT '-1',
 `orderCnt` int(11) NOT NULL,
 `productTitle` text CHARACTER SET utf32 NOT NULL,
 `productImgUrl` text CHARACTER SET utf32 NOT NULL,
 `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `cat` bigint(20) NOT NULL DEFAULT '-1',
 PRIMARY KEY (`id`),
 UNIQUE KEY `productId` (`productId`),
 KEY `orderCnt` (`orderCnt`),
 KEY `cat` (`cat`),
 KEY `promPCPriceStr` (`promPCPriceStr`)
) ENGINE=InnoDB AUTO_INCREMENT=37773 DEFAULT CHARSET=latin1

Таблица заказов

CREATE TABLE `orders` (
 `oid` int(11) NOT NULL AUTO_INCREMENT,
 `countryCode` varchar(10) NOT NULL,
 `date` datetime NOT NULL,
 `id` bigint(20) NOT NULL,
 `productId` bigint(20) NOT NULL,
 PRIMARY KEY (`oid`),
 UNIQUE KEY `id` (`id`),
 KEY `date` (`date`),
 KEY `productId` (`productId`)
) ENGINE=InnoDB AUTO_INCREMENT=9790205 DEFAULT CHARSET=latin1

Ответы [ 2 ]

3 голосов
/ 14 апреля 2019

MySQL не будет использовать индекс, даже если он существует в столбце, который вы ищете, если искомые значения отображаются в большом подмножестве строк.

Я провел тест с MySQL 5.6. Я создал таблицу с ~ 1 000 000 строк со столбцом x со случайными значениями, равномерно распределенными между 1 и 1000. В столбце x.

есть индекс.

В зависимости от моих условий поиска, я вижу, что индекс используется, если я ищу диапазон значений, совпадающий с достаточно небольшим подмножеством строк, в противном случае он решает, что использование индекса является слишком большой проблемой, и просто выполняет сканирование таблицы:

mysql> explain select * from foo where x < 50;
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra                 |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+
|  1 | SIMPLE      | foo   | range | x             | x    | 4       | NULL | 102356 | Using index condition |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+

mysql> explain select * from foo where x < 100;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | foo   | ALL  | x             | NULL | NULL    | NULL | 1046904 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

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

WHERE `orderCnt` > 0 
 AND `orderCnt` < 2000 
 AND `promPCPriceStr` > 0 
 AND `promPCPriceStr` < 2000 

Если вы считаете, что MySQL делает неправильный выбор, вы можете попытаться использовать индексную подсказку , чтобы сообщить MySQL, что сканирование таблицы непомерно дорого. Это побудит его использовать индекс (если индекс относится к условию поиска).

mysql> explain select * from foo force index (x) where x < 100;
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra                 |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+
|  1 | SIMPLE      | foo   | range | x             | x    | 4       | NULL | 216764 | Using index condition |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-----------------------+

Я бы написал запрос таким образом, без какого-либо подзапроса:

SELECT t.productTitle, t.orderCnt, t.promPCPriceStr,
  t.productImgUrl, t.oriPriceStr, t.detailUrl,
  COUNT(o.id) AS orderToday
FROM products t
LEFT JOIN orders o ON t.productid = o.productid AND o.date > CURDATE() - INTERVAL 2 DAY
WHERE t.orderCnt > 0 AND t.orderCnt < 2000
 AND t.promPCPriceStr > 0 AND t.promPCPriceStr < 2000
GROUP BY t.productid
HAVING ordertoday > 5 AND ordertoday < 2000
ORDER BY ordertoday DESC LIMIT 150

Когда я ОБЪЯСНЯЮ запрос, я получаю этот отчет:

+----+-------------+-------+------+-----------------------------------+-----------+---------+------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys                     | key       | key_len | ref              | rows | Extra                                        |
+----+-------------+-------+------+-----------------------------------+-----------+---------+------------------+------+----------------------------------------------+
|  1 | SIMPLE      | t     | ALL  | productId,orderCnt,promPCPriceStr | NULL      | NULL    | NULL             | 9993 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | o     | ref  | date,productId                    | productId | 8       | test.t.productId |    1 | Using where                                  |
+----+-------------+-------+------+-----------------------------------+-----------+---------+------------------+------+----------------------------------------------+

Он все еще выполняет сканирование таблицы для products, но объединяет соответствующие совпадающие строки в orders с поиском по индексу вместо коррелированного подзапроса.

Я заполнил свои таблицы случайной датой, чтобы получить 98 846 строк товаров и 215 508 строк заказов. Когда я запускаю запрос, это занимает около 0,18 секунды.

Хотя, когда я запускаю ваш запрос с коррелированным подзапросом, это занимает 0,06 секунды. Я не знаю, почему ваш запрос такой медленный. Возможно, вы работаете на недостаточно мощном сервере.

Я запускаю тест на Macbook Pro 2017 с процессором i7 и 16 ГБ ОЗУ.

1 голос
/ 24 апреля 2019

В обеих таблицах контрпродуктивно иметь столбцы AUTO_INCREMENT PRIMARY KEY и BIGINT, равные UNIQUE. Избавьтесь от колонки ИИ и продвиньте другого в ПК. Это может потребовать изменения части вашего кода, так как столбец AI исчез.

Что касается подзапроса ...

  (SELECT count(id) FROM orders t4 
   WHERE t4.productId = t1.productId 
     AND DATE( t4.`date`) > DATE_SUB(CURDATE(), INTERVAL 2 DAY)
  ) as ordertoday

Измените COUNT(id) на COUNT(*), если вам не нужно проверить id на наличие NOT NULL (в чем я сомневаюсь).

Столбец date скрыт в вызове функции, поэтому индекс не будет полезен. Итак, измените дату теста на

AND t4.`date` > CURDATE - INTERVAL 2 DAY

Затем добавьте этот составной индекс. (Это также поможет с переформулировкой Карвина).

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