sql >> Base de Datos >  >> RDS >> Mysql

Distinto vs Agrupar por

Por lo general, se recomienda usar DISTINCT en lugar de GROUP BY , ya que eso es lo que realmente desea, y deje que el optimizador elija el "mejor" plan de ejecución. Sin embargo, ningún optimizador es perfecto. Usando DISTINCT el optimizador puede tener más opciones para un plan de ejecución. Pero eso también significa que tiene más opciones para elegir un mal plan .

Escribes que el DISTINCT la consulta es "lenta", pero no dice ningún número. En mi prueba (con 10 veces más filas en MariaDB 10.0.19 y 10.3.13 ) el DISTINCT la consulta es como (solo) un 25% más lenta (562ms/453ms). El EXPLAIN El resultado no es de ayuda en absoluto. Incluso es "mentir". Con LIMIT 100, 30 necesitaría leer al menos 130 filas (eso es lo que mi EXPLAIN en realidad se muestra para GROUP BY ), pero te muestra 65.

No puedo explicar la diferencia del 25 % en el tiempo de ejecución, pero parece que el motor está realizando un análisis completo de tabla/índice en cualquier caso y ordena el resultado antes de que pueda omitir 100 y seleccionar 30 filas.

El mejor plan probablemente sería:

  • Leer filas desde idx_reg_date índice (tabla A ) uno por uno en orden descendente
  • Mira si hay una coincidencia en el idx_order_id índice (tabla B )
  • Omitir 100 filas coincidentes
  • Enviar 30 filas coincidentes
  • Salir

Si hay como el 10 % de las filas en A que no tienen coincidencia en B , este plan leería algo así como 143 filas de A .

Lo mejor que podría hacer para forzar este plan de alguna manera es:

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 devuelve el mismo resultado en 156 ms (3 veces más rápido que GROUP BY ). Pero eso sigue siendo demasiado lento. Y probablemente todavía esté leyendo todas las filas en la tabla A .

Podemos probar que puede existir un mejor plan con un "pequeño" truco 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 se ejecuta "sin tiempo" (~ 0 ms) y devuelve el mismo resultado en mis datos de prueba. Y aunque no es 100 % confiable, muestra que el optimizador no está haciendo un buen trabajo.

Entonces, ¿cuáles son mis conclusiones:

  • El optimizador no siempre hace el mejor trabajo y, a veces, necesita ayuda
  • Incluso cuando conocemos "el mejor plan", no siempre podemos hacerlo cumplir
  • DISTINCT no siempre es más rápido que GROUP BY
  • Cuando no se puede usar un índice para todas las cláusulas, las cosas se están poniendo bastante complicadas

Esquema de prueba y datos ficticios:

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;

Consultas:

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