MySQL: использование индексов в подвыборах UNION - PullRequest
1 голос
/ 22 октября 2009

В MySQL 5.0.75-0ubuntu10.2 У меня есть фиксированный макет таблицы, такой:

Таблица parent с идентификатором Таблица parent2 с идентификатором Таблица children1 с parentId

CREATE TABLE  `Parent` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(200) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB
CREATE TABLE  `Parent2` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(200) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB
CREATE TABLE  `Children1` (
  `id` int(11) NOT NULL auto_increment,
  `parentId` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `parent` (`parentId`)
) ENGINE=InnoDB

У ребенка есть родитель в одной из таблиц Parent или Parent2. Когда мне нужно получить детей, я использую такой запрос:

select * from Children1 c 
inner join (
select id as parentId from Parent
union 
select id as parentId from Parent2
) p on p.parentId = c.parentId

Объясняя этот запрос дает:

+----+--------------+------------+-------+---------------+---------+---------+------+------+-----------------------------------------------------+
| id | select_type  | table      | type  | possible_keys | key     | key_len | ref  | rows | Extra                                               |
+----+--------------+------------+-------+---------------+---------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY      | NULL       | NULL  | NULL          | NULL    | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables | 
|  2 | DERIVED      | Parent     | index | NULL          | PRIMARY | 4       | NULL |    1 | Using index                                         | 
|  3 | UNION        | Parent2    | index | NULL          | PRIMARY | 4       | NULL |    1 | Using index                                         | 
| NULL | UNION RESULT | <union2,3> | ALL   | NULL          | NULL    | NULL    | NULL | NULL |                                                     | 
+----+--------------+------------+-------+---------------+---------+---------+------+------+-----------------------------------------------------+
4 rows in set (0.00 sec)

, что разумно с учетом макета.

Теперь проблема: предыдущий запрос несколько бесполезен, так как он не возвращает столбцы из родительских элементов. Сейчас, когда я добавляю больше столбцов во внутренний запрос, индекс больше не будет использоваться:

mysql> explain select * from Children1 c  inner join ( select id as parentId,name from Parent union  select id as parentId,name from Parent2 ) p on p.parentId = c.parentId;
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY      | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables | 
|  2 | DERIVED      | Parent     | ALL  | NULL          | NULL | NULL    | NULL |    1 |                                                     | 
|  3 | UNION        | Parent2    | ALL  | NULL          | NULL | NULL    | NULL |    1 |                                                     | 
| NULL | UNION RESULT | <union2,3> | ALL  | NULL          | NULL | NULL    | NULL | NULL |                                                     | 
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
4 rows in set (0.00 sec)

Может кто-нибудь объяснить, почему (ПЕРВИЧНЫЕ) индексы больше не используются? Есть ли обходной путь для этой проблемы, если это возможно, без необходимости изменения структуры БД?

Спасибо!

Ответы [ 2 ]

1 голос
/ 16 декабря 2009

Я думаю, что оптимизатор падает, когда вы начинаете извлекать несколько столбцов в производном запросе из-за возможности того, что ему потребуется преобразовать типы данных в объединении (не в этом случае, а в целом). Это также может быть связано с тем, что ваш запрос по сути хочет быть коррелированным производным подзапросом, что невозможно (из dev.mysql.com ):

Подзапросы в предложении FROM не могут быть коррелированными подзапросами, если только они не используются в предложении ON операции JOIN.

То, что вы пытаетесь сделать (но не действует):

select * from Children1 c 
inner join (
select id as parentId from Parent where Parent.id = c.parentId
union 
select id as parentId from Parent2 where Parent.id = c.parentId
) p 

Result: "Unknown column 'c.parentId' in 'where clause'.

Есть ли причина, по которой вы не предпочитаете два левых соединения и IFNULL:

select *, IFNULL(p1.name, p2.name) AS name from Children1 c
left join Parent p1 ON p1.id = c.parentId
left join Parent2 p2 ON p2.id = c.parentId

Единственная разница между запросами состоит в том, что в вашем случае вы получите две строки, если в каждой таблице есть родитель. Если это то, что вы хотите / нужно, тогда это также будет работать хорошо, и объединения будут быстрыми и всегда будут использовать индексы:

(select * from Children1 c join Parent p1 ON p1.id = c.parentId)
union
(select * from Children1 c join Parent2 p2 ON p2.id = c.parentId)
0 голосов
/ 22 октября 2009

Моя первая мысль - вставить «значительное» количество записей в таблицы и использовать ANALYZE TABLE для обновления статистики. Таблицу с 4 записями всегда будет быстрее читать при полном сканировании, а не через индекс! Кроме того, вы можете попробовать ИСПОЛЬЗОВАТЬ ИНДЕКС, чтобы принудительно использовать индекс и посмотреть, как меняется план.

Я также рекомендую прочитать эту документацию и посмотреть, какие биты актуальны MYSQL :: Оптимизация запросов с помощью EXPLAIN

Эта статья также может быть полезна 7 способов убедить MySQL использовать правильный индекс

...