Saya googling, belajar sendiri & mencari solusi selama berjam-jam tetapi tidak berhasil. Saya menemukan beberapa pertanyaan serupa di sini tetapi tidak dalam kasus ini.
Meja saya:
- orang (~ baris 10M)
- atribut (lokasi, umur, ...)
- tautan (M: M) antara orang dan atribut (~ baris 40M)
Situasi:
Saya mencoba untuk memilih id semua orang ( person_id
) dari beberapa lokasi ( location.attribute_value BETWEEN 3000 AND 7000
), menjadi beberapa jenis kelamin ( gender.attribute_value = 1
), lahir dalam beberapa tahun ( bornyear.attribute_value BETWEEN 1980 AND 2000
) dan memiliki warna mata ( eyecolor.attribute_value IN (2,3)
).
Ini adalah penyihir kueri saya memakan waktu 3 ~ 4 mnt. dan saya ingin mengoptimalkan:
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;
Hasil:
+-----------+
| person_id |
+-----------+
| 233 |
| 605 |
| ... |
| 8702599 |
| 8703617 |
+-----------+
100000 rows in set (3 min 42.77 sec)
Jelaskan diperpanjang:
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| 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)
Pembuatan profil:
+------------------------------+-----------+
| 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)
Struktur tabel:
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;
Permintaan telah dilakukan pada server virtual DigitalOcean dengan SSD dan RAM 1GB.
Saya berasumsi mungkin ada masalah dengan desain database. Apakah Anda punya saran untuk merancang situasi ini dengan lebih baik? Atau hanya untuk menyesuaikan pilih di atas?
attribute (person_id, attribute_type_id, attribute_value)
(attribute_type_id, attribute_value, person_id)
dan(attribute_type_id, person_id, attribute_value)
Jawaban:
Pilih beberapa atribut untuk disertakan
person
. Buat indeks dalam beberapa kombinasi - gunakan indeks komposit, bukan indeks satu kolom.Itu pada dasarnya satu-satunya jalan keluar dari EAV-sucks-at-performance, yang mana Anda berada.
Berikut ini diskusi lebih lanjut: http://mysql.rjweb.org/doc.php/eav termasuk saran untuk menggunakan JSON dan bukan tabel nilai kunci.
sumber
Tambahkan indeces ke
attribute
untuk:(person_id, attribute_type_id, attribute_value)
dan(attribute_type_id, attribute_value, person_id)
Penjelasan
Dengan desain Anda saat ini,
EXPLAIN
mengharapkan permintaan Anda untuk memeriksa1,265,229 * 4 * 4 * 4 = 80,974,656
barisattribute
. Anda dapat mengurangi jumlah ini dengan menambahkan indeks komposit padaattribute
untuk(person_id, attribute_type_id)
. Dengan menggunakan indeks ini, kueri Anda hanya akan memeriksa 1 daripada 4 baris untuk masing-masinglocation
,eyecolor
dangender
.Anda bisa memperpanjang indeks yang meliputi
attribute_type_value
juga:(person_id, attribute_type_id, attribute_value)
. Ini akan mengubah indeks ini menjadi indeks penutup untuk permintaan ini, yang juga akan meningkatkan kinerja.Lebih jauh lagi, menambahkan indeks pada
(attribute_type_id, attribute_value, person_id)
(lagi-lagi indeks penutup dengan memasukkanperson_id
) akan meningkatkan kinerja dibandingkan hanya menggunakan indeks diattribute_value
mana lebih banyak baris harus diperiksa. Dalam hal ini akan mempercepat langkah pertama dalam penjelasan Anda: memilih rentang daribornyear
.Menggunakan dua indeces tersebut menurunkan waktu eksekusi permintaan Anda di sistem saya dari ~ 2.0 s ke ~ 0.2 s dengan output yang dijelaskan seperti ini:
sumber
SELECT person.person_id
karena jika tidak maka tidak akan berjalan. Apakah Anda melakukannyaANALYZE TABLE attribute
setelah menambahkan indeces? Anda mungkin ingin menambahkanEXPLAIN
output baru Anda (setelah menambahkan indeces) ke pertanyaan Anda juga.Anda menggunakan desain Entity-Attribute-Value, yang sering berkinerja buruk, baik, menurut desain.
Cara relasional klasik untuk mendesain ini akan membuat tabel terpisah untuk setiap atribut. Secara umum, Anda dapat memiliki ini tabel terpisah:
location
,gender
,bornyear
,eyecolor
.Berikut ini tergantung pada apakah atribut tertentu selalu ditentukan untuk seseorang, atau tidak. Dan, apakah seseorang hanya dapat memiliki satu nilai atribut. Misalnya, biasanya orang tersebut hanya memiliki satu jenis kelamin. Dalam desain Anda saat ini, tidak ada yang menghentikan Anda untuk menambahkan tiga baris untuk orang yang sama dengan nilai berbeda untuk gender di dalamnya. Anda juga dapat menetapkan nilai gender bukan ke 1 atau 2, tetapi ke beberapa nomor yang tidak masuk akal, seperti 987 dan tidak ada kendala dalam database yang akan mencegahnya. Namun, ini adalah masalah terpisah lainnya dalam menjaga integritas data dengan desain EAV.
Jika Anda selalu mengetahui jenis kelamin orang tersebut, maka tidak masuk akal untuk meletakkannya di tabel terpisah dan itu jauh lebih baik untuk memiliki kolom non-nol
GenderID
di dalamperson
tabel, yang akan menjadi kunci asing ke tabel pencarian dengan daftar semua jenis kelamin yang mungkin dan nama mereka. Jika Anda paling sering mengetahui jenis kelamin orang tersebut, tetapi tidak selalu, Anda dapat membuat kolom ini dapat dibatalkan dan aturNULL
ketika informasi tidak tersedia. Jika sebagian besar waktu jenis kelamin seseorang tidak diketahui, maka mungkin lebih baik untuk memiliki tabel terpisahgender
yang terhubung keperson
1: 1 dan memiliki baris hanya untuk orang-orang yang memiliki jenis kelamin yang diketahui.Pertimbangan serupa berlaku untuk
eyecolor
danbornyear
- orang tersebut tidak mungkin memiliki dua nilai untuk suatueyecolor
ataubornyear
.Jika mungkin bagi seseorang untuk memiliki beberapa nilai untuk suatu atribut, maka Anda pasti akan meletakkannya di tabel terpisah. Misalnya, tidak jarang seseorang memiliki beberapa alamat (rumah, kantor, pos, liburan, dll.), Jadi Anda akan mencantumkan semuanya dalam sebuah tabel
location
. Tabelperson
danlocation
akan dihubungkan 1: M.Jika menggunakan desain EAV, maka saya setidaknya akan melakukan hal berikut.
attribute_type_id
,attribute_value
,person_id
untukNOT NULL
.attribute.person_id
denganperson.person_id
.(attribute_type_id, attribute_value, person_id)
. Urutan kolom penting di sini.Saya akan menulis kueri seperti ini. Gunakan
INNER
alih-alihLEFT
bergabung dan secara eksplisit menulis subquery untuk setiap atribut untuk memberikan optimizer semua peluang untuk menggunakan indeks.Juga, mungkin patut partisi yang
attribute
table olehattribute_type_id
.sumber
JOIN ( SELECT ... )
tidak mengoptimalkan dengan baik.JOINing
langsung ke meja bekerja lebih baik (tetapi masih bermasalah).Saya harap saya menemukan solusi yang memadai. Itu terinspirasi oleh artikel ini .
Jawaban singkat:
ft_min_word_len=1
(untuk MyISAM) di[mysqld]
bagian daninnodb_ft_min_token_size=1
(untuk InnoDb) dalammy.cnf
file, restart layanan mysql.SELECT * FROM person_index WHERE MATCH(attribute_1) AGAINST("123 456 789" IN BOOLEAN MODE) LIMIT 1000
di mana123
,456
sebuah789
adalah ID yang orang seharusnya terkait dalamattribute_1
. Permintaan ini berlangsung kurang dari 1 detik.Jawaban terinci:
Langkah 1. Membuat tabel dengan indeks teks lengkap. InnoDb mendukung indeks teks lengkap dari MySQL 5.7 jadi jika Anda menggunakan 5.5 atau 5.6, Anda harus menggunakan MyISAM. Terkadang bahkan lebih cepat untuk pencarian FT daripada InnoDb.
Langkah 2. Masukkan data dari tabel EAV (entitas-atribut-nilai). Misalnya dinyatakan dalam pertanyaan itu dapat dilakukan dengan 1 SQL sederhana:
Hasilnya harus seperti ini:
Langkah 3. Pilih dari tabel dengan kueri seperti ini:
Kueri memilih semua baris:
attr_1
:3000, 3001, 3002, 3003, 3004, 3005, 3006 or 3007
1
diattr_2
(kolom ini mewakili jenis kelamin jadi jika larutan ini disesuaikan, itu harussmallint(1)
dengan indeks sederhana, dll ...)1980, 1981, 1982, 1983 or 1984
diattr_3
2
atau3
dalamattr_4
Kesimpulan:
Saya tahu solusi ini tidak sempurna dan ideal untuk banyak situasi tetapi dapat digunakan sebagai alternatif yang baik untuk desain tabel EAV.
Saya harap ini akan membantu seseorang.
sumber
Coba gunakan petunjuk indeks permintaan yang terlihat sesuai
Petunjuk Indeks Mysql
sumber