当我使用子查询时,为什么我的查询不使用任何索引?

时间:2010-04-21 13:58:25

标签: mysql

我有以下表格(删除了未用于我的示例的列):

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)

现在我想扩展此查询以考虑别名。

首先,我尝试过使用连接:

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 p.* from person ... or p.id in (4711,12345)select id from personalias a where a.name = 'John Mayer')的效果非常好。

为什么MySQL不为我的两个查询使用任何索引?我还能做什么?目前,最好为别名获取person.ids,并将其作为in(...)静态添加到第二个查询中。当然,必须有另一种方法来执行单个查询。我目前没有想法。我可以以某种方式强迫MySQL使用另一个(更好的)查询计划吗?

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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将照顾清晰度。