2016-08-02 35 views
5

Ich habe die folgenden Tabellen in Frage:Wie kann ich die Leistung bei einer DISTINCT-Auswahl über drei verbundene Tabellen verbessern?

  • Personas
  • ImpressionsPersonas [Tisch sitzen - Personas ManyToMany Impressionen]
  • Impressionen

Meine Frage wie folgt aussieht, die EXPLAIN Ergebnisse gebunden sind, unten:

SELECT 

    DISTINCT (Personas.id), 
    Personas.parent_id, 
    Personas.persona, 
    Personas.subpersonas_count, 
    Personas.is_subpersona, 
    Personas.impressions_count, 
    Personas.created, 
    Personas.modified 


    FROM personas as Personas 

    INNER JOIN 
     impressions_personas ImpressionsPersonas ON (
     Personas.id = ImpressionsPersonas.persona_id 
    ) 
    inner JOIN impressions Impressions ON (Impressions.id = ImpressionsPersonas.impression_id AND Impressions.timestamp >= "2016-06-01 00:00:00" AND Impressions.timestamp <= "2016-07-31 00:00:00") 

ERKLÄREN

+----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+ 
    | id | select_type | table    | type | possible_keys               | key   | key_len | ref           | rows | filtered | Extra     | 
    +----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+ 
    | 1 | SIMPLE  | Personas   | ALL | PRIMARY                | NULL  | NULL | NULL          | 159 | 100.00 | Using temporary  | 
    | 1 | SIMPLE  | ImpressionsPersonas | ref | impression_idx,persona_idx,comp_imp_persona,comp_imp_pri,comp_per_pri | persona_idx | 8  | gen1_d2go.Personas.id      | 396 | 100.00 | Distinct    | 
    | 1 | SIMPLE  | Impressions   | eq_ref | PRIMARY,timestamp,timestamp_id          | PRIMARY  | 8  | gen1_d2go.ImpressionsPersonas.impression_id | 1 | 100.00 | Using where; Distinct | 
    +----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+ 
    3 rows in set, 1 warning (0.00 sec) 

Anweisung CREATE FÜR PERSONAS

CREATE TABLE `personas` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `parent_id` bigint(20) unsigned DEFAULT NULL, 
    `persona` varchar(150) NOT NULL, 
    `subpersonas_count` int(10) unsigned DEFAULT '0', 
    `is_subpersona` tinyint(1) unsigned DEFAULT '0', 
    `impressions_count` bigint(20) unsigned DEFAULT '0', 
    `created` datetime DEFAULT NULL, 
    `modified` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `lookup` (`parent_id`,`persona`), 
    KEY `parent_index` (`parent_id`), 
    KEY `persona` (`persona`), 
    KEY `persona_a_id` (`id`,`persona`), 
    CONSTRAINT `self_referential_join_to_self` FOREIGN KEY (`parent_id`) REFERENCES `personas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION 
) ENGINE=InnoDB AUTO_INCREMENT=1049 DEFAULT CHARSET=utf8; 

CREATE STATEMENT FÜR IMPRESSIONS_PERSONAS

CREATE TABLE `impressions_personas` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `impression_id` bigint(20) unsigned NOT NULL, 
    `persona_id` bigint(20) unsigned NOT NULL, 
    `created` datetime DEFAULT NULL, 
    `modified` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `impression_idx` (`impression_id`), 
    KEY `persona_idx` (`persona_id`), 
    KEY `comp_imp_persona` (`impression_id`,`persona_id`), 
    KEY `comp_imp_pri` (`impression_id`,`id`), 
    KEY `comp_per_pri` (`persona_id`,`id`), 
    CONSTRAINT `impression` FOREIGN KEY (`impression_id`) REFERENCES `impressions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, 
    CONSTRAINT `persona` FOREIGN KEY (`persona_id`) REFERENCES `personas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION 
) ENGINE=InnoDB AUTO_INCREMENT=19387839 DEFAULT CHARSET=utf8; 

RECHNUNG FÜR IMPRESSIONEN CREATE

CREATE TABLE `impressions` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `device_id` bigint(20) unsigned NOT NULL, 
    `beacon_id` bigint(20) unsigned NOT NULL, 
    `zone_id` bigint(20) unsigned NOT NULL, 
    `application_id` bigint(20) unsigned DEFAULT NULL, 
    `timestamp` datetime NOT NULL, 
    `google_place_id` bigint(20) unsigned DEFAULT NULL, 
    `name` varchar(60) DEFAULT NULL, 
    `lat` decimal(15,10) DEFAULT NULL, 
    `lng` decimal(15,10) DEFAULT NULL, 
    `personas_count` int(10) unsigned DEFAULT '0', 
    `created` datetime DEFAULT NULL, 
    `modified` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `device_idx` (`device_id`), 
    KEY `zone_idx` (`zone_id`), 
    KEY `beacon_id_idx2` (`beacon_id`), 
    KEY `timestamp` (`timestamp`), 
    KEY `appid_fk_idx_idx` (`application_id`), 
    KEY `comp_lookup` (`device_id`,`beacon_id`,`timestamp`), 
    KEY `timestamp_id` (`timestamp`,`id`), 
    CONSTRAINT `appid_fk_idx` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `beacon_id` FOREIGN KEY (`beacon_id`) REFERENCES `beacons` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, 
    CONSTRAINT `device2` FOREIGN KEY (`device_id`) REFERENCES `devices` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 
    CONSTRAINT `zone_FK` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION 
) ENGINE=InnoDB AUTO_INCREMENT=1582724 DEFAULT CHARSET=utf8; 

jetzt - wenn ich die Abfrage ohne DISTINCT laufen und mit einem COUNT(*), zieht es über 17 Millionen Datensätze. Das Ausführen mit DISTINCT ergibt 112 Datensätze. Ich bin nicht sicher, warum es so viele Aufzeichnungen, die zeigen, wenn das nur 159 und 396.

Einige Informationen über die Tabellen zeigten explain:

Die Personas Tabelle enthält 159 Datensätze. Die ImpressionsPersonas-Tabelle enthält etwa 12,6 Millionen und Impressions enthält etwa 920.000 Datensätze.

Wir wählen die Tabelle Personas aus und verbinden uns mit den Impressions über die Join-Tabelle ImpressionsPersonas. Auf die Tabelle Impressions werden Filter angewendet (Datum in diesem Fall).

Hinweis: Das Entfernen des Datumsfilters hatte einen vernachlässigbaren Einfluss auf die Ausführungszeit - die ungefähr um 120s schwebt. Gibt es eine Möglichkeit, diese Datensätze zu filtern, um die Ausführungszeit dieser Abfrage zu reduzieren?

+0

Würde die ganze Verbindung in 'IN' ändern oder etwas helfen? Nicht sicher, wie die Engine die Dinge handhabt, aber es scheint ein bisschen sinnlos zu sein, wenn man nur eindeutige IDs findet und dann diese wenigen Datensätze aus der Personas-Tabelle holt. –

+0

gut detailiing – tharif

+1

Etwas in den Zeilen von 'select * from personas wo id in (select distinct (persona_id) von impressions_personas inner JOIN Impressionen Impressionen ON Impressions.id = ImpressionsPersonas.impression_id UND Impressions.timestamp> =" 2016-06-01 00 : 00: 00 "UND Impressions.timestamp <=" 2016-07-31 00:00:00 "))'. Auch nicht sicher, ob Gruppe von unterscheidet sich gut von DISTINCT –

Antwort

2

Ich nehme an, dass Sie die Liste der Personen zu bekommen, die mindestens 1 Eindruck innerhalb eines bestimmten Zeitraums haben.Um dies zu umgehen, können Sie eine solche korrelierten Unterabfrage verwenden:

SELECT 
    Personas.id, 
    Personas.parent_id, 
    Personas.persona, 
    Personas.subpersonas_count, 
    Personas.is_subpersona, 
    Personas.impressions_count, 
    Personas.created, 
    Personas.modified 

    FROM personas as Personas 
    WHERE EXISTS(SELECT 1 FROM impressions_personas 
     LEFT JOIN impressions Impressions ON 
      Impressions.id = ImpressionsPersonas.impression_id 
     WHERE Personas.id = ImpressionsPersonas.persona_id 
      AND Impressions.timestamp >= "2016-06-01 00:00:00" 
      AND Impressions.timestamp <= "2016-07-31 00:00:00" 
    ) 
+0

Dies konnte mich zur Lösung bringen - danke! –

0

Erstellen Sie eine INDEX auf timestamp Spalte impressions Tabelle. Und sehen Sie, wenn verbessert sonst versuchen Sie den erstellten Index in der Abfrage (forcing index).

UPDATE

Verwenden INDEX im JOIN

SELECT 
DISTINCT (Personas.id), 
Personas.parent_id, 
Personas.persona, 
Personas.subpersonas_count, 
Personas.is_subpersona, 
Personas.impressions_count, 
Personas.created, 
Personas.modified 
FROM 
    personas as Personas 
INNER JOIN 
    impressions_personas ImpressionsPersonas ON (
    Personas.id = ImpressionsPersonas.persona_id 
) 
INNER JOIN 
    impressions Impressions WITH(INDEX(timestamp)) ON 
    (Impressions.id = ImpressionsPersonas.impression_id AND 
    Impressions.timestamp >= "2016-06-01 00:00:00" AND 
    Impressions.timestamp <= "2016-07-31 11:59:59") 
+0

KEY 'timestamp' (' timestamp'), –

0

Im Moment sind Sie ersten drei Tabellen mit Millionen von Zeilen verbinden und dann DISTINCT mit nur ein paar Zeilen aus ihm heraus. Besser ist es, zuerst nur die benötigten IDs zu bekommen und diese dann zur Auswahl der eigentlichen Ergebnisdaten zu verwenden.

Zum Beispiel:

SELECT column, other FROM personas 
WHERE id IN 
    (SELECT distinct persona_id 
    FROM impressions_personas 
    INNER JOIN impressions Impressions 
     ON Impressions.id = ImpressionsPersonas.impression_id 
     AND Impressions.timestamp >= "2016-06-01 00:00:00" 
     AND Impressions.timestamp <= "2016-07-31 00:00:00")) 

diese Weise wird der Motor mit einer einzigen Spalte für das gesamte Verfahren nur behandelt, die Ergebnisse bis zu bekommen.