Bagaimana cara mempercepat kueri pada tabel baris 220 juta besar (data 9 manggung)?

31

Masalah:

Kami memiliki situs sosial di mana anggota dapat menilai satu sama lain untuk kompatibilitas atau pencocokan. user_match_ratingsTabel ini berisi lebih dari 220 juta baris (9 pertunjukan data atau hampir 20 pertunjukan dalam indeks). Kueri terhadap tabel ini secara rutin muncul di slow.log (ambang> 2 detik) dan merupakan kueri lambat yang paling sering dicatat dalam sistem:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Versi MySQL:

  • versi protokol: 10
  • versi: 5.0.77-log
  • versi bdb: Perangkat Lunak Sleepycat: Berkeley DB 4.1.24: (29 Januari 2009)
  • mesin kompilasi versi: x86_64 version_compile_os: redhat-linux-gnu

Info tabel:

SHOW COLUMNS FROM user_match_ratings;

Memberi:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Permintaan sampel:

select * from mutual_match_ratings where id=221673540;

memberi:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Indeks

Tabel ini memiliki 3 indeks yang diatur:

  1. indeks tunggal aktif rated_user_id
  2. indeks komposit pada rater_user_iddancreated_at
  3. indeks komposit pada rated_user_iddanrater_user_id
tampilkan indeks dari user_match_ratings;

memberi:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Bahkan dengan indeks permintaan ini lambat.

Pertanyaan saya:

Apakah memisahkan tabel / data ini ke database lain di server yang memiliki ram cukup untuk menyimpan data ini dalam memori akankah ini mempercepat pertanyaan ini? Apakah ada hal lain yang mengatur tabel / indeks yang dapat kita tingkatkan untuk membuat kueri ini lebih cepat?

Saat ini kami memiliki memori 16GB; namun kami sedang mencari cara meningkatkan mesin yang ada ke 32GB atau menambahkan mesin baru dengan setidaknya sebanyak itu, mungkin solid state drive juga.

Ranknoodle
sumber
1
Pertanyaan Anda luar biasa. Saya sangat tertarik dengan solusi Anda saat ini yang bagaimana Anda berhasil mendapatkan hasil dalam <= 2 detik? Karena saya punya satu tabel yang hanya memiliki 20 juta catatan dan masih butuh 30 detik untuk SELECT QUERY. Tolong sarankan? PS Pertanyaan Anda memaksa saya untuk bergabung dengan komunitas ini (y);)
NullPointer
2
Lihatlah indeks pada tabel yang Anda cari .. sering kali banyak perbaikan pada kueri dapat dilakukan dengan membuat indeks yang sesuai. Tidak selalu tetapi melihat banyak contoh di mana kueri dibuat cepat dengan memberikan indeks terhadap kolom di mana klausa pada kueri. Apalagi jika meja tumbuh semakin besar.
Ranknoodle
Tentu @Ranknoodle. Terima kasih. Saya akan memeriksa masing-masing.
NullPointer

Jawaban:

28

Pikiran tentang masalah ini, dilemparkan secara acak:

  • Indeks jelas untuk query ini adalah: (rated_user_id, rating). Kueri yang mendapatkan data hanya untuk satu dari satu juta pengguna dan membutuhkan 17 detik adalah melakukan sesuatu yang salah: membaca dari (rated_user_id, rater_user_id)indeks dan kemudian membaca dari tabel nilai (ratusan hingga ribuan) untuk ratingkolom, karena ratingtidak ada dalam indeks apa pun. Jadi, kueri harus membaca banyak baris tabel yang terletak di berbagai lokasi disk.

  • Sebelum mulai menambahkan banyak indeks dalam tabel, cobalah untuk menganalisis kinerja seluruh database, seluruh rangkaian pertanyaan yang lambat, periksa kembali pilihan dari tipe data, mesin yang Anda gunakan dan pengaturan konfigurasi.

  • Pertimbangkan untuk pindah ke versi MySQL yang lebih baru, 5.1, 5.5 atau bahkan 5.6 (juga: versi Percona dan MariaDB.) Beberapa manfaat sebagai bug telah diperbaiki, pengoptimal ditingkatkan dan Anda dapat mengatur ambang rendah untuk kueri lambat menjadi kurang dari 1 detik (seperti 10 milidetik). Ini akan memberi Anda info yang jauh lebih baik tentang permintaan lambat.

  • Pilihan untuk tipe data ratinganeh. VARCHAR(1)? Mengapa tidak CHAR(1)? Mengapa tidak TINYINT? Ini akan menghemat ruang, baik timah tabel dan indeks yang (akan) menyertakan kolom itu. Kolom varchar (1) membutuhkan satu byte lebih dari char (1) dan jika mereka utf8, kolom char (var) akan membutuhkan 3 (atau 4) byte, bukan 1 (tinyint).

ypercubeᵀᴹ
sumber
2
Berapa banyak dampak kinerja atau pemborosan penyimpanan dalam hal% jika Anda menggunakan tipe data yang salah?
FlyingAtom
1
@FlyingAtom Tergantung pada kasusnya, tetapi untuk beberapa kolom yang diindeks masih perlu dipindai (misalnya ketika Anda tidak memiliki klausa di mana tetapi Anda hanya mengambil kolom itu), mesin mungkin memutuskan untuk memindai indeks alih-alih tabel, dan jika Anda mengoptimalkan tipe data menjadi satu setengah ukuran maka pemindaian akan dua kali lebih cepat dan responsnya akan menjadi setengah ukuran. Jika Anda masih memindai tabel alih-alih indeks (misalnya ketika Anda mengambil lebih banyak kolom tidak hanya yang ada di indeks), maka manfaatnya akan kurang signifikan.
Sebastián Grignoli
-1

Saya menangani tabel untuk Pemerintah Jerman dengan kadang-kadang 60 juta catatan.

Kami punya banyak tabel ini.

Dan kami perlu tahu berkali-kali jumlah baris dari sebuah tabel.

Setelah berbicara dengan programmer Oracle dan Microsoft kami tidak begitu bahagia ...

Jadi kami, kelompok pemrogram basis data, memutuskan bahwa dalam setiap tabel ada catatan satu selalu catatan di mana jumlah total catatan disimpan. Kami memperbarui nomor ini, tergantung pada baris INSERT atau DELETE.

Kami mencoba semua cara lain. Ini adalah cara tercepat.

Kami menggunakan cara ini sekarang sejak tahun 1998 dan tidak pernah salah jumlah baris, di semua tabel multi-juta catatan kami.

FrankyBkk
sumber
7
Saya sarankan untuk melihat beberapa fitur yang diperkenalkan dalam 18 tahun terakhir. Di antaranya, count(*)ada beberapa peningkatan.
dezso
Bagaimana Anda tahu bahwa Anda tidak pernah memiliki nomor yang salah jika Anda tidak dapat menghitungnya? uhmmmm ...
Tonca
-3

Saya akan mencoba mempartisi pada jenis peringkat, seperti:

mutual_match_ratings_N, mutual_match_ratings_S, dll.

Anda harus melakukan kueri untuk setiap jenis, tetapi mungkin itu lebih cepat daripada cara lainnya. Cobalah.

Ini mengasumsikan Anda memiliki sejumlah tipe peringkat tetap, dan bahwa Anda tidak memerlukan tabel ini untuk kueri lain yang akan lebih buruk dengan struktur baru ini.

Jika itu masalahnya, Anda harus mencari pendekatan lain, atau mempertahankan dua salinan tabel (tabel awal Anda, dan yang dipartisi) jika itu terjangkau dalam hal ruang dan rawatan (atau logika aplikasi).

appartisan
sumber