Ini meja saya dengan ~ 10.000.000 data baris
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;
Berikut adalah kardinalitas indeks
Jadi ketika saya melakukan kueri ini:
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;
Saya mengharapkan itu menggunakan indeks voter_timestamp
tetapi mysql memilih untuk menggunakan ini sebagai gantinya:
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
Dan saya mendapat waktu permintaan 200-400ms.
Jika saya memaksanya untuk menggunakan indeks yang tepat seperti:
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 dapat mengembalikan hasilnya dalam 1-2ms
dan inilah penjelasannya:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Jadi mengapa mysql tidak memilih voter_timestamp
indeks untuk permintaan awal saya?
Apa yang saya coba adalah analyze table votes
,, optimize table votes
letakkan indeks itu dan tambahkan lagi, tetapi mysql masih menggunakan indeks yang salah. tidak begitu mengerti apa masalahnya.
subject_name = "medium"
bagian itu juga dapat memilih indeks yang tepat, tidak perlu indeksrate
(voter_id, updated_at)
. Indeks lain akan(voter_id, subject_name, updated_at)
atau(subject_name, voter_id, updated_at)
(tanpa kurs).subject_name='medium' and rate=1
)LIMIT
atau bahkanORDER BY
kecuali indeks memenuhi semua penyaringan terlebih dahulu. Artinya, tanpa 4 kolom penuh, ia akan mengumpulkan semua baris yang relevan, mengurutkan semuanya, lalu memilihLIMIT
. Dengan indeks 4-kolom, query dapat menghindari jenis dan berhenti setelah membaca hanya satuLIMIT
baris.Jawaban:
MySQL menggunakan model biaya yang relatif sederhana (lebih sederhana dari RDBMS lainnya) untuk perencanaan kueri di mana penyaringan dataset Anda memiliki prioritas yang cukup tinggi. Dalam kueri pertama Anda dengan indeks gabungan, diperkirakan pemindaian ~ 9000 baris akan diperlukan sedangkan yang kedua dengan petunjuk indeks akan membutuhkan 18.000. Taruhan saya adalah bahwa ini akan berbobot dalam perhitungan yang cukup untuk memindahkan skala ke arah gabungan. . Anda dapat mengkonfirmasi ini (atau menemukan alasan lain) dengan menghidupkan
optimizer_trace
, menjalankan kueri Anda dan mengevaluasi hasilnya.Satu komentar pada
index_merge
: dalam kebanyakan kasus Anda akan menemukan bahwa itu cukup mahal. Walaupun sangat berguna untuk skenario tipe OLAP, itu mungkin tidak terlalu cocok untuk OLTP karena operasi dapat mengambil waktu yang signifikan dari permintaan Anda dan seperti yang Anda lihat kadang-kadang rencana eksekusi suboptimal sebenarnya lebih cepat.Untungnya MySQL menyediakan sakelar untuk pengoptimal sehingga Anda dapat menyesuaikannya sesuai keinginan.
Untuk semua opsi, Anda dapat menjalankan:
Untuk mengubahnya, Anda tidak perlu menyalin seluruh string. Ia bekerja seperti
dict.update()
di python.Jika memungkinkan saya juga akan melihat struktur meja Anda dan meningkatkan. Memiliki kunci primer ~ 100 byte dengan banyak kunci sekunder tidak disarankan.
Anda memiliki empat kunci sekunder dan beberapa di antaranya berlebihan, misalnya
(voter_id)
indeks adalah subset dari(voter_id, updated_at)
sumber
OR
menjadiUNION
sering kali lebih baik atau lebih baik.Untuk permintaan itu, Anda memerlukan indeks ini:
Itu
updated_at
harus menjadi yang terakhir; tiga lainnya bisa dalam urutan apa pun. (Indeks 3-kolom ypercube tidak terlalu berguna karena tidak menyelesaikanWHERE
kolom sebelum mengenaiORDER BY
kolom.)Saat Anda menambahkan indeks ini, Anda mungkin bisa menghilangkan semua kunci sekunder lainnya:
KEY
IDX_518B7ACFEBB4B8AD
(voter_id
), - The FK dapat menggunakan indeks KEY sayasubject_timestamp
(subject_name
,subject_id
,updated_at
), - sebagian besar berlebihan KEYvoter_timestamp
(voter_id
,updated_at
), - mungkin upaya AndaDengan indeks 4-kolom, Anda memiliki peluang untuk mengoptimalkan "pagination" dan menghindari
OFFSET
. Lihat blog ini.Pada topik lain ... Ketika saya melihat
X_name
danX_id
, saya menganggap "normalisasi" sedang terjadi. Saya berharap melihat dua kolom dalam sebuah tabel, dengan hampir tidak ada yang lain. Saya tidak akan berharap untuk melihat keduanya di beberapa meja lainnya.(voter_id, updated_at)
tidak akan melewativoter_id
karena belum selesai dengan pemfilteranWHERE
. Kemudian, karena indeks yang lain lebih kecil, itu diambil. Milik saya memiliki 3 kolom untuk mengurus pemfilteran, lalu kolom untukORDER BY
.sumber