2010-04-21 94 views
4

我有如下表(删除列未用于我的例子):为什么当我使用子查询时,我的查询没有使用任何索引?

CREATE TABLE `person` (
    `id` int(11) NOT NULL, 
    `name` varchar(1024) NOT NULL, 
    `sortname` varchar(1024) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `sortname` (`sortname`(255)), 
    KEY `name` (`name`(255)) 
); 

CREATE TABLE `personalias` (
    `id` int(11) NOT NULL, 
    `person` int(11) NOT NULL, 
    `name` varchar(1024) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `person` (`person`), 
    KEY `name` (`name`(255)) 
) 

目前,我正在使用此查询该工程只是罚款:

select p.* from person p where name = 'John Mayer' or sortname = 'John Mayer'; 

mysql> explain select p.* from person p where name = 'John Mayer' or sortname = 'John Mayer'; 
+----+-------------+-------+-------------+---------------+---------------+---------+------+------+----------------------------------------------+ 
| id | select_type | table | type  | possible_keys | key   | key_len | ref | rows | Extra          | 
+----+-------------+-------+-------------+---------------+---------------+---------+------+------+----------------------------------------------+ 
| 1 | SIMPLE  | p  | index_merge | name,sortname | name,sortname | 767,767 | NULL | 3 | Using sort_union(name,sortname); Using where | 
+----+-------------+-------+-------------+---------------+---------------+---------+------+------+----------------------------------------------+ 
1 row in set (0.00 sec) 

现在的我d想扩展这个查询来考虑别名。

首先,我使用连接尝试:

select p.* from person p join personalias a on p.id = a.person where p.name = 'John Mayer' or p.sortname = 'John Mayer' or a.name = 'John Mayer'; 

mysql> explain select p.* from person p join personalias a on p.id = a.person where p.name = 'John Mayer' or p.sortname = 'John Mayer' or a.name = 'John Mayer'; 
+----+-------------+-------+--------+-----------------------+---------+---------+-------------------+-------+-----------------+ 
| id | select_type | table | type | possible_keys   | key  | key_len | ref    | rows | Extra   | 
+----+-------------+-------+--------+-----------------------+---------+---------+-------------------+-------+-----------------+ 
| 1 | SIMPLE  | a  | ALL | ref,name    | NULL | NULL | NULL    | 87401 | Using temporary | 
| 1 | SIMPLE  | p  | eq_ref | PRIMARY,name,sortname | PRIMARY | 4  | musicbrainz.a.ref |  1 | Using where  | 
+----+-------------+-------+--------+-----------------------+---------+---------+-------------------+-------+-----------------+ 
2 rows in set (0.00 sec) 

情况不妙:没有索引,87401行,使用临时。仅当使用distinct时才会出现使用临时文件,但作为别名可能与名称相同,我无法真正摆脱它。

接下来,我试图替换子查询联接:

select p.* from person p where p.name = 'John Mayer' or p.sortname = 'John Mayer' or p.id in (select person from personalias a where a.name = 'John Mayer'); 

mysql> explain select p.* from person p where p.name = 'John Mayer' or p.sortname = 'John Mayer' or p.id in (select id from personalias a where a.name = 'John Mayer'); 
+----+--------------------+-------+----------------+------------------+--------+---------+------+--------+-------------+ 
| id | select_type  | table | type   | possible_keys | key | key_len | ref | rows | Extra  | 
+----+--------------------+-------+----------------+------------------+--------+---------+------+--------+-------------+ 
| 1 | PRIMARY   | p  | ALL   | name,sortname | NULL | NULL | NULL | 540309 | Using where | 
| 2 | DEPENDENT SUBQUERY | a  | index_subquery | person,name  | person | 4  | func |  1 | Using where | 
+----+--------------------+-------+----------------+------------------+--------+---------+------+--------+-------------+ 
2 rows in set (0.00 sec) 

再次,这看起来很糟糕:没有索引,540309行。有趣的是,这两个查询(​​和select id from personalias a where a.name = 'John Mayer')工作得非常好。

为什么MySQL没有为我的两个查询使用任何索引?我还能做什么?目前,它最好为别名获取person.ids,并将它们静态添加到第二个查询中的(...)中。肯定有另一种方法来做到这一点与一个单一的查询。尽管如此,我目前没有想法。我能以某种方式迫使MySQL使用另一个(更好的)查询计划吗?

+0

为什么在你的连接中没有'ON'子句? – RedFilter 2010-04-21 14:07:45

+0

@OrbMan谢谢,它只是在解释的查询中,忘了修复它在另一个。 – sfussenegger 2010-04-21 14:13:54

回答

1

请尝试:

SELECT p.* from person p 
WHERE p.name = 'John Mayer' or p.sortname = 'John Mayer' 

UNION 

SELECT p.* from person p, personalias a 
WHERE p.id =a.person and a.name = 'John Mayer' 

UNION将照顾清晰。

+0

+1谢谢,像魅力一样工作 – sfussenegger 2010-04-21 14:26:13

4

在第一个查询中,只有一个表。

MySQL使用index merge:它采用来自两个索引的行指针并将它们联合起来。

你的第二个查询引入了另一个表。由于记录指针不同,因此MySQL无法组合来自另一个表的索引。

您需要仿效这一点:

SELECT p.* 
FROM (
     SELECT id 
     FROM person p 
     WHERE p.name = 'John Mayer' 
       OR p.sortname = 'John Mayer' 
     UNION 
     SELECT person 
     FROM personalias a 
     WHERE a.name = 'John Mayer' 
     ) q 
JOIN person p 
ON  p.id = q.id 

如果你的表是MyISAM,包括id作为尾随列索引:

CREATE INDEX ix_person_name_id ON (name, id); 
CREATE INDEX ix_person_sortname_id ON (sortname, id); 
CREATE INDEX ix_personalias_name_person (name, person); 

另外请注意,对于这样的疑问,这是更好使用FULLTEXT索引:

CREATE FULLTEXT INDEX fx_person_name_sortname ON person (name, sortname); 

SELECT p.* 
FROM (
     SELECT id 
     FROM person p 
     WHERE MATCH (name, sortname) AGAINST ('"John Mayer"' IN BOOLEAN MODE) 
     UNION 
     SELECT person 
     FROM personalias a 
     WHERE a.name = 'John Mayer' 
     ) q 
JOIN person p 
ON  p.id = q.id 
+0

+1:很好的答案! – RedFilter 2010-04-21 14:20:32

+0

+1谢谢你的回答,效果很好。太糟糕了,我不能接受两个答案。 – sfussenegger 2010-04-21 14:25:44

相关问题