DISTINCT导致全表扫描

时间:2014-05-07 23:31:41

标签: mysql sql select left-join distinct

我在MySQL(5.5.31)中有一个表,它有大约20M行。以下查询:

SELECT DISTINCT mytable.name name FROM mytable 
LEFT JOIN mytable_c ON mytable_c.id_c = mytable.id
WHERE mytable.deleted = 0  ORDER BY mytable.date_modified DESC  LIMIT 0,21

导致全表扫描,解释说typeALL,额外信息为Using where; Using temporary; Using filesort。解释结果:

  id  select_type table           type    possible_keys   key     key_len ref         rows        Extra
  1   SIMPLE      mytable         ALL     NULL            NULL    NULL    NULL        19001156    Using where; Using temporary; Using filesort
  1   SIMPLE      mytable_c       eq_ref  PRIMARY         PRIMARY 108     mytable.id  1           Using index

没有连接解释如下:

  id  select_type table           type    possible_keys   key         key_len ref         rows        Extra
  1   SIMPLE      mytable         index   NULL            mytablemod  9       NULL        21          Using where; Using temporary

id_cmytable_c的主键,而mytable_c mytable中的每一行都没有多行。 date_modified已编入索引。但看起来MySQL并不理解。如果我删除了DISTINCT子句,那么explain使用索引并且只按预期触摸21行。如果我删除了连接,它也会这样做。有没有办法让它在没有连接的全表扫描的情况下工作? explain显示mysql知道它只需要来自mytable_c的一行并且它正在使用主键,但仍在mytable上执行完全扫描。

DISTINCT的原因是ORM系统生成查询,其中可能存在JOIN生成多行的情况,但SELECT字段的值将始终是唯一的(即,如果JOIN是针对多个值链接只有每个连接行中相同的字段将在SELECT中。

2 个答案:

答案 0 :(得分:1)

这些只是通用注释,而不是特定于mysql。

要查找name中所有可能的mytable值,需要对表或索引进行全面扫描。可能的选择:

  • 全桌扫描
  • deleted开头的索引的完整索引扫描(利用过滤器)
  • name开头的索引的完整索引扫描(仅输出关注列)

如果deleted上有索引,则服务器可以找到所有deleted = 0索引条目,然后从表中查找相应的name值。但是如果deleted基数较低或者统计数据不同,那么首先对索引进行双重读取然后对应的数据项可能会更加昂贵。在这种情况下,只需扫描表格。

如果name上有索引,则索引扫描就足够了,但是需要检查表以查找过滤器。再次频繁地从索引跳到桌面。

还需要以类似的方式考虑连接列。

如果您忘记了连接部分并且在列namedeleted上有多部分索引,那么可能会发生索引扫描。

更新

对我来说,DISTINCTORDER BY部分有点令人困惑。其中name条记录是用于排序的date_modified?我觉得这样的事情会更清楚一点:

SELECT mytable.name name --, MIN(mytable.date_modified)
  FROM mytable 
  LEFT JOIN mytable_c ON mytable_c.id_c = mytable.id
  WHERE mytable.deleted = 0
  GROUP BY mytable.name
  ORDER BY MIN(mytable.date_modified) DESC  LIMIT 0,21

无论哪种方式,一旦ORDER BY发挥作用,就需要进行全面扫描才能找到订单。没有ORDER BY,找到的前21个就足够了。

答案 1 :(得分:0)

为什么不尝试将条件 mytable.deleted = 0 从WHERE移到JOIN ON?您也可以尝试FORCE INDEX(mytablemod)