Mysql
 sql >> Base de données >  >> RDS >> Mysql

Distinct vs Grouper par

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 (tableau A ) un par un dans l'ordre décroissant
  • Regardez s'il y a une correspondance dans le idx_order_id index (tableau B )
  • 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 que GROUP 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