Geralmente é aconselhável usar
DISTINCT
em vez de GROUP BY
, já que é isso que você realmente quer, e deixe o otimizador escolher o "melhor" plano de execução. No entanto - nenhum otimizador é perfeito. Usando DISTINCT
o otimizador pode ter mais opções para um plano de execução. Mas isso também significa que ele tem mais opções para escolher um plano ruim . Você escreve que o
DISTINCT
consulta é "lenta", mas você não diz nenhum número. No meu teste (com 10 vezes mais linhas no MariaDB 10.0.19 e 10.3.13 ) o DISTINCT
consulta é como (apenas) 25% mais lenta (562ms/453ms). A EXPLAIN
resultado não ajuda em nada. É até "mentindo". Com LIMIT 100, 30
ele precisaria ler pelo menos 130 linhas (é o que meu EXPLAIN
na verdade, mostra para GROUP BY
), mas mostra 65. Não posso explicar a diferença de 25% no tempo de execução, mas parece que o mecanismo está fazendo uma verificação completa de tabela/índice em qualquer caso e classifica o resultado antes que ele possa pular 100 e selecionar 30 linhas.
O melhor plano provavelmente seria:
- Ler linhas de
idx_reg_date
índice (tabelaA
) um por um em ordem decrescente - Veja se há uma correspondência no
idx_order_id
índice (tabelaB
) - Pular 100 linhas correspondentes
- Enviar 30 linhas correspondentes
- Sair
Se houver 10% de linhas em
A
que não têm correspondência em B
, esse plano leria algo como 143 linhas de A
. O melhor que eu poderia fazer para forçar de alguma forma esse plano é:
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Esta consulta retorna o mesmo resultado em 156 ms (3 vezes mais rápido que
GROUP BY
). Mas isso ainda é muito lento. E provavelmente ainda está lendo todas as linhas na tabela A
. Podemos provar que um plano melhor pode existir com um "pequeno" truque de subconsulta:
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Esta consulta é executada "sem tempo" (~ 0 ms) e retorna o mesmo resultado nos meus dados de teste. E embora não seja 100% confiável, mostra que o otimizador não está fazendo um bom trabalho.
Então, quais são minhas conclusões:
- O otimizador nem sempre faz o melhor trabalho e às vezes precisa de ajuda
- Mesmo quando conhecemos "o melhor plano", nem sempre podemos aplicá-lo
DISTINCT
nem sempre é mais rápido queGROUP BY
- Quando nenhum índice pode ser usado para todas as cláusulas - as coisas estão ficando muito complicadas
Esquema de teste e dados fictícios:
drop table if exists `order`;
CREATE TABLE `order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_reg_date` (`reg_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into `order`(reg_date)
select from_unixtime(floor(rand(1) * 1000000000)) as reg_date
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 218860;
drop table if exists `order_detail_products`;
CREATE TABLE `order_detail_products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint(20) unsigned NOT NULL,
`order_detail_id` int(11) NOT NULL,
`prod_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_detail_id` (`order_detail_id`,`prod_id`),
KEY `idx_order_id` (`order_id`,`order_detail_id`,`prod_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into order_detail_products(id, order_id, order_detail_id, prod_id)
select null as id
, floor(rand(2)*218860)+1 as order_id
, 0 as order_detail_id
, 0 as prod_id
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 437320;
Dúvidas:
SELECT DISTINCT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 562 ms
SELECT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
GROUP BY A.id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 453 ms
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 156 ms
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- ~ 0 ms