Mengapa MYSQL LIMIT yang lebih tinggi diimbangi memperlambat permintaan?

173

Skenario singkatnya: Sebuah tabel dengan lebih dari 16 juta catatan [ukuran 2GB]. Semakin tinggi LIMIT diimbangi dengan SELECT, semakin lambat kueri menjadi, saat menggunakan ORDER OLEH * primary_key *

Begitu

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

membutuhkan waktu jauh lebih sedikit daripada

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

Itu hanya memesan 30 catatan dan bagaimanapun juga. Jadi bukan overhead dari ORDER BY.
Sekarang ketika mengambil 30 baris terbaru dibutuhkan sekitar 180 detik. Bagaimana saya bisa mengoptimalkan permintaan sederhana itu?

Rahman
sumber
CATATAN: Saya penulis. MySQL tidak merujuk ke indeks (PRIMARY) dalam kasus di atas. lihat tautan di bawah ini oleh pengguna "Quassnoi" untuk penjelasan.
Rahman

Jawaban:

197

Itu normal bahwa offset yang lebih tinggi memperlambat permintaan, karena permintaan harus menghitung OFFSET + LIMITcatatan pertama (dan hanya mengambilLIMIT dari mereka). Semakin tinggi nilai ini, semakin lama kueri berjalan.

Kueri tidak dapat langsung ke OFFSET karena, pertama, catatan bisa memiliki panjang yang berbeda, dan, kedua, bisa ada kesenjangan dari catatan yang dihapus. Perlu memeriksa dan menghitung setiap catatan pada jalannya.

Dengan asumsi bahwa idadalah PRIMARY KEYdari MyISAMmeja, Anda dapat mempercepat itu dengan menggunakan trik ini:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

Lihat artikel ini:

Quassnoi
sumber
7
Perilaku MySQL "pencarian baris awal" adalah jawaban mengapa itu berbicara begitu lama. Dengan trik yang Anda berikan, hanya id yang cocok (dengan indeks langsung) yang terikat, menyimpan pencarian baris yang tidak dibutuhkan dari terlalu banyak catatan. Itu berhasil, hore!
Rahman
4
@harald: apa sebenarnya yang Anda maksud dengan "tidak bekerja"? Ini adalah peningkatan kinerja murni. Jika tidak ada indeks yang dapat digunakan oleh ORDER BYatau indeks mencakup semua bidang yang Anda butuhkan, Anda tidak perlu solusi ini.
Quassnoi
6
@ f055: jawabannya mengatakan "percepat", bukan "jadikan instan". Sudahkah Anda membaca kalimat pertama dari jawabannya?
Quassnoi
3
Apakah mungkin menjalankan sesuatu seperti ini untuk InnoDB?
NeverEndingQueue
3
@Lanti: silakan posting sebagai pertanyaan terpisah dan jangan lupa untuk menandainya postgresql. Ini adalah jawaban khusus MySQL.
Quassnoi
220

Saya sendiri memiliki masalah yang sama persis. Mengingat fakta bahwa Anda ingin mengumpulkan sejumlah besar data ini dan bukan set khusus 30 Anda mungkin akan menjalankan loop dan menambah offset dengan 30.

Jadi yang bisa Anda lakukan adalah:

  1. Tahan id terakhir dari serangkaian data (30) (mis. LastId = 530)
  2. Tambahkan kondisinya WHERE id > lastId limit 0,30

Jadi, Anda selalu dapat memiliki NOL offset. Anda akan kagum dengan peningkatan kinerja.

Nikos Kyr
sumber
Apakah ini berfungsi jika ada celah? Bagaimana jika Anda tidak memiliki kunci unik tunggal (misalnya kunci komposit)?
xaisoft
8
Mungkin tidak jelas bagi semua bahwa ini hanya berfungsi jika set hasil Anda diurutkan berdasarkan kunci itu, dalam urutan naik (untuk urutan menurun, ide yang sama berfungsi, tetapi ubah> lastid menjadi <lastid.) Tidak masalah apakah itu adalah kunci utama, atau bidang lain (atau grup bidang.)
Eloff
Bagus sekali, pria itu! Solusi yang sangat sederhana yang telah memecahkan masalah saya :-)
oodavid
30
Hanya catatan bahwa limit / offset sering digunakan dalam hasil paginasi, dan menahan lastId sama sekali tidak mungkin karena pengguna dapat melompat ke halaman mana pun, tidak selalu halaman berikutnya. Dengan kata lain, offset sering kali perlu dihitung secara dinamis berdasarkan halaman dan batas, bukannya mengikuti pola kontinu.
Tom
3
Saya berbicara lebih jauh tentang "mengingat di mana Anda tinggalkan" di mysql.rjweb.org/doc.php/pagination
Rick James
17

MySQL tidak dapat langsung menuju ke catatan 10.000 (atau byte 80000 sebagai saran Anda) karena ia tidak dapat menganggap bahwa itu dikemas / dipesan seperti itu (atau bahwa ia memiliki nilai kontinu dalam 1 hingga 10.000). Meskipun mungkin seperti itu dalam kenyataannya, MySQL tidak dapat berasumsi bahwa tidak ada lubang / kesenjangan / id yang dihapus.

Jadi, seperti yang dicatat bobs, MySQL harus mengambil 10.000 baris (atau melintasi hingga 10.000 entri indeks aktif id) sebelum menemukan 30 untuk kembali.

EDIT : Untuk mengilustrasikan poin saya

Perhatikan bahwa meskipun

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

akan lambat (er) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

akan cepat (er) , dan akan mengembalikan hasil yang sama asalkan tidak ada yang hilang id(yaitu kesenjangan).

Riedsio
sumber
2
Ini benar. Tetapi karena dibatasi oleh "id", mengapa perlu waktu begitu lama ketika id itu berada dalam indeks (kunci utama)? Pengoptimal harus merujuk ke indeks itu secara langsung, dan kemudian mengambil baris dengan id yang cocok (yang berasal dari indeks itu)
Rahman
1
Jika Anda menggunakan klausa WHERE pada id, itu bisa langsung ke tanda itu. Namun, jika Anda membatasinya, dipesan oleh id, itu hanya penghitung relatif ke awal, sehingga harus melintang seluruhnya.
Riedsio
Artikel yang sangat bagus, eversql.com/...
Pa
Bekerja untuk saya @Riedsio Terima kasih.
mahesh kajale
8

Saya menemukan contoh menarik untuk mengoptimalkan permintaan SELECT ORDER BY id LIMIT X, Y. Saya memiliki 35 juta baris sehingga butuh 2 menit untuk menemukan berbagai baris.

Ini triknya:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

Cukup masukkan WHERE dengan id terakhir yang Anda peroleh meningkatkan kinerja. Bagi saya itu dari 2 menit hingga 1 detik :)

Trik menarik lainnya di sini: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Ini juga berfungsi dengan string

sym
sumber
1
ini hanya berfungsi untuk tabel, di mana tidak ada data yang dihapus
miro
1
@miro Itu hanya benar jika Anda bekerja di bawah asumsi bahwa permintaan Anda dapat melakukan pencarian di halaman acak, yang saya tidak percaya poster ini berasumsi. Meskipun saya tidak suka metode ini untuk sebagian besar kasus dunia nyata, ini akan bekerja dengan kesenjangan selama Anda selalu mendasarkannya pada id terakhir yang diperoleh.
Gremio
5

Bagian yang menyita waktu dari dua kueri mengambil baris dari tabel. Secara logis, dalam LIMIT 0, 30versi ini, hanya 30 baris yang perlu diambil. Dalam LIMIT 10000, 30versi ini, 10.000 baris dievaluasi dan 30 baris dikembalikan. Mungkin ada beberapa optimasi yang dapat saya lakukan dalam proses membaca data, tetapi pertimbangkan hal berikut:

Bagaimana jika Anda memiliki klausa WHERE dalam kueri? Mesin harus mengembalikan semua baris yang memenuhi syarat, lalu mengurutkan data, dan akhirnya mendapatkan 30 baris.

Juga pertimbangkan kasus di mana baris tidak diproses dalam urutan ORDER BY. Semua baris yang memenuhi syarat harus disortir untuk menentukan baris mana yang akan dikembalikan.

bobs
sumber
1
hanya ingin tahu mengapa ia menghabiskan waktu untuk mengambil 10.000 baris itu. Indeks yang digunakan pada bidang itu (id, yang merupakan kunci utama) harus membuat pengambilan baris tersebut secepat mencari indeks PK untuk catatan no. 10000, yang pada gilirannya seharusnya secepat mencari file ke offset itu dikalikan dengan panjang catatan indeks, (yaitu, mencari 10.000 * 8 = byte no 80000 - mengingat bahwa 8 adalah panjang catatan indeks)
Rahman
@Rahman - Satu-satunya cara untuk menghitung melewati 10.000 baris adalah dengan melangkahi mereka satu per satu. Ini mungkin hanya melibatkan indeks, tetapi masih baris indeks membutuhkan waktu untuk melangkah. Tidak ada struktur MyISAM atau InnoDB yang dapat dengan benar (dalam semua kasus) "mencari" untuk merekam 10000. Saran 10000 * 8 mengasumsikan (1) MyISAM, (2) catatan panjang TETAP, dan (3) tidak pernah ada penghapusan dari tabel . Bagaimanapun, indeks MyISAM adalah BTree, jadi itu tidak akan berfungsi.
Rick James
Seperti yang dinyatakan oleh jawaban ini, saya percaya, bagian yang sangat lambat adalah pencarian baris, tidak melintasi indeks (yang tentu saja akan bertambah juga, tetapi jauh dari jumlah pencarian baris pada disk). Berdasarkan kueri pemecahan masalah yang disediakan untuk masalah ini, saya yakin pencarian baris cenderung terjadi jika Anda memilih kolom di luar indeks - bahkan jika itu bukan bagian dari pesanan pada atau di mana klausa. Saya belum menemukan alasan mengapa ini perlu, tetapi tampaknya itulah mengapa beberapa solusi membantu.
Gremio
1

Bagi mereka yang tertarik dengan perbandingan dan angka :)

Eksperimen 1: Dataset berisi sekitar 100 juta baris. Setiap baris berisi beberapa BIGINT, TINYINT, serta dua bidang TEKS (sengaja) yang berisi sekitar 1 rb karakter.

  • Biru: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Oranye: = @ metode Quassnoi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Tentu saja, metode ketiga ... WHERE id>xxx LIMIT 0,5,, tidak muncul di sini karena itu harus waktu yang konstan.

Eksperimen 2: Hal serupa, kecuali bahwa satu baris hanya memiliki 3 BIGINT.

  • hijau: = biru sebelumnya
  • merah: = oranye sebelumnya

masukkan deskripsi gambar di sini

ch271828n
sumber