Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Distinto vs Agrupar por


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 (tabela A ) um por um em ordem decrescente
  • Veja se há uma correspondência no idx_order_id índice (tabela B )
  • 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 que GROUP 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