MySql 没有为少数查询选择正确的索引

MySql not picking correct index for few queries

我正在 运行 对 table 进行查询,我正在更改 where 条件下的值,而 运行 在一种情况下采用一个索引,另一种情况采用这是另一个(错误的??)索引。

查询 1 的行数是 402954,大约需要 1.5 秒

查询 2 的行数是 52097,大约需要 35 秒

查询 1 和查询 2 都是相同的,只是我在 where 条件中更改了值

查询 1

EXPLAIN SELECT 
     log_type,count(DISTINCT subscriber_id) AS distinct_count,
     count(subscriber_id) as total_count 
FROM campaign_logs 
WHERE 
    domain = 'xxx' AND 
    campaign_id='123' AND 
    log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') AND 
    log_time BETWEEN 
       CONVERT_TZ('2015-02-12 00:00:00','+05:30','+00:00') AND
       CONVERT_TZ('2015-02-19 23:59:58','+05:30','+00:00') 
GROUP BY log_type;

上述查询的解释

+----+-------------+---------------+-------+------------------------------------------------------------------------------------------------------+-----------------------------------------+---------+------+--------+-------------+
| id | select_type | table         | type  | possible_keys                                                                                        | key                                     | key_len | ref  | rows   | Extra       |
+----+-------------+---------------+-------+------------------------------------------------------------------------------------------------------+-----------------------------------------+---------+------+--------+-------------+
|  1 | SIMPLE      | campaign_logs | range | campaign_id_index,domain_index,log_type_index,log_time_index,campaignid_domain_logtype_logtime_index | campaignid_domain_logtype_logtime_index | 468     | NULL | 402954 | Using where |
+----+-------------+---------------+-------+------------------------------------------------------------------------------------------------------+-----------------------------------------+---------+------+--------+-------------+

查询 2

EXPLAIN SELECT 
    log_type,count(DISTINCT subscriber_id) AS distinct_count,
    count(subscriber_id) as total_count 
FROM stats.campaign_logs 
WHERE 
    domain = 'yyy' AND 
    campaign_id='345' AND 
    log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') AND 
    log_time BETWEEN 
         CONVERT_TZ('2014-02-05 00:00:00','+05:30','+00:00') AND
         CONVERT_TZ('2015-02-19 23:59:58','+05:30','+00:00') 
GROUP BY log_type;

解释上述查询

+----+-------------+---------------+-------------+------------------------------------------------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+
| id | select_type | table         | type        | possible_keys                                                                                        | key                            | key_len | ref  | rows  | Extra                                                                        |
+----+-------------+---------------+-------------+------------------------------------------------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+
|  1 | SIMPLE      | campaign_logs | index_merge | campaign_id_index,domain_index,log_type_index,log_time_index,campaignid_domain_logtype_logtime_index | campaign_id_index,domain_index | 153,153 | NULL | 52097 | Using intersect(campaign_id_index,domain_index); Using where; Using filesort |
+----+-------------+---------------+-------------+------------------------------------------------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+

查询 1 使用了正确的索引,因为我有复合索引

查询 2 正在使用索引合并,执行需要很长时间

为什么 MySql 对同一查询使用不同的索引

我知道我们可以在查询中提及 USE INDEX,但为什么 MySql 在这种情况下没有选择正确的索引??。我做错了什么吗??

不,你没有做错任何事。

正如 Chipmonkey 在评论中所述,有时 MySQL 会因为过时的 table 统计信息而选择错误的执行计划。您可以通过执行 ANALYZE TABLE.

更新 table 统计信息

不过,MySQL 优化器并没有那么复杂。它看到在这两种情况下,MySQL 都必须访问两个二级索引,然后执行对聚簇索引的查找以获取实际的 table 数据,所以当它看到也许第二个查询有通过使用两个单独的索引并合并它们来提高选择性,你不能仅仅因为它猜错了就责怪它。

我猜如果你有一个 covering 索引,这样 MySQL 就可以只用这个索引执行整个查询,那么它会比执行该索引更有利合并。

尝试将 subscriber_id 添加到多列索引的末尾以获得覆盖索引。

否则,请使用 USE INDEXFORCE INDEX,因为这就是它们的用途。您比 MySQL 更了解数据。

我建议你试试这个:

添加复合索引的这个排列。

 (campaign_id,domain,log_time,log_type,subscriber_id)

更改您的查询以删除 WHERE log_type IN() 条件,从而允许聚合函数使用它在 log_time 上的范围扫描中找到的所有记录。在索引中包含 subscriber_id 应该允许直接从索引中满足整个查询。即这是一个覆盖索引.

最后,您可以通过将整个查询包装在

中来过滤 log_type
  SELECT *
    FROM (/*the whole query*/) x
   WHERE log_type IN 
        ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED')
   ORDER BY log_type

这应该会给您带来更好、更可预测的性能。

(除非你想要的 log_types 是记录的一小部分,在这种情况下请忽略此建议。)