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 (tablaA
) uno por uno en orden descendente - Mira si hay una coincidencia en el
idx_order_id
índice (tablaB
) - 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 queGROUP 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