Mengoptimalkan kondisi WHERE untuk bidang TIMESTAMP dalam pernyataan MySQL SELECT

8

Saya sedang mengerjakan skema untuk sistem analitik yang melacak waktu penggunaan, dan ada kebutuhan untuk melihat total waktu penggunaan dalam rentang tanggal tertentu.

Untuk memberikan contoh sederhana, jenis kueri ini akan sering dijalankan:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Kueri ini biasanya memakan waktu sekitar 7 detik pada tabel yang padat. Ini memiliki ~ 35 juta baris, MyISAM di MySQL berjalan di Amazon RDS (db.m3.xlarge).

Menyingkirkan klausa WHERE membuat kueri hanya membutuhkan waktu 4 detik, dan menambahkan klausa kedua (time_off> XXX) menambahkan 1,5 detik tambahan, menjadikan waktu kueri menjadi 8,5 detik.

Karena saya tahu jenis pertanyaan ini akan umum dilakukan, saya ingin mengoptimalkan beberapa hal sehingga lebih cepat, idealnya di bawah 5 detik.

Saya mulai dengan menambahkan indeks pada time_on, dan meskipun secara drastis mempercepat permintaan WHERE "=", itu tidak berpengaruh pada kueri ">". Apakah ada cara untuk membuat indeks yang akan mempercepat permintaan WHERE ">" atau "<"?

Atau jika ada saran lain tentang kinerja jenis permintaan ini, beri tahu saya.

Catatan: Saya menggunakan bidang "diff_ms" sebagai langkah denormalisasi (sama dengan time_off - time_on) yang meningkatkan kinerja agregasi sekitar 30% -40%.

Saya membuat indeks dengan perintah ini:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

Menjalankan "jelaskan" pada permintaan asli (dengan "time_on>") mengatakan time_on adalah "mungkin_kunci" dan select_type adalah "SIMPLE". Kolom "ekstra" mengatakan "Menggunakan tempat", dan "ketik" adalah "SEMUA". Setelah indeks ditambahkan, tabel mengatakan "time_on" adalah tipe kunci "MUL", yang tampaknya benar karena waktu yang sama dapat hadir dua kali.

Berikut adalah skema tabel:

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

PEMBARUAN: Saya membuat indeks berikut berdasarkan respons ypercube, tetapi ini meningkatkan waktu kueri untuk kueri pertama menjadi sekitar 17 detik!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

UPDATE 2: MENJELASKAN output

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

Pembaruan 3: hasil dari permintaan yang diminta

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)
Locksleyu
sumber
Apakah Anda benar-benar memiliki null di 2 kolom ini ( time_ondan diff_ms)? Apa yang terjadi jika Anda menambahkan dalam kueri WHERE ... AND diff_ms IS NOT NULL?
ypercubeᵀᴹ
Tolong tunjukkan kepada kami output dariSELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ
Juga dijelaskan di "Perbarui 2" Anda menunjukkan " tabel:writetest_table_old " sementara permintaan memiliki from writetest_table. Apakah itu salah ketik atau Anda menjalankan kueri di tabel yang berbeda?
ypercubeᵀᴹ

Jawaban:

3

Saya pikir saya mulai mengerti.

Ketika saya meminta Anda untuk lari

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Anda mengatakan itu 2015-07-13 15:11:56yang Anda miliki di WHEREklausa Anda

Ketika Anda melakukan kueri

select sum(diff_ms) from writetest_table;

Itu melakukan pemindaian tabel penuh 35,8 juta baris.

Ketika Anda melakukan kueri

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Itu melakukan pemindaian indeks penuh 35,8 juta baris.

Sangat masuk akal bahwa permintaan tanpa klausa WHERE lebih cepat. Mengapa

Pemindaian tabel akan membaca 35,8 juta baris dalam satu lintasan linear.

EXPLAIN pada kueri dengan WHERE juga menghasilkan 35,8 juta baris. Pemindaian indeks akan berperilaku sedikit berbeda. Sementara BTREE menjaga urutan kunci, mengerikan untuk melakukan pemindaian jangkauan. Dalam kasus khusus Anda, Anda melakukan pemindaian jangkauan terburuk yang mungkin, yang akan memiliki jumlah entri BTREE yang sama karena ada baris dalam tabel. MySQL harus melintasi halaman BTREE (setidaknya melintasi node daun) untuk membaca nilainya. Selain itu, time_onkolom harus dibandingkan sepanjang jalan dalam urutan yang ditentukan oleh indeks. Oleh karena itu, node BTREE non-daun harus dilalui juga.

Silakan lihat posting saya di BTREEs

Jika kueri per tengah malam hari ini

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

atau bahkan siang hari ini

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

itu harus memakan waktu lebih sedikit.

MORAL OF THE STORY: Jangan gunakan klausa WHERE yang melakukan pemindaian rentang yang sama dengan jumlah baris dalam tabel target.

RolandoMySQLDBA
sumber
Satu-satunya masalah saya adalah bagaimana pergi dari sini. Saya melakukan kueri dengan tanggal yang menghasilkan hanya 1 juta baris yang difilter dan jumlahnya hanya 1 detik. Tetapi kadang-kadang saya mungkin harus melakukan jumlah agregat di sebagian besar data. Ada saran bagaimana menangani ini? Saya berharap bahwa MySQL akan cukup pintar untuk mengetahui kapan harus menggunakan indeks dan kapan tidak, tapi saya kira itu tidak memiliki informasi yang cukup dalam hal ini.
Locksleyu
Saya benar-benar berharap ada semacam indeks yang disusun untuk membuat klausa WHERE menentukan rentang tanggal dengan cepat, yang sepertinya akan mungkin secara teknis untuk diterapkan, tapi saya kira itu tidak didukung.
Locksleyu
Anda memiliki terlalu banyak data dalam jarak sesingkat itu. Tidak ada klausa WHERE yang bisa dikompensasi. Mengapa Bukan indeks yang menjadi masalah. Ini adalah Opini Indeks Pengoptimal Permintaan MySQL. Ketika Anda mulai mengumpulkan lebih banyak data (misalkan bernilai sekitar dua minggu), statistik indeks akan turun dan Anda akan melihat peningkatan kinerja. Hanya saja, jangan melakukan scan indeks penuh.
RolandoMySQLDBA
4

Untuk permintaan spesifik:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

indeks pada (time_on, diff_ms)akan menjadi pilihan terbaik. Jadi, jika kueri berjalan cukup sering atau efisiensinya sangat penting untuk aplikasi Anda, tambahkan indeks ini:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(Tidak terkait dengan pertanyaan)
Dan sungguh, ubah mesin tabel ke InnoDB. Ini tahun 2015 dan pemakaman MyISAM beberapa tahun yang lalu.
(/ kata-kata kasar)

ypercubeᵀᴹ
sumber
Saya membuat indeks persis yang Anda sarankan dan kemudian menjalankan kueri persis yang Anda sebutkan pertama kali dalam respons Anda, tetapi waktunya sekarang jauh lebih buruk, mengambil sekitar 17 detik secara konsisten (saya mencoba beberapa kali).
Locksleyu
Saya tidak tahu apa yang menyebabkannya. Dalam hal ini penting, hanya ada 3671 nilai time_on dalam tabel yang berbeda (ini karena bagaimana skrip pengujian saya mengisi data).
Locksleyu
Anda harus melakukan tiga (3) hal: 1. menjalankan ALTER TABLE writetest_table DROP INDEX time_on;, 2) menjalankan ANALYZE TABLE writetest_table;, dan 3) menjalankan kembali kueri. Apakah waktu kembali ke 7 detik?
RolandoMySQLDBA
1
Anda juga harus berlari EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");. Apakah indeks baru digunakan? Jika tidak digunakan, saya akan mengatakan itu adalah populasi kunci Anda, terutama jika time_on awal Anda hanya beberapa hari yang lalu. Karena jumlah baris meningkat dengan hari yang lebih berbeda, distribusi kunci harus turun dan EXPLAIN akan lebih baik .
RolandoMySQLDBA
RolandoMySQLDBA - Saya mencoba tiga langkah Anda, dan ya waktunya kembali ke 7 detik. Saya melakukan penjelasan dan mengatakan indeks sedang digunakan. Saya masih tidak mengerti mengapa menambahkan indeks seperti ini dapat membuat kinerja lebih dari 2x lebih buruk.
Locksleyu