MySQL: Uso de índices em subselects UNIÃO
-
05-07-2019 - |
Pergunta
Em MySQL 5.0.75-0ubuntu10.2
Eu tenho um layout de tabela fixo assim:
parent
Tabela com um id
parent2
mesa com um id
children1
mesa com um 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
A criança tem um pai em um dos Parent
tabelas ou Parent2
. Quando eu preciso para obter uma criança eu uso uma consulta assim:
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
Explicando este rendimentos de consulta:
+----+--------------+------------+-------+---------------+---------+---------+------+------+-----------------------------------------------------+
| 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)
que é razoável dado o layout.
Agora o problema: a consulta anterior é um pouco inútil, uma vez que retorna sem colunas dos elementos pai. No momento em que eu adicionar mais colunas para a consulta interna será utilizado nenhum índice mais:
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)
Alguém pode explicar por que os índices (primário) não são usados ??mais? Existe uma solução para este problema, se possível, sem ter que mudar o layout DB?
Obrigado!
Solução
Eu acho que o otimizador cai uma vez que você começar a puxar para fora várias colunas na consulta derivados por causa da possibilidade que seria necessário para converter tipos de dados sobre a união (não neste caso, mas em geral). Ele também pode ser devido ao fato de que sua consulta essencialmente quer ser uma subconsulta derivado correlacionados, o que não é possível (a partir de dev.mysql.com ):
Subqueries na cláusula FROM não pode ser subconsultas correlacionadas, a não ser utilizados na cláusula ON de um JOIN operação.
O que você está tentando fazer (mas não é válido) é:
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'.
Existe uma razão você não prefere dois esquerda junções e IFNULLs:
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
A única diferença entre as consultas é que, em seu você vai ter duas linhas se houver uma mãe em cada mesa. Se é isso que você quer / precisa, então isso vai funcionar bem também e se junta vai ser rápido e sempre fazer uso dos índices:
(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)
Outras dicas
Meu primeiro pensamento é inserir um número "significativo" de registros nas tabelas e uso ANALYZE TABLE para atualizar as estatísticas. Uma mesa com 4 registros será sempre mais rápido para ler usando uma varredura completa em vez de ir através do índice! Além disso, você pode tentar USE INDEX para forçar o uso do índice e olhar como as mudanças de plano.
Além disso, vou recomendar a leitura desta documentação e veja quais bits são relevantes MYSQL :: otimização de consultas com EXPLICAR
Este artigo também pode ser útil 7 maneiras de convencer MySQL para usar o indicador direito