2017-04-13 1 views
0

J'ai une table comme ceci:MySql GROUP BY en utilisant filesort - l'optimisation des requêtes

CREATE TABLE `purchase` (
    `fact_purchase_id` binary(16) NOT NULL, 
    `purchase_id` int(10) unsigned NOT NULL, 
    `purchase_id_primary` int(10) unsigned DEFAULT NULL, 
    `person_id` int(10) unsigned NOT NULL, 
    `person_id_owner` int(10) unsigned NOT NULL, 
    `service_id` int(10) unsigned NOT NULL, 
    `fact_count` int(10) unsigned NOT NULL DEFAULT '0', 
    `fact_type` tinyint(3) unsigned NOT NULL, 
    `date_fact` date NOT NULL, 
    `purchase_name` varchar(255) DEFAULT NULL, 
    `activation_price` decimal(7,2) unsigned NOT NULL DEFAULT '0.00', 
    `activation_price_total` decimal(7,2) unsigned NOT NULL DEFAULT '0.00', 
    `renew_price` decimal(7,2) unsigned DEFAULT '0.00', 
    `renew_price_total` decimal(7,2) unsigned NOT NULL DEFAULT '0.00', 
    `activation_cost` decimal(7,2) unsigned DEFAULT '0.00', 
    `activation_cost_total` decimal(7,2) unsigned NOT NULL DEFAULT '0.00', 
    `renew_cost` decimal(7,2) unsigned DEFAULT '0.00', 
    `renew_cost_total` decimal(7,2) unsigned NOT NULL DEFAULT '0.00', 
    `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (`fact_purchase_id`), 
    KEY `purchase_id_idx` (`purchase_id`), 
    KEY `person_id_idx` (`person_id`), 
    KEY `person_id_owner_idx` (`person_id_owner`), 
    KEY `service_id_idx` (`service_id`), 
    KEY `fact_type_idx` (`fact_type`), 
    KEY `renew_price_idx` (`renew_price`), 
    KEY `renew_cost_idx` (`renew_cost`), 
    KEY `renew_price_year_idx` (`renew_price_year`), 
    KEY `renew_cost_year_idx` (`renew_cost_year`), 
    KEY `date_created_idx` (`date_created`), 
    KEY `purchase_id_primary_idx` (`purchase_id_primary`), 
    KEY `fact_count` (`fact_count`), 
    KEY `renew_price_year_total_idx` (`renew_price_total`), 
    KEY `renew_cost_year_total_idx` (`renew_cost_total`), 
    KEY `date_fact` (`date_fact`) USING BTREE, 
    CONSTRAINT `purchase_person_fk` FOREIGN KEY (`person_id`) REFERENCES `person` (`person_id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `purchase_person_owner_fk` FOREIGN KEY (`person_id_owner`) REFERENCES `person` (`person_id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `purchase_service_fk` FOREIGN KEY (`service_id`) REFERENCES `service` (`service_id`) ON DELETE NO ACTION ON UPDATE NO ACTION 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

Je lance cette requête:

SELECT 
    purchase.date_fact, 
    UNIX_TIMESTAMP(purchase.date_fact), 
    COUNT(DISTINCT purchase.purchase_id) AS Num 
FROM 
    purchase 
WHERE 
    purchase.date_fact >= '2017-01-01' 
    AND purchase.date_fact <= '2017-01-31' 
    AND purchase.fact_type = 3 
    AND purchase.purchase_id_primary IS NULL 
GROUP BY purchase.date_fact 

Le tableau contient un total de 5.629.670 dossiers et l'exécution d'une EXPLAIN sur la requête que je reçois ces résultats:

  • rows = 2.814.835
  • possible_keys = fact_type_idx,purchase_id_primary_idx,date_fact
  • key = fact_type_idx
  • key_len = 1
  • ref = const
  • filtered = 25,00
  • Extra = Using index condition;Using where;Using filesort

La requête prend 30-35 seco nds à exécuter. C'est trop long à attendre.

Le problème est que GROUP BY provoque l'application de fichiers. L'application de ORDER BY NULL à la requête ne change rien.

Je pourrais éventuellement utiliser un index de couverture, mais j'ai juste besoin de date_fact dans cette requête: quels champs puis-je utiliser?

Comment est-ce que je peux éviter le filesort sur GROUP BY? Comment puis-je optimiser la requête pour la rendre plus rapide? J'utilise cette table à des fins statistiques (OLAP). Peut-être y at-il un meilleur SGBD à cette fin?

Je cours MySql Server 5.7.17.

Merci

Répondre

2

Pour cette requête:

SELECT p.date_fact, UNIX_TIMESTAMP(p.date_fact), 
     COUNT(DISTINCT p.purchase_id) AS Num 
FROM purchase p 
WHERE p.date_fact >= '2017-01-01' AND 
     p.date_fact <= '2017-01-31' AND 
     p.fact_type = 3 AND 
     p.purchase_id_primary IS NULL 
GROUP BY p.date_fact; 

je recommande un indice composé sur (fact_type, purchase_id_primary, date_fact, purchase_id). Les deux premières clés ont des conditions d'égalité dans le WHERE. Le troisième a une inégalité, et le quatrième permet à l'index de "couvrir" la requête (toutes les colonnes de la requête sont dans l'index).

Je voudrais également ajouter: si vous n'avez pas besoin de COUNT(DISTINCT), ne l'utilisez pas. purchase_id peut déjà être unique dans purchase.