SQL查询速度慢且未使用索引

时间:2019-04-14 18:53:46

标签: mysql sql performance

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 个答案:

答案 0 :(得分:3)

即使您搜索的列上存在索引,即使您搜索的值出现在行的较大子集上,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做出了错误的选择,则可以尝试使用index hint来告诉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秒。我不知道您的查询为什么这么慢。您可能正在电源不足的服务器上运行。

我正在使用i7 CPU和16GB RAM的Macbook Pro 2017上运行测试。

答案 1 :(得分:1)

在两个表中,同时具有AUTO_INCREMENT PRIMARY KEYBIGINT的{​​{1}}列会适得其反。摆脱AI列,将另一个提升为PK。由于AI列已消失,因此可能需要更改一些代码。

关于子查询...

UNIQUE

(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

然后添加此复合索引。 (这也将有助于Karwin的改写)。

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