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

时间:2015-02-24 14:06:35

标签: mysql sql query-optimization database-indexes

我正在对表执行以下查询,我正在更改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在这种情况下没有选择正确的索引?我做错了吗?

2 个答案:

答案 0 :(得分:3)

不,你没有做错任何事。

正如Chipmonkey在评论中所述,由于过时的表统计信息,MySQL有时会选择错误的执行计划。您可以通过执行ANALYZE TABLE来更新表统计信息。

但是,MySQL优化器并不那么复杂。它发现,在这两种情况下,MySQL都必须访问二级索引,然后执行查找聚簇索引以获取实际的表数据,因此当它看到第二个查询可能通过使用两个单独的索引具有更好的选择性时并合并它们,你不能因为它猜错而过分责备它。

我猜测如果你有一个覆盖索引,以便MySQL只用索引执行整个查询,那么它将优先于该索引执行合并。

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

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

答案 1 :(得分:1)

我建议你试试这个:

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

 (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是记录的一小部分,在这种情况下请忽略这个建议。)