如何避免对此mysql查询进行全表扫描?

时间:2012-12-31 23:23:04

标签: mysql

explain
select
    *
from
    zipcode_distances z 
inner join
    venues v    
    on z.zipcode_to=v.zipcode
inner join
    events e
    on v.id=e.venue_id
where
    z.zipcode_from='92108' and
    z.distance <= 5

我正试图在5英里内的邮政编码92108找到所有“活动”,但是,我很难优化这个查询。

以下是解释的内容:

id, select_type, table, type, possible_keys, key, key_len, ref, rows, Extra

1, SIMPLE, e, ALL, idx_venue_id, , , , 60024, 
1, SIMPLE, v, eq_ref, PRIMARY,idx_zipcode, PRIMARY, 4, comedyworld.e.venue_id, 1, 
1, SIMPLE, z, ref, idx_zip_from_distance,idx_zip_to_distance,idx_zip_from_to, idx_zip_from_to, 30, const,comedyworld.v.zipcode, 1, Using where; Using index

我正在“e”表上进行全表扫描,我无法弄清楚我需要创建哪个索引才能让它变快。

任何建议都将不胜感激

谢谢

4 个答案:

答案 0 :(得分:8)

根据您问题中的EXPLAIN输出,您已经拥有查询使用的所有索引,即:

CREATE INDEX idx_zip_from_distance
  ON zipcode_distances (zipcode_from, distance, zipcode_to);
CREATE INDEX idx_zipcode ON venues (zipcode, id);
CREATE INDEX idx_venue_id ON events (venue_id);

(我不确定你的索引名称idx_zip_from_distance是否真的包含zipcode_to列。如果没有,你应该添加它以使其成为covering index。而且,我'我们在venues.id中包含idx_zipcode列以确保完整性,但是,假设它是表格的主键并且您正在使用InnoDB,则无论如何都会自动包含它。)

然而,看起来MySQL正在选择一个不同的,可能是次优的查询计划,它会扫描所有事件,找到他们的场地和邮政编码,然后才能过滤距离上的结果。如果事件表的基数足够低,那么可以是最佳查询计划,但是从你提出这个问题的事实我认为不是。

次优查询计划的一个原因可能太多索引,这会使计划程序混乱。例如,你真的是否需要zipcode表上的所有这三个索引,因为它存储的数据可能是对称的?就个人而言,我只建议我在上面描述的索引,加上一个唯一的索引(如果你没有人工的那个也可以是主键)(zipcode_to, zipcode_from)(最好是按顺序,这样任何偶然的zipcode_to=?查询都可以使用它。)

然而,基于我所做的一些测试,我怀疑MySQL选择错误的查询计划的主要问题仅仅取决于表的相对基数。据推测,你的实际zipcode_distances表是巨大的,并且MySQL不够聪明,无法意识到WHERE子句中的条件确实缩小了它的范围。

如果是这样,最简单的解决办法可能只是force MySQL to use the indexes you want

select
    *
from
    zipcode_distances z 
    FORCE INDEX (idx_zip_from_distance)
inner join
    venues v    
    FORCE INDEX (idx_zipcode)
    on z.zipcode_to=v.zipcode
inner join
    events e
    FORCE INDEX (idx_venue_id)
    on v.id=e.venue_id
where
    z.zipcode_from='92108' and
    z.distance <= 5

使用该查询,您确实应该获得所需的查询计划。 (这里确实需要FORCE INDEX,因为只有USE INDEX,查询规划者仍然可以决定使用表扫描而不是建议的索引,从而无法实现目的。当我第一次测试时,我就会发生这种情况。 )

聚苯乙烯。这是一个关于SQLize的演示,包括withwithout FORCE INDEX,演示了这个问题。

答案 1 :(得分:1)

是否已为两个表中的列编制索引?

e.id and v.venue_id

如果不这样做,请在两个表中创建索引。如果您已经拥有,那么可能是您在一个或多个表中有少量记录,并且分析器检测到执行完整扫描而不是索引读取更有效。

答案 2 :(得分:0)

您可以使用子查询:

select * from zipcode_distances z, venues v, events e
where
    z.id in (select id from zipcode z where z.zipcode_from='92108' and z.distance <= 5)
    and z.zipcode_to=v.zipcode
    and v.id=e.venue_id

答案 3 :(得分:-1)

您正在选择所有表(select *)中的所有列,因此当查询引擎必须在每一行上从索引到表进行查找时,优化器中使用索引几乎没有意义。