相同的查询,MySQL 5.5 和 5.7 上的执行时间不同。 (MySQL 5.5 未使用索引)

Same query, different execution time on MySQL 5.5 and 5.7. (MySQL 5.5 not using the index)

出于兼容性原因,我不得不将生产数据库从 MySQL 5.7 降级到 MySQL 5.5。

迁移到 5.5 后,我注意到这个查询变得更慢了,从大约 200 毫秒到大约 20 秒的执行时间。

查询如下:

SELECT
  COUNT(*)
FROM
  `calendar`
INNER JOIN
  `spot` ON `spot`.`product` = `calendar`.`product`
        AND `spot`.`company_id` = `calendar`.`company_id`
INNER JOIN
  `detection` ON `detection`.`spot_id` = `spot`.`id`
WHERE `calendar`.`starts_at` = '2017-11-17'
  AND `calendar`.`user_id` = 73
  AND `detection`.`date` >= '2017-11-17'
  AND `detection`.`date` <= '2017-11-23'

这是 MySQL 5.5 的 EXPLAIN 输出:

1 SIMPLE | calendar | ref starts_at_ends_at_index starts_at_ends_at_index 3 const 1204 | Using where
1 SIMPLE | spot ref PRIMARY,company_id_index,product_index | product_index | 302 calendar.product | 13 | Using where
1 SIMPLE | detection | ref spot_id_index,date_index | spot_id_index 48 | spot.Id | 80 | Using where

这是 MySQL 5.7 的 EXPLAIN 输出:

1 SIMPLE | calendar | ref starts_at_ends_at_index starts_at_ends_at_index 3 const 1204 | Using where
1 SIMPLE | spot ref PRIMARY,company_id_index,product_index | product_index | 302 calendar.product | 13 | Using index condition; Using where
1 SIMPLE | detection | ref spot_id_index,date_index | spot_id_index 48 | spot.Id | 80 | Using where

我能看到的唯一区别是 MySQL 5.7 使用:Using index condition; Using where on product_index,5.5 不使用。

我试图通过指定 USE INDEX(product_index) 来强制使用索引,但没有任何改变

有什么建议吗?

编辑:

当前有用的索引:

ALTER TABLE `calendar` ADD INDEX `starts_at_ends_at_index` (`starts_at`, `ends_at`);

ALTER TABLE `spot` ADD INDEX `company_id_index` (`company_id`);

ALTER TABLE `spot` ADD INDEX `product_index` (`product`);

ALTER TABLE `detection` ADD INDEX `spot_id_index` (`spot_id`);

ALTER TABLE `detection` ADD INDEX `date_index` (`date`);

我会尝试将不过滤日历 table 的 where 子句谓词移动到连接谓词中,如果没有别的,它有助于提高可读性,但也可以帮助引擎编译更优化的计划。

SELECT 
    COUNT(*)
FROM
    `calendar`
INNER JOIN `spot` 
    ON `spot`.`product` = `calendar`.`product` 
    AND `spot`.`company_id` = `calendar`.`company_id`
INNER JOIN `detection` 
    ON `detection`.`spot_id` = `spot`.`id`
    AND `detection`.`date` BETWEEN '2017-11-17' AND '2017-11-23'  
WHERE
    `calendar`.`starts_at` = '2017-11-17' 
    AND `calendar`.`user_id` = 73

也有可能索引在降级后需要重建,您可以使用以下方法对每个 table 执行此操作。

OPTIMIZE TABLE `calendar`;
OPTIMIZE TABLE `spot`;
OPTIMIZE TABLE `detection`;

虽然 table 是 运行,但它确实会锁定 table,因此在生产数据库中请记住这一点。

最后,spot.productcalendar.product的外键还是相反?它们是完全相同的数据类型吗?

您的查询通过两个相等条件过滤 calendar,因此它们应该出现在彼此相同的索引中。然后它使用 product 列访问另一个 table。因此,将这三列合并为一列 compound index。试试这个:

 ALTER TABLE calendar ADD INDEX user_id_starts_at_product (user_id, starts_at, product);

您的查询对 detection 进行了数据范围筛选,并且还选择了具有特定值 spot_id 的行。所以试试这个复合索引。

 ALTER TABLE detection ADD INDEX spot_id_date (spot_id, date);

也可以尝试使用逆序列的复合索引,并保留性能更好的索引。

 ALTER TABLE detection ADD INDEX date_spot_id (date, spot_id);

尝试在 spot 上使用复合索引来涵盖两个过滤条件(出现在您的 ON 子句中)。

  ALTER TABLE spot ADD INDEX company_id_product (company_id, product);

专业提示:MySQL 通常每个查询(或子查询)的每个 table 只能使用一个索引。因此,添加大量单列索引通常不是加快特定查询速度的好方法。相反,添加符合查询要求的复合索引是可行的方法。对于各种数据库版本都是如此。