Il est généralement conseillé d'utiliser DISTINCT
au lieu de GROUP BY
, puisque c'est ce que vous voulez réellement, et laissez l'optimiseur choisir le "meilleur" plan d'exécution. Cependant - aucun optimiseur n'est parfait. Utilisation de DISTINCT
l'optimiseur peut avoir plus d'options pour un plan d'exécution. Mais cela signifie également qu'il a plus d'options pour choisir un mauvais plan .
Vous écrivez que le DISTINCT
la requête est "lente", mais vous ne dites aucun nombre. Dans mon test (avec 10 fois plus de lignes sur MariaDB 10.0.19 et 10.3.13 ) le DISTINCT
la requête est comme (seulement) 25% plus lente (562ms/453ms). Le EXPLAIN
le résultat n'est d'aucune aide. C'est même "mentir". Avec LIMIT 100, 30
il aurait besoin de lire au moins 130 lignes (c'est ce que mon EXPLAIN
montre en fait pour GROUP BY
), mais il vous affiche 65.
Je ne peux pas expliquer la différence de 25 % dans le temps d'exécution, mais il semble que le moteur effectue une analyse complète de la table/de l'index dans tous les cas et trie le résultat avant de pouvoir sauter 100 et sélectionner 30 lignes.
Le meilleur plan serait probablement :
- Lire les lignes de
idx_reg_date
index (tableauA
) un par un dans l'ordre décroissant - Regardez s'il y a une correspondance dans le
idx_order_id
index (tableauB
) - Ignorer 100 lignes correspondantes
- Envoyer 30 lignes correspondantes
- Quitter
S'il y a environ 10 % de lignes dans A
qui n'ont pas de correspondance dans B
, ce plan lirait quelque chose comme 143 lignes de A
.
Le mieux que je puisse faire pour forcer ce plan est :
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
Cette requête renvoie le même résultat en 156 ms (3 fois plus rapide que GROUP BY
). Mais c'est encore trop lent. Et il lit probablement encore toutes les lignes de la table A
.
Nous pouvons prouver qu'un meilleur plan peut exister avec une "petite" astuce de sous-requête :
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
Cette requête s'exécute en "aucun temps" (~ 0 ms) et renvoie le même résultat sur mes données de test. Et même si ce n'est pas fiable à 100 %, cela montre que l'optimiseur ne fait pas du bon travail.
Alors quelles sont mes conclusions :
- L'optimiseur ne fait pas toujours le meilleur travail et a parfois besoin d'aide
- Même lorsque nous connaissons "le meilleur plan", nous ne pouvons pas toujours l'appliquer
DISTINCT
n'est pas toujours plus rapide queGROUP BY
- Lorsqu'aucun index ne peut être utilisé pour toutes les clauses, les choses deviennent assez délicates
Schéma de test et données factices :
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;
Requêtes :
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