MySQL:为什么IN子句中的第5个ID会大大改变查询计划?

时间:2018-08-10 20:52:32

标签: mysql sql indexing query-performance

给出以下两个查询:

查询#1

SELECT log.id
FROM log
WHERE user_id IN
      (188858, 188886, 189854, 203623, 204072)
      and type in (14, 15, 17)
ORDER BY log.id DESC
LIMIT 25 OFFSET 0;

查询#2-4个ID代替5

SELECT log.id
FROM log
WHERE user_id IN
      (188858, 188886, 189854, 203623)
      and type in (14, 15, 17)
ORDER BY log.id DESC
LIMIT 25 OFFSET 0;

说明计划

-- Query #1
1   SIMPLE  log range   idx_user_id_and_log_id  idx_user_id_and_log_id  4       41280   Using index condition; Using where; Using filesort
-- Query #2
1   SIMPLE  log index   idx_user_id_and_log_id  PRIMARY                 4       53534   Using where

为什么添加单个ID会使执行计划如此不同?我说的是毫秒到1分钟之间的时间差。我以为它可能与eq_range_index_dive_limit参数有关,但是无论如何它都低于10(默认值)。我知道我可以强制使用索引而不是clustered index,但是我想知道为什么MySQL决定这样做。

我应该尝试理解吗?还是有时候无法理解查询计划者的决定?

其他详细信息

  • 表大小:11GB
  • 行:1.08亿
  • MySQL:5.6.7
  • 从IN子句中删除哪个ID无关紧要。
  • 索引:idx_user_id_and_log_id(user_id, id)

3 个答案:

答案 0 :(得分:2)

正如您所显示的,MySQL有两个替代查询计划,用于使用ORDER BY ... LIMIT n的查询:

  1. 阅读所有符合条件的行,对它们进行排序,然后选择 n 顶行。
  2. 按排序顺序读取行,并在找到 n 个符合条件的行时停止。

为了确定哪个是更好的选择,优化器需要估计WHERE条件的过滤效果。这不是直截了当的,特别是对于没有索引的列或与值相关的列。在您的情况下,为了找到前25个符合条件的行,可能要比排序程序读取更多的表,而不是优化程序期望的行。

在5.6的更高版本(您正在GA之前的版本中运行!)和较新的版本(5.7、8.0)中,对LIMIT查询的处理方式都有了一些改进。我建议您尝试升级到更高版本,看看是否仍然存在问题。

通常,如果您想了解查询计划者的决定,则应查看查询的优化器跟踪。

答案 1 :(得分:0)

JOIN效率更高。

使用IN运算符的值创建一个临时表。 然后在表'log'和临时值表之间进行JOIN操作。

请参阅this answer 有关更多信息。

答案 2 :(得分:0)

添加

INDEX(user_id, type, id),
INDEX(type, user_id, id)

每个都是“覆盖”索引。这样,整个查询可以通过仅查看一个索引来执行,而无需触摸“数据”。

对于Optimizer,我有两种选择-希望它可以选择user_id IN (...)是更具选择性还是type IN (...)以选择更好的索引。

如果在添加这些元素后idx_user_id_and_log_id(user_id, id)DROP没有任何用途。

(不,我无法解释为什么查询2选择进行表扫描。)