MySQL:Использование индексов в подзапросах UNION
-
05-07-2019 - |
Вопрос
В MySQL 5.0.75-0ubuntu10.2
У меня есть фиксированный макет таблицы:
Стол parent
с таблицей удостоверения личности parent2
с таблицей удостоверения личности children1
с родительским идентификатором
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)
Может ли кто-нибудь объяснить, почему индексы (PRIMARY) больше не используются?Есть ли обходной путь этой проблемы, если это возможно, без необходимости изменения макета БД?
Спасибо!
Решение
Я думаю, что оптимизатор отваливается, как только начинаешь выдергивать несколько столбцов в производном запросе из-за возможности, что ему придется конвертировать типы данных при объединении (не в этом случае, а вообще).Это также может быть связано с тем, что ваш запрос по сути хочет быть коррелированным производным подзапросом, что невозможно (из-за 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)
Другие советы
Моя первая мысль — вставить «значительное» количество записей в таблицы и использовать ANALYZE TABLE для обновления статистики.Таблицу с 4 записями всегда будет быстрее читать при полном сканировании, чем при использовании индекса!Далее можно попробовать USE INDEX принудительно использовать индекс и посмотреть, как изменится план.
Я также рекомендую прочитать эту документацию и посмотреть, какие биты актуальны.MYSQL::Оптимизация запросов с помощью EXPLAIN
Эта статья также может быть полезна7 способов убедить MySQL использовать правильный индекс