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 INDEX
或 FORCE 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 是记录的一小部分,在这种情况下请忽略此建议。)
我正在 运行 对 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
.
不过,MySQL 优化器并没有那么复杂。它看到在这两种情况下,MySQL 都必须访问两个二级索引,然后执行对聚簇索引的查找以获取实际的 table 数据,所以当它看到也许第二个查询有通过使用两个单独的索引并合并它们来提高选择性,你不能仅仅因为它猜错了就责怪它。
我猜如果你有一个 covering 索引,这样 MySQL 就可以只用这个索引执行整个查询,那么它会比执行该索引更有利合并。
尝试将 subscriber_id
添加到多列索引的末尾以获得覆盖索引。
否则,请使用 USE INDEX
或 FORCE 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 是记录的一小部分,在这种情况下请忽略此建议。)