Hier ist meine Tabelle mit ~ 10.000.000 Zeilendaten
CREATE TABLE `votes` (
`subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`subject_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`rate` int(11) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
KEY `voter_timestamp` (`voter_id`,`updated_at`),
CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Hier sind die Index-Kardinalitäten
Also, wenn ich diese Abfrage mache:
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Ich hatte erwartet, dass es Index verwendet, voter_timestamp
aber MySQL wählt stattdessen diesen:
explain select SQL_NO_CACHE * from votes where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`
type:
index_merge
possible_keys:
PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
102,98
ref:
NULL
rows:
9255
filtered:
10.00
Extra:
Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort
Und ich habe 200-400ms Abfragezeit.
Wenn ich es zwinge, den richtigen Index zu verwenden, wie:
SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
MySQL kann die Ergebnisse in 1-2 ms zurückgeben
und hier ist die Erklärung:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Warum hat mysql den voter_timestamp
Index für meine ursprüngliche Abfrage nicht ausgewählt?
Was ich versucht hatte, ist analyze table votes
, optimize table votes
diesen Index zu löschen und erneut hinzuzufügen, aber MySQL verwendet immer noch den falschen Index. nicht ganz verstehen, was das Problem ist.
subject_name = "medium"
Teil entferne, kann es auch den richtigen Index auswählen, ohne dass ein Index erforderlich istrate
(voter_id, updated_at)
. Ein anderer Index wäre(voter_id, subject_name, updated_at)
oder(subject_name, voter_id, updated_at)
(ohne die Rate).subject_name='medium' and rate=1
)LIMIT
oder sogar zum, esORDER BY
sei denn, der Index erfüllt zuerst alle Filter. Das heißt, ohne die vollständigen 4 Spalten werden alle relevanten Zeilen gesammelt, alle sortiert und dann die ausgewähltLIMIT
. Mit dem 4-Spalten-Index kann die Abfrage die Sortierung vermeiden und anhalten, nachdem nur dieLIMIT
Zeilen gelesen wurden .Antworten:
MySQL verwendet ein relativ einfaches (einfacher als andere RDBMS) Kostenmodell für die Planung von Abfragen, bei denen das Filtern Ihres Datasets eine hohe Priorität hat. Bei Ihrer ersten Abfrage mit dem Zusammenführungsindex wird geschätzt, dass das Scannen von ~ 9000 Zeilen erforderlich sein wird, während für die zweite Abfrage mit dem Indexhinweis 18000 erforderlich sind. Ich wette, dass dies in der Berechnung ausreicht, um die Skala in Richtung Zusammenführung zu verschieben . Sie können dies bestätigen (oder andere Gründe finden), indem Sie es aktivieren
optimizer_trace
, Ihre Abfrage ausführen und die Ergebnisse auswerten.Eine Bemerkung zu
index_merge
: In den meisten Fällen werden Sie feststellen, dass es ziemlich teuer ist. Obwohl dies für OLAP-Szenarien sehr nützlich ist, ist es für OLTP möglicherweise nicht sehr gut geeignet, da der Vorgang viel Zeit in Anspruch nehmen kann und der suboptimale Ausführungsplan manchmal tatsächlich schneller ist.Glücklicherweise bietet MySQL Schalter für das Optimierungsprogramm, sodass Sie es nach Ihren Wünschen anpassen können.
Für alle Optionen, die Sie ausführen können:
Um eine zu ändern, müssen Sie nicht die gesamte Zeichenfolge kopieren und einfügen. Es funktioniert wie
dict.update()
in Python.Wenn möglich würde ich mir auch Ihre Tabellenstruktur ansehen und verbessern. Ein Primärschlüssel mit ~ 100 Byte und vielen Sekundärschlüsseln wird nicht empfohlen.
Sie haben vier Sekundärschlüssel, von denen einige überflüssig sind. Beispielsweise ist der
(voter_id)
Index eine Teilmenge von(voter_id, updated_at)
quelle
OR
in zu verwandelnUNION
ist oft genauso gut oder besser.Für diese Abfrage benötigen Sie diesen Index:
Das
updated_at
muss das letzte sein; Die anderen drei können in beliebiger Reihenfolge sein. (Die 3-Spalten-Indizes von ypercube sind nicht sehr nützlich, da sie dieWHERE
Spalten nicht beenden, bevor sie auf dieORDER BY
Spalte treffen .)Wenn Sie diesen Index hinzufügen, können Sie wahrscheinlich alle anderen Sekundärschlüssel entfernen:
KEY
IDX_518B7ACFEBB4B8AD
(voter_id
) - Der FK meinen Index KEY verwendensubject_timestamp
(subject_name
,subject_id
,updated_at
meist redundante KEY -),voter_timestamp
(voter_id
,updated_at
), - gewesen sein mag Ihren VersuchMit dem 4-Spalten-Index haben Sie die Möglichkeit, die "Paginierung" zu optimieren und zu vermeiden
OFFSET
. Siehe diesen Blog.Zu einem anderen Thema ... Wenn ich
X_name
und seheX_id
, gehe ich davon aus, dass "Normalisierung" stattfindet. Ich würde erwarten, diese beiden Spalten in einer Tabelle zu sehen, mit praktisch nichts anderem. Ich würde nicht erwarten, beide in einer anderen Tabelle zu sehen.(voter_id, updated_at)
kommt nicht vorbei,voter_id
da die Filterung noch nicht abgeschlossen istWHERE
. Da dann der andere Index kleiner ist, wird er ausgewählt. Meins hat 3 Spalten, um sich um das Filtern zu kümmern, dann die Spalte fürORDER BY
.quelle