Untuk menghitung jumlah baris dengan tanggal tertentu, MySQL harus menemukan nilai itu dalam indeks (yang cukup cepat, lagi pula untuk itulah indeks dibuat) dan kemudian membaca entri berikutnya dari indeks sampai menemukan tanggal berikutnya. Tergantung pada tipe data esi
, ini berarti membaca beberapa MB data untuk menghitung 700 ribu baris Anda. Membaca beberapa MB tidak memakan banyak waktu (dan data itu bahkan mungkin sudah di-cache di buffer pool, tergantung seberapa sering Anda menggunakan indeks).
Untuk menghitung rata-rata untuk kolom yang tidak termasuk dalam indeks, MySQL akan, sekali lagi, menggunakan indeks untuk menemukan semua baris untuk tanggal tersebut (sama seperti sebelumnya). Tetapi selain itu, untuk setiap baris yang ditemukan, ia harus membaca data tabel aktual untuk baris tersebut, yang berarti menggunakan kunci utama untuk menemukan baris, membaca beberapa byte, dan mengulanginya 700 ribu kali. Ini "akses acak"
banyak lebih lambat dari pembacaan berurutan dalam kasus pertama. (Ini diperburuk oleh masalah bahwa "beberapa byte" adalah innodb_page_size
(16KB secara default), jadi Anda mungkin harus membaca hingga 700k * 16KB =11GB, dibandingkan dengan "beberapa MB" untuk count(*)
; dan tergantung pada konfigurasi memori Anda, beberapa data ini mungkin tidak di-cache dan harus dibaca dari disk.)
Solusi untuk ini adalah memasukkan semua kolom yang digunakan dalam indeks ("indeks penutup"), mis. buat indeks pada date, 01
. Kemudian MySQL tidak perlu mengakses tabel itu sendiri, dan dapat melanjutkan, mirip dengan metode pertama, hanya dengan membaca indeks. Ukuran indeks akan meningkat sedikit, jadi MySQL perlu membaca "beberapa MB lagi" (dan melakukan avg
-operasi), tetapi seharusnya masih dalam hitungan detik.
Di komentar, Anda menyebutkan bahwa Anda perlu menghitung rata-rata lebih dari 24 kolom. Jika Anda ingin menghitung avg
untuk beberapa kolom sekaligus, Anda memerlukan indeks penutup pada semua kolom tersebut, mis. date, 01, 02, ..., 24
untuk mencegah akses tabel. Ketahuilah bahwa indeks yang berisi semua kolom membutuhkan ruang penyimpanan sebanyak tabel itu sendiri (dan akan memakan waktu lama untuk membuat indeks seperti itu), jadi mungkin bergantung pada seberapa penting kueri ini jika layak untuk sumber daya tersebut.
Untuk menghindari MySQL-limit 16 kolom per indeks
, Anda dapat membaginya menjadi dua indeks (dan dua kueri). Buat mis. indeks date, 01, .., 12
dan date, 13, .., 24
, lalu gunakan
select * from (select `date`, avg(`01`), ..., avg(`12`)
from mytable where `date` = ...) as part1
cross join (select avg(`13`), ..., avg(`24`)
from mytable where `date` = ...) as part2;
Pastikan untuk mendokumentasikan ini dengan baik, karena tidak ada alasan yang jelas untuk menulis kueri dengan cara ini, tetapi mungkin sepadan.
Jika Anda hanya pernah membuat rata-rata pada satu kolom, Anda dapat menambahkan 24 indeks terpisah (pada date, 01
, date, 02
, ...), meskipun secara total, mereka akan membutuhkan lebih banyak ruang, tetapi mungkin sedikit lebih cepat (karena mereka lebih kecil secara individual). Namun kumpulan buffer mungkin masih mendukung indeks penuh, tergantung pada faktor seperti pola penggunaan dan konfigurasi memori, jadi Anda mungkin harus mengujinya.
Sejak date
adalah bagian dari kunci utama Anda, Anda juga dapat mempertimbangkan untuk mengubah kunci utama menjadi date, esi
. Jika Anda menemukan tanggal dengan kunci utama, Anda tidak memerlukan langkah tambahan untuk mengakses data tabel (karena Anda sudah mengakses tabel), sehingga perilakunya akan mirip dengan indeks penutup. Namun ini adalah perubahan signifikan pada tabel Anda dan dapat memengaruhi semua kueri lainnya (misalnya, gunakan esi
untuk menemukan baris), jadi harus dipertimbangkan dengan hati-hati.
Seperti yang Anda sebutkan, opsi lain adalah membuat tabel ringkasan tempat Anda menyimpan nilai yang telah dihitung sebelumnya, terutama jika Anda tidak menambahkan atau mengubah baris untuk tanggal yang lalu (atau dapat tetap memperbaruinya dengan pemicu).