Ich arbeite an einer Abfrage , wo ich verwenden müssen IN, BETWEEN, GROUP BY, JOIN, ORDER BYalle in einer Abfrage. Ich habe Probleme mit der Leistung dieser Abfrage, daher benötige ich Hilfe bei der Auswahl von Indizes oder bei Änderungen an Tabellenstrukturen, wenn Indizes nicht helfen.
Einige Überlegungen
- Die Anzahl der Zeilen für die beiden folgenden Tabellen ist in
millions. - Es gibt Funktionen in dem Benutzerliste filtern können
name,age,genderusw. - Es gibt Funktionen , wo Benutzerliste sortieren kann durch einige Metriken wie
age,visits_countusw. - Brauchen Sie Paginierung für Liste.
Tabellenstrukturen
Tabelle 1
CREATE TABLE `table_1` (
`visitor_id` varchar(32) CHARACTER SET ascii NOT NULL,
`name` varchar(200) NOT NULL,
`gender` varchar(1) NOT NULL DEFAULT 'M',
`mobile_number` int(10) unsigned DEFAULT NULL,
`age` tinyint(1) unsigned NOT NULL DEFAULT '1',
`visits_count` mediumint(5) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`visitor_id`),
KEY `indx_t1_test` (`visitor_id`,`visits_count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Tabelle 2
CREATE TABLE `table_2` (
`company_id` bigint(20) unsigned NOT NULL,
`visitor_id` varchar(32) CHARACTER SET ascii NOT NULL,
`time_duration` mediumint(5) unsigned NOT NULL DEFAULT '0',
`visited_on` date NOT NULL,
PRIMARY KEY (`company_id`,`visitor_id`,`visited_on`),
KEY `indx_t2_test` (`visited_on`,`company_id`,`visitor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Die meisten Basisdaten, die ich abrufen möchte
Sie möchten 20 (Paginierung) eindeutige Besucher ( GROUP BY / DISTINCT) erhalten, die eine bestimmte Unternehmensgruppe ( INTeil) zwischen dem ausgewählten Zeitraum ( BETWEENTeil) und der Reihenfolge ihres Alters ( ORDER BYTeil) besucht haben.
Abfrage 1
Erste Abfrage, wenn ich dafür aufschreibe, dann wäre es:
SELECT
t1.visitor_id
FROM table_1 AS t1
INNER JOIN table_2 AS t2 ON t2.visitor_id = t1.visitor_id
WHERE
t2.company_id IN (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455) AND
t2.visited_on BETWEEN '2015-01-01' AND '2017-01-31'
GROUP BY t1.visitor_id
ORDER BY t1.`visits_count` DESC
LIMIT 20;
Wenn ich diese Abfrage für ein einzelnes Unternehmen ausführe, werden Daten schnell genug zurückgegeben (wenn die Anzahl der übereinstimmenden Zeilen gering ist, ist die Abfrageleistung gut).
Das Problem ist, dass es einige INZeit dauert, 36 secondsbis das Ergebnis zurückgegeben wird, wenn die Anzahl der Unternehmen in einem Teil der Abfrage zunimmt (ich muss 100 Unternehmen für diesen Teil der Abfrage unterstützen) .
Explain Die Ausgabe dieser Abfrage lautet:
Abfrage 2
Zweite Abfrage, die ich mir für den gleichen Fall vorstellen kann, dann wäre es ungefähr so:
SELECT
(
SELECT
t2.visitor_id
FROM table_2 AS t2
WHERE
t2.company_id IN (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455)
AND t2.visitor_id = t1.`visitor_id`
AND t2.visited_on BETWEEN '2015-01-01' AND '2017-01-31'
LIMIT 1
) AS visitor_id
FROM `table_1` AS t1
HAVING visitor_id IS NOT NULL
ORDER BY t1.`visits_count` DESC
LIMIT 0, 20
Das Verhalten dieser Abfrage ist dem ersten entgegengesetzt. Wenn ich eine Abfrage für ein Unternehmen mit wenigen Besuchern ausführe, ist die Leistung dieser Abfrage sehr gering (es dauerte ungefähr 38 seconds) (nur ein Unternehmen INteilweise und dieses Unternehmen hat nur 3-4 Besucher). Wenn die Anzahl der Unternehmen INteilweise hoch ist, werden die Ergebnisse im Vergleich zu einem Unternehmen schneller zurückgegeben (es dauerte ungefähr 13 seconds), aber die Leistung ist immer noch nicht nutzbar.
Explain Die Ausgabe dieser Abfrage lautet:
Abfrage 3
Um die Verwendung eines INTeils der Abfrage zu vermeiden, habe ich eine temporäre Tabelle erstellt, Firmen-IDs in diese Tabelle eingefügt und dann Folgendes verwendet JOIN:
SELECT
DISTINCT
t1.visitor_id
FROM `table_1` AS t1
INNER JOIN `table_2` AS t2 ON t1.`visitor_id` = t2.visitor_id
INNER JOIN temp_table AS t3 ON t3.company_id = t2.company_id
ORDER BY t1.`visits_count` DESC
LIMIT 0, 20;
Diese Abfrage dauert ebenfalls bis zu 22 Sekunden . Ich benötige Leistung bis zu 2-3 secondsdieser Auflistung.
Zusätzliche Information
innodb_buffer_pool_sizebeträgt 12 GB- RAM ist 30 GB
- Ich verwende eine AWS RDS-
db.r3.xlargeInstanz SHOW TABLE STATUSDie Ausgabe ist wie folgt:

Die Abfrage wird
SELECT COUNT(*) FROM table_2 WHERE company_id IN (...) AND visited_on BETWEEN '2015-01-01' AND '2017-01-31'zurückgegeben2660123Nur zum ersten Mal braucht es Zeit. Wenn ich dieselbe Abfrage erneut ausführe, ist sie sehr viel schneller (0,2 Sekunden). Wenn ich das Limit jedoch auf "
LIMIT 20, 20Dann" ändere, wird es24 secondszum ersten Mal wiederholt , und dieselbe Abfrage zum zweiten Mal ist schneller. Es kann daran liegeninnodb_buffer_pool_size.Die Ausgabe von
EXPLAIN FORMAT=JSON SELECT ...;ist wie folgt.
{ "query_block": { "select_id": 1, "ordering_operation": { "using_filesort": true, "grouping_operation": { "using_temporary_table": true, "using_filesort": false, "nested_loop": [ { "table": { "table_name": "t2", "access_type": "range", "possible_keys": [ "PRIMARY", "indx_t2_test" ], "key": "PRIMARY", "used_key_parts": [ "company_id" ], "key_length": "8", "rows": 17301, "filtered": 100, "using_index": true, "attached_condition": "((`db`.`t2`.`company_id` in (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455)) and (`db`.`t2`.`visited_on` between '2015-01-01' and '2017-01-31'))" } }, { "table": { "table_name": "t1", "access_type": "eq_ref", "possible_keys": [ "PRIMARY", "indx_t1_test" ], "key": "PRIMARY", "used_key_parts": [ "visitor_id" ], "key_length": "34", "ref": [ "db.t2.visitor_id" ], "rows": 1, "filtered": 100 } } ] } } } }
Die Ausgabe der von Rick James vorgeschlagenen Abfrage:
SELECT
t2.visitor_id
FROM (
SELECT
DISTINCT visitor_id
FROM table_2
WHERE
company_id IN (528,211,1275,521,1299,493,492,852, 868,
869,1235,486,485,1238,855,1237,651,538,1241,1240, 548,
543,1247,1253,490,468,582,583,569,477,488,802,1294, 518,
1274,476,545,1267,556,479,1266,1265,541,1189,1263, 1152,
1260,478,1257,885,1139,1256,804,708,547,561,1239, 1142,
1226,1148,1230,529,1223,1192,1191,874,830,822,818, 817,
794,718,487,709,706,705,669,513,455)
AND visited_on BETWEEN '2015-01-01' AND '2017-01-31'
) AS t2
INNER JOIN table_1 AS t1 ON t2.visitor_id = t1.visitor_id
ORDER BY t1.`visits_count` DESC
LIMIT 20;
Explain Die Ausgabe der Abfrage lautet wie folgt:
Diese Abfrage dauert 58 Sekunden
Explain Die Ausgabe der inneren Unterabfrage ist wie folgt
Die Abfrage:
SELECT
COUNT(DISTINCT company_id, visited_on, visitor_id),
COUNT(DISTINCT company_id, LEFT(visited_on, 7), visitor_id),
COUNT(*)
FROM table_2;
kehrt zurück:
COUNT(DISTINCT company_id, visited_on, visitor_id)= 7607938.COUNT(DISTINCT company_id, LEFT(visited_on, 7), visitor_id)= 5194480COUNT(*)= 7607938
Beachten Sie, dass diese Ausgabe die neuesten Daten enthält, sodass sich die Anzahl der Zeilen count(*)möglicherweise erhöht hat.







limitnatürlich ohne )? Wenn dies ein wesentlicher Bestandteil Ihres Geschäftsmodells ist, sollten Sie über vorberechnete Ergebnisse nachdenken. Als Test: Könnten Sie versuchen, einen Index(visits_count)für Tabelle 1 und(visitor_id, company, visited_on)für Tabelle 2 hinzuzufügen , Abfrage 1 ohne (falsch) zu versuchengroup by,select distinct t1.visitor_idstattdessen zu verwenden und die Verwendung dieser Indizes zu erzwingen?