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

Posted

技术标签:

【中文标题】MySql 没有为少数查询选择正确的索引【英文标题】:MySql not picking correct index for few queries 【发布时间】:2015-02-24 14:06:34 【问题描述】:

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

【问题讨论】:

有时像这样糟糕的查询优化与糟糕的统计数据有关——还有很多事情要做,MySQL 在这里记录了它:dev.mysql.com/doc/innodb/1.1/en/…。考虑运行 ANALYZE TABLE 来更新有关索引分布的统计信息,然后重新运行解释。 【参考方案1】:

不,你没有做错任何事。

正如 Chipmonkey 在 cmets 中所说,有时 MySQL 会因为过时的表统计信息而选择错误的执行计划。您可以通过执行ANALYZE TABLE 来更新表统计信息。

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

我猜如果您有一个 覆盖 索引,以便 MySQL 可以仅使用该索引执行整个查询,那么它会支持该索引而不是执行合并。

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

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

【讨论】:

@Adams ,感谢您的评论,该表有 2500 万多条记录并且它有 8 列,所以我的问题是在 5 列上覆盖索引是否安全,我怀疑它会增加磁盘空间。 是的,它会增加磁盘空间(理想情况下是内存占用)。还有什么专栏? 这里还有一列是subscriber_id 这个问题是反问的。索引中已经有 4 列。为什么现在担心 5 列?【参考方案2】:

我建议你试试这个:

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

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

【讨论】:

这是真的。 MySQL 只能使用索引搜索到第一个范围。删除 log_type 的范围要求应该会提高性能,但稍后在外部查询中添加它可能会得到混合结果。如果这样做,您还必须从 covering 索引中删除 log_type。

以上是关于MySql 没有为少数查询选择正确的索引的主要内容,如果未能解决你的问题,请参考以下文章

MySql,我不确定我的索引是不是正确,或者我是不是可以提高查询速度?

MySQL如何创建正确的索引

MySQL-分区表

MySQL---正确使用索引limit分页执行计划慢日志查询

通过 Join-Where-Group 通过选择查询避免使用临时的正确索引;使用文件排序

Mysql-如何正确的使用索引以及索引的原理