Ich googelte, war autodidaktisch und suchte stundenlang nach einer Lösung, aber ohne Glück. Ich habe hier einige ähnliche Fragen gefunden, aber nicht diesen Fall.
Meine Tische:
- Personen (~ 10 Millionen Reihen)
- Attribute (Ort, Alter, ...)
- Verknüpfungen (M: M) zwischen Personen und Attributen (~ 40 Millionen Zeilen)
Situation:
Ich versuche, alle Personen-IDs ( person_id
) von einigen Orten ( location.attribute_value BETWEEN 3000 AND 7000
) auszuwählen , die ein Geschlecht haben ( gender.attribute_value = 1
), in einigen Jahren geboren sind ( bornyear.attribute_value BETWEEN 1980 AND 2000
) und eine Augenfarbe haben (eyecolor.attribute_value IN (2,3)
).
Dies ist meine Frage, die 3 ~ 4 min dauerte . und ich möchte optimieren:
SELECT person_id
FROM person
LEFT JOIN attribute location ON location.attribute_type_id = 1 AND location.person_id = person.person_id
LEFT JOIN attribute gender ON gender.attribute_type_id = 2 AND gender.person_id = person.person_id
LEFT JOIN attribute bornyear ON bornyear.attribute_type_id = 3 AND bornyear.person_id = person.person_id
LEFT JOIN attribute eyecolor ON eyecolor.attribute_type_id = 4 AND eyecolor.person_id = person.person_id
WHERE 1
AND location.attribute_value BETWEEN 3000 AND 7000
AND gender.attribute_value = 1
AND bornyear.attribute_value BETWEEN 1980 AND 2000
AND eyecolor.attribute_value IN (2,3)
LIMIT 100000;
Ergebnis:
+-----------+
| person_id |
+-----------+
| 233 |
| 605 |
| ... |
| 8702599 |
| 8703617 |
+-----------+
100000 rows in set (3 min 42.77 sec)
Erklären Sie erweitert:
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| 1 | SIMPLE | bornyear | range | attribute_type_id,attribute_value,person_id | attribute_value | 5 | NULL | 1265229 | 100.00 | Using where |
| 1 | SIMPLE | location | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | eyecolor | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | gender | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.eyecolor.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | test1.location.person_id | 1 | 100.00 | Using where; Using index |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
5 rows in set, 1 warning (0.02 sec)
Profiling:
+------------------------------+-----------+
| Status | Duration |
+------------------------------+-----------+
| Sending data | 3.069452 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.968915 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.042468 |
| Waiting for query cache lock | 0.000043 |
| Sending data | 3.264984 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.823919 |
| Waiting for query cache lock | 0.000038 |
| Sending data | 2.863903 |
| Waiting for query cache lock | 0.000014 |
| Sending data | 2.971079 |
| Waiting for query cache lock | 0.000020 |
| Sending data | 3.053197 |
| Waiting for query cache lock | 0.000087 |
| Sending data | 3.099053 |
| Waiting for query cache lock | 0.000035 |
| Sending data | 3.064186 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.939404 |
| Waiting for query cache lock | 0.000018 |
| Sending data | 3.440288 |
| Waiting for query cache lock | 0.000086 |
| Sending data | 3.115798 |
| Waiting for query cache lock | 0.000068 |
| Sending data | 3.075427 |
| Waiting for query cache lock | 0.000072 |
| Sending data | 3.658319 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.335427 |
| Waiting for query cache lock | 0.000049 |
| Sending data | 3.319430 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.496563 |
| Waiting for query cache lock | 0.000029 |
| Sending data | 3.017041 |
| Waiting for query cache lock | 0.000032 |
| Sending data | 3.132841 |
| Waiting for query cache lock | 0.000050 |
| Sending data | 2.901310 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.107269 |
| Waiting for query cache lock | 0.000062 |
| Sending data | 2.937373 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.097082 |
| Waiting for query cache lock | 0.000261 |
| Sending data | 3.026108 |
| Waiting for query cache lock | 0.000026 |
| Sending data | 3.089760 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 3.012763 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 3.069694 |
| Waiting for query cache lock | 0.000046 |
| Sending data | 3.591908 |
| Waiting for query cache lock | 0.000060 |
| Sending data | 3.526693 |
| Waiting for query cache lock | 0.000076 |
| Sending data | 3.772659 |
| Waiting for query cache lock | 0.000069 |
| Sending data | 3.346089 |
| Waiting for query cache lock | 0.000245 |
| Sending data | 3.300460 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.135361 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.909447 |
| Waiting for query cache lock | 0.000039 |
| Sending data | 3.337561 |
| Waiting for query cache lock | 0.000140 |
| Sending data | 3.138180 |
| Waiting for query cache lock | 0.000090 |
| Sending data | 3.060687 |
| Waiting for query cache lock | 0.000085 |
| Sending data | 2.938677 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 2.977974 |
| Waiting for query cache lock | 0.000872 |
| Sending data | 2.918640 |
| Waiting for query cache lock | 0.000036 |
| Sending data | 2.975842 |
| Waiting for query cache lock | 0.000051 |
| Sending data | 2.918988 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.943810 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.330211 |
| Waiting for query cache lock | 0.000025 |
| Sending data | 3.411236 |
| Waiting for query cache lock | 0.000023 |
| Sending data | 23.339035 |
| end | 0.000807 |
| query end | 0.000023 |
| closing tables | 0.000325 |
| freeing items | 0.001217 |
| logging slow query | 0.000007 |
| logging slow query | 0.000011 |
| cleaning up | 0.000104 |
+------------------------------+-----------+
100 rows in set (0.00 sec)
Tabellenstrukturen:
CREATE TABLE `attribute` (
`attribute_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`attribute_type_id` int(11) unsigned DEFAULT NULL,
`attribute_value` int(6) DEFAULT NULL,
`person_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`attribute_id`),
KEY `attribute_type_id` (`attribute_type_id`),
KEY `attribute_value` (`attribute_value`),
KEY `person_id` (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=40000001 DEFAULT CHARSET=utf8;
CREATE TABLE `person` (
`person_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`person_name` text CHARACTER SET latin1,
PRIMARY KEY (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8;
Die Abfrage wurde auf einem virtuellen DigitalOcean-Server mit SSD und 1 GB RAM durchgeführt.
Ich gehe davon aus, dass es Probleme mit dem Datenbankdesign geben könnte. Haben Sie Vorschläge, um diese Situation besser zu gestalten? Oder nur um die Auswahl oben anzupassen?
attribute (person_id, attribute_type_id, attribute_value)
(attribute_type_id, attribute_value, person_id)
und(attribute_type_id, person_id, attribute_value)
Antworten:
Wählen Sie einige Attribute aus, die berücksichtigt werden sollen
person
. Indizieren Sie sie in wenigen Kombinationen - verwenden Sie zusammengesetzte Indizes und keine einspaltigen Indizes.Dies ist im Grunde der einzige Ausweg aus EAV-Sucks-at-Performance, bei dem Sie sich gerade befinden.
Hier finden Sie weitere Informationen: http://mysql.rjweb.org/doc.php/eav, einschließlich eines Vorschlags zur Verwendung von JSON anstelle der Schlüsselwerttabelle.
quelle
Addiere Indeces zu
attribute
für:(person_id, attribute_type_id, attribute_value)
und(attribute_type_id, attribute_value, person_id)
Erläuterung
Mit Ihrem aktuellen Design
EXPLAIN
erwartet Ihre Abfrage,1,265,229 * 4 * 4 * 4 = 80,974,656
Zeilen in zu untersuchenattribute
. Sie können durch Hinzufügen eines diese Zahl reduzieren Composite - Index aufattribute
für(person_id, attribute_type_id)
. Diesen Index Kriterien nur 1 statt 4 Zeilen für die einzelnen untersuchenlocation
,eyecolor
undgender
.Sie könnten diesen Index erweitern umfassen
attribute_type_value
auch:(person_id, attribute_type_id, attribute_value)
. Dies würde diesen Index in einen abdeckenden Index für diese Abfrage verwandeln , was ebenfalls die Leistung verbessern sollte.Darüber hinaus sollte das Hinzufügen eines Index für
(attribute_type_id, attribute_value, person_id)
(erneut ein abdeckender Index durch Einschließenperson_id
) die Leistung verbessern, anstatt nur einen Index für einen Index zu verwenden,attribute_value
bei dem mehr Zeilen untersucht werden müssten. In diesem Fall wird der erste Schritt in Ihrer Erklärung beschleunigt: Auswahl eines Bereichs ausbornyear
.Durch die Verwendung dieser beiden Indizes wurde die Ausführungszeit Ihrer Abfrage auf meinem System von ~ 2,0 s auf ~ 0,2 s gesenkt, wobei die EXPLAIN-Ausgabe folgendermaßen aussah:
quelle
SELECT person.person_id
da sie sonst offensichtlich nicht ausgeführt werden würde. Haben Sie getan,ANALYZE TABLE attribute
nachdem Sie die Indices hinzugefügt haben? Möglicherweise möchten Sie auch Ihre neueEXPLAIN
Ausgabe (nach dem Hinzufügen von Indices) zu Ihrer Frage hinzufügen.Sie verwenden ein sogenanntes "Entity-Attribute-Value" -Design, das aufgrund seines Designs oft eine schlechte Leistung erbringt.
Die klassische relationale Methode, dies zu entwerfen, besteht darin, für jedes Attribut eine eigene Tabelle zu erstellen. In der Regel können Sie diese separaten Tabellen haben:
location
,gender
,bornyear
,eyecolor
.Das Folgende hängt davon ab, ob bestimmte Attribute für eine Person immer definiert sind oder nicht. Und ob eine Person nur einen Wert eines Attributs haben kann. Zum Beispiel hat die Person normalerweise nur ein Geschlecht. In Ihrem aktuellen Design hindert Sie nichts daran, drei Zeilen für dieselbe Person mit unterschiedlichen Werten für das Geschlecht hinzuzufügen. Sie können auch einen Geschlechtswert nicht auf 1 oder 2 festlegen, sondern auf eine Zahl, die keinen Sinn ergibt, z. B. 987, und die Datenbank enthält keine Einschränkungen, die dies verhindern würden. Dies ist jedoch ein weiteres Problem bei der Aufrechterhaltung der Datenintegrität beim EAV-Design.
Wenn Sie das Geschlecht der Person immer kennen, ist es wenig sinnvoll, es in einer separaten Tabelle abzulegen, und es ist viel besser, eine Nicht-Null-Spalte
GenderID
in derperson
Tabelle zu haben, die ein Fremdschlüssel für die Nachschlagetabelle mit der Liste von wäre alle möglichen Geschlechter und ihre Namen. Wenn Sie das Geschlecht der Person die meiste Zeit, aber nicht immer kennen, können Sie diese Spalte auf null setzen und festlegen,NULL
wenn keine Informationen verfügbar sind. Wenn das Geschlecht der Person meistens nicht bekannt ist, ist es möglicherweise besser, eine separate Tabelle zu habengender
, die mitperson
1: 1 verknüpft ist und nur Zeilen für die Personen enthält, deren Geschlecht bekannt ist.Ähnliche Überlegungen gelten für
eyecolor
undbornyear
- es ist unwahrscheinlich, dass die Person zwei Werte für eineyecolor
oder hatbornyear
.Wenn es für eine Person möglich ist, mehrere Werte für ein Attribut zu haben, würden Sie es definitiv in eine separate Tabelle stellen. Beispielsweise ist es nicht ungewöhnlich, dass eine Person mehrere Adressen hat (zu Hause, bei der Arbeit, auf dem Postweg, in den Ferien usw.), sodass Sie sie alle in einer Tabelle auflisten
location
. Tabellenperson
undlocation
würde 1: M verknüpft werden.Wenn Sie das EAV-Design verwenden, würde ich zumindest Folgendes tun.
attribute_type_id
,attribute_value
,person_id
aufNOT NULL
.attribute.person_id
mit verknüpft istperson.person_id
.(attribute_type_id, attribute_value, person_id)
. Die Reihenfolge der Spalten ist hier wichtig.Ich würde die Abfrage so schreiben. Verwenden Sie
INNER
anstelle vonLEFT
Joins und schreiben Sie explizit eine Unterabfrage für jedes Attribut, um dem Optimierer alle Möglichkeiten zur Verwendung des Index zu geben.Es kann auch sinnvoll sein, die Tabelle nach zu partitionieren .
attribute
attribute_type_id
quelle
JOIN ( SELECT ... )
Optimiert nicht gut.JOINing
direkt am tisch klappt besser (ist aber immer noch problematisch).Ich hoffe ich habe eine ausreichende Lösung gefunden. Es ist von diesem Artikel inspiriert .
Kurze Antwort:
ft_min_word_len=1
(für MyISAM) im[mysqld]
Abschnitt undinnodb_ft_min_token_size=1
(für InnoDb) in dermy.cnf
Datei den mysql-Dienst neu zu starten.SELECT * FROM person_index WHERE MATCH(attribute_1) AGAINST("123 456 789" IN BOOLEAN MODE) LIMIT 1000
Wo123
,456
a789
sind IDs, die Personen zugeordnet haben sollenattribute_1
. Diese Abfrage dauerte weniger als 1 Sek.Ausführliche Antwort:
Schritt 1. Tabelle mit Volltextindizes erstellen. InnoDb unterstützt Volltextindizes aus MySQL 5.7. Wenn Sie also 5.5 oder 5.6 verwenden, sollten Sie MyISAM verwenden. Bei der FT-Suche ist es manchmal sogar schneller als bei InnoDb.
Schritt 2. Fügen Sie Daten aus der EAV-Tabelle (Entity-Attribut-Wert) ein. Zum Beispiel angegeben in Frage kann es mit 1 einfachen SQL erfolgen:
Das Ergebnis sollte ungefähr so aussehen:
Schritt 3. Wählen Sie aus der Tabelle mit der folgenden Abfrage:
Die Abfrage wählt alle Zeilen aus:
attr_1
:3000, 3001, 3002, 3003, 3004, 3005, 3006 or 3007
1
inattr_2
(diese Spalte repräsentiert das Geschlecht. Wenn diese Lösung also angepasst wäre, sollte siesmallint(1)
mit einem einfachen Index usw. versehen sein.)1980, 1981, 1982, 1983 or 1984
inattr_3
2
oder3
inattr_4
Fazit:
Ich weiß, dass diese Lösung nicht perfekt und ideal für viele Situationen ist, sondern als gute Alternative für die Gestaltung von EAV-Tischen verwendet werden kann.
Ich hoffe es hilft jemandem.
quelle
Versuchen Sie, Abfrageindexhinweise zu verwenden, die angemessen aussehen
MySQL Index Hinweise
quelle