如何使用ON cond1或cond2阻止SQL INNER JOIN忽略键并执行全表扫描

时间:2014-03-13 07:22:27

标签: mysql sql optimization

我有一个简单的查询,它不能按预期工作。尽管有索引,但查询的连接部分会忽略它并执行全表扫描。这是查询

SELECT m0.id_field, 
       attr_73217_   
  FROM object_73195_ o
       INNER JOIN master_slave m0 
           ON  (   m0.id_object = 73130 
                OR m0.id_object = 82344) 
           AND (   m0.id_master = 73195 
                OR m0.id_master = 82413) 
           AND m0.id_slave_field = o.id 
ORDER BY
       o.id_order

EXPLAIN命令返回以下行:

id  select_type table   type    possible_keys                                                               key     key_len ref                       rows  Extra
1   SIMPLE      m0      ALL     id_object,id_master,id_slave_field,id_slave_field_2,id_object_2,id_object_3 \N      \N      \N                        2782  Using where; Using temporary; Using filesort
1   SIMPLE      o       eq_ref  PRIMARY                                                                     PRIMARY 8       project.m0.id_slave_field 1     Using where

正如您所看到的,它不使用密钥,即使它是这样创建的:

ALTER TABLE master_slave ADD INDEX (id_object,id_master,id_slave_field); 

有趣的是,如果我从m0.id_field部分注释掉SELECT,那么首先选择类型(由explain命令给出)变为range,查询开始使用键id_object_3以及非常重要的 - 它现在扫描master_slave表中较少的行数。但问题是,我m0.id_field部分需要select。我想我需要对我的指数做些什么,但我不知道到底是什么。

修改 我试图添加另一个这样的键:

ALTER TABLE master_slave ADD INDEX (id_field); 
ALTER TABLE master_slave ADD INDEX (id_object); 

但是EXPLAIN命令返回的是同一组行 - 没有键和全表扫描。整个问题是由select部分中的m0.id_field引起的。

修改

我刚刚在master_slave表中添加了一堆索引:

ALTER TABLE master_slave ADD INDEX (id_field,id_object,id_master,id_slave_field);
ALTER TABLE master_slave ADD INDEX (id_object,id_field,id_master,id_slave_field);
ALTER TABLE master_slave ADD INDEX (id_object,id_master,id_field,id_slave_field);
ALTER TABLE master_slave ADD INDEX (id_object,id_master,id_slave_field,id_field);

每个索引都会降低扫描行数。我特别感谢 kordirko

1 个答案:

答案 0 :(得分:1)

@Jacobian - 这不是你问题的答案 或者只是部分回答 我在这里写作是因为我的解释太长而且不适合评论。


如果select语句不包含m0.id_field,则查询仅引用m0表中的3个字段:id_object,id_master,id_slave_field。
由于该表的3列中存在覆盖索引,因此显而易见的选择是扫描此索引而不是表。索引(磁盘上的索引文件)比表小得多,读取索引的成本低于读取表。


当索引包含查询检索到的所有必需列时,我们说覆盖索引,并且查询可以直接从索引检索所有信息 - >见:http://en.wikipedia.org/wiki/Database_index#Covering_index


如果将m0.id_field添加到select子句中,则没有包含所有这4列的索引,在这种情况下,查询必须从表中读取此列的值。
它可以通过两种方式实现:
1.使用索引过滤行,然后使用从索引获得的主键(逐行 - 随机访问)访问表中的行。
2.扫描整个表格,不触及任何索引

在预期行数较小(<5%可能<10%的表)的情况下,第一种方法是好的。请记住,DBMS无法从磁盘读取一行,它始终必须读取整页!要获得一个大小的行,例如50个字节,它必须读取整个页面,其大小为5k或10k或更多(页面的长度取决于设置)。可以进行一些优化,例如MySql,在扫描索引时,首先在内存中收集PK值,然后对它们进行排序,最后使用这些PK按升序扫描表,以最小化从磁盘检索的页数。但它仍然是随机访问,它比顺序读取慢(磁盘必须寻找随机轨道的头部,而不是按轨道读取轨道)

如果预期的行数很大(在我们的例子中是表的34%),使用第二种方法(扫描整个表)要比首次扫描和过滤索引便宜得多,然后对扫描结果进行排序,然后使用从索引中检索的PK访问表。必须从磁盘读取的最终磁盘页数较少(扫描索引也必须从磁盘读取一些页面)。