2014-05-07 60 views
0

我在MySQL(5.5.31)中有一个约有20M行的表。下面的查询:DISTINCT导致全表扫描

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_cmytable_c主键不具备的每一行不止一行在mytabledate_modified已编入索引。但看起来像MySQL不明白这一点。如果我删除了DISTINCT子句,那么explain使用索引,并且只触及21行就像预期的那样。如果我删除连接它也这样做。如果没有连接的全表扫描,有没有办法让它工作? explain显示mysql知道它只需要mytable_c中的一行,它正在使用主键,但仍在mytable上进行全面扫描。

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

+0

对于没有列选择的表,您有一个外连接。我不明白。 – Strawberry

+0

@Strawberry这个查询有点简单,但它*仍*导致全表扫描。这是奇怪的部分 - 无论我是否包含其他表的字段,都会发生全表扫描。 – StasM

+0

试图回答这种问题而没有看到a)解释和b)正确的DDLs是愚蠢的。 (并不是说即使有这些信息我也能做得更好!) – Strawberry

回答

1

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

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

  • 全表扫描
  • 开始 deleted(利用过滤器)开始 name(仅用于输出关注的列的索引的
  • 全索引扫描索引的
  • 全索引扫描)

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

如果在name上有索引,索引扫描可能就足够了,但是需要检查该表以检查过滤器。再次频繁跳转从索引到表格。

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

如果您忘记了连接部分并在列name,deleted上有多部分索引,则可能会发生索引扫描。

更新

对我来说,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个就足够了。

+0

删除的确有倾向于非常低的选择性,它只有两个值(0和1),对于几乎所有的行都是0.但它不需要找到'name'的所有值,只有21个不同的值?的确,这就是没有加入的情况。但加入后,它会进行全面扫描。为什么? – StasM

+0

@StasM通过以下链接查看关于独特和排序的内容:http://dev.mysql.com/doc/refman/5.7/en/distinct-optimization.html – Glenn

+0

但是,这并非如此 - 即使订单已满除非有联接,否则不会发生扫描。它也不应该发生,因为来自date_modified索引的前21条记录完成查询(所有名称都是不同的),并且不需要额外的记录。只是注意到我忘记提及 - 'date_modified'有一个索引。 – StasM

0

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