Mana yang tercepat? SELECT SQL_CALC_FOUND_ROWS DARI `table`, atau SELECT COUNT (*)

176

Saat Anda membatasi jumlah baris yang akan dikembalikan oleh kueri SQL, biasanya digunakan dalam paging, ada dua metode untuk menentukan jumlah total catatan:

Metode 1

Sertakan SQL_CALC_FOUND_ROWSopsi dalam yang asli SELECT, dan kemudian dapatkan jumlah total baris dengan menjalankan SELECT FOUND_ROWS():

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

Metode 2

Jalankan kueri secara normal, lalu dapatkan jumlah total baris dengan menjalankan SELECT COUNT(*)

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

Metode mana yang terbaik / tercepat?

Jrgns
sumber

Jawaban:

120

Tergantung. Lihat posting Blog Kinerja MySQL tentang hal ini: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Hanya ringkasan singkat: Peter mengatakan bahwa itu tergantung pada indeks Anda dan faktor lainnya. Banyak komentar di posting ini sepertinya mengatakan bahwa SQL_CALC_FOUND_ROWS hampir selalu lebih lambat - terkadang hingga 10x lebih lambat - daripada menjalankan dua kueri.

nathan
sumber
27
Saya dapat mengonfirmasi ini - Saya baru saja memperbarui kueri dengan 4 bergabung pada database 168.000 baris. Memilih hanya 100 baris pertama dengan SQL_CALC_FOUND_ROWSwaktu lebih dari 20 detik; menggunakan COUNT(*)kueri terpisah membutuhkan waktu di bawah 5 detik (untuk kueri hitungan + hasil).
Sam Dufel
9
Temuan yang sangat menarik. Sejak dokumentasi MySQL secara eksplisit menunjukkan bahwa SQL_CALC_FOUND_ROWSakan lebih cepat, aku bertanya-tanya dalam situasi apa (jika ada) itu benar-benar adalah lebih cepat!
svidgen
12
topik lama, tetapi bagi mereka yang masih menarik! Baru saja menyelesaikan cek saya di INNODB dari 10 cek, saya dapat mengatakan bahwa itu 26 (2 kueri) terhadap 9,2 (1 permintaan) SELECT SQL_CALC_FOUND_ROWS tblA. *, TblB.id SEBAGAI 'b_id', tblB.city AS 'b_city', tblC.id AS 'C_ID', tblC.type AS 'c_type', tblD.id AS 'd_id', tblD.extype AS 'd_extype', tblY.id AS 'y_id', tblY.ydt AS y_ydt DARI tblA, tblB, tblC, tblD, tblY MANA tblA.b = tblC.id DAN tblA.c = tblB.id DAN tblA.d = tblD.id DAN tblA.y = tblY.id
Al Po
4
Saya baru saja menjalankan percobaan ini dan SQLC_CALC_FOUND_ROWS jauh lebih cepat dari dua pertanyaan. Sekarang tabel utama saya hanya 65k dan dua bergabung dari beberapa ratus, tetapi permintaan utama membutuhkan 0,18 detik dengan atau tanpa SQLC_CALC_FOUND_ROWS tetapi ketika saya menjalankan kueri kedua dengan COUNT ( id) butuh 0,25 saja.
transilvlad
1
Selain kemungkinan masalah kinerja, pertimbangkan yang FOUND_ROWS()telah usang dalam MySQL 8.0.17. Lihat juga jawaban @ madhur-bhaiya.
arueckauer
19

Saat memilih pendekatan "terbaik", pertimbangan yang lebih penting daripada kecepatan mungkin adalah pemeliharaan dan kebenaran kode Anda. Jika demikian, SQL_CALC_FOUND_ROWS lebih disukai karena Anda hanya perlu mempertahankan satu permintaan. Menggunakan satu kueri benar-benar menghalangi kemungkinan perbedaan halus antara kueri utama dan jumlah, yang dapat menyebabkan COUNT tidak akurat.

Jeff Clemens
sumber
11
Ini tergantung pada pengaturan Anda. Jika Anda menggunakan semacam ORM atau pembuat kueri, sangat mudah untuk menggunakan kriteria yang sama di mana untuk kedua kueri, menukar bidang yang dipilih dengan hitungan, dan menjatuhkan batas. Anda seharusnya tidak pernah menulis kriteria dua kali.
mpen
Saya akan menunjukkan bahwa saya lebih suka mempertahankan kode menggunakan dua standar sederhana yang cukup, mudah dimengerti query SQL daripada yang menggunakan fitur MySQL eksklusif - yang patut dicatat sudah usang dalam versi MySQL yang lebih baru.
thomasrutter
15

MySQL telah mulai SQL_CALC_FOUND_ROWSmenghilangkan fungsionalitas dengan versi 8.0.17 dan seterusnya.

Jadi, selalu lebih disukai untuk mempertimbangkan mengeksekusi kueri Anda dengan LIMIT, dan kemudian kueri kedua dengan COUNT(*)dan tanpa LIMITmenentukan apakah ada baris tambahan.

Dari dokumen :

SQL_CALC_FOUND_ROWS pengubah permintaan dan fungsi FOUND_ROWS () yang menyertainya tidak digunakan pada MySQL 8.0.17 dan akan dihapus dalam versi MySQL yang akan datang.

COUNT (*) tunduk pada optimasi tertentu. SQL_CALC_FOUND_ROWS menyebabkan beberapa optimasi dinonaktifkan.

Gunakan kueri ini sebagai gantinya:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

Juga, SQL_CALC_FOUND_ROWStelah diamati memiliki lebih banyak masalah secara umum, seperti yang dijelaskan dalam MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS memiliki sejumlah masalah. Pertama-tama, lambat. Seringkali, akan lebih murah untuk menjalankan kueri dengan LIMIT dan kemudian SELECT COUNT terpisah ( ) untuk permintaan yang sama, karena COUNT ( ) dapat menggunakan pengoptimalan yang tidak dapat dilakukan ketika mencari seluruh rangkaian hasil (misalnya filesort dapat diabaikan untuk COUNT (*), sedangkan dengan CALC_FOUND_ROWS, kita harus menonaktifkan beberapa optimisasi fileort untuk menjamin hasil yang benar)

Lebih penting lagi, ini memiliki semantik yang sangat tidak jelas dalam sejumlah situasi. Secara khusus, ketika sebuah kueri memiliki beberapa blok kueri (misalnya dengan UNION), tidak ada cara untuk menghitung jumlah baris "yang akan pernah" pada saat yang sama dengan menghasilkan kueri yang valid. Ketika eksekutator iterator mengalami kemajuan ke arah pertanyaan semacam ini, sungguh sulit untuk mencoba mempertahankan semantik yang sama. Selain itu, jika ada beberapa LIMIT dalam kueri (misalnya untuk tabel turunan), itu tidak harus jelas ke yang SQL_CALC_FOUND_ROWS mana yang harus dirujuk. Dengan demikian, pertanyaan nontrivial seperti itu tentu akan mendapatkan semantik yang berbeda dalam pelaksana iterator dibandingkan dengan yang mereka miliki sebelumnya.

Akhirnya, sebagian besar kasus penggunaan di mana SQL_CALC_FOUND_ROWS tampaknya berguna harus diselesaikan dengan mekanisme lain selain LIMIT / OFFSET. Misalnya, buku telepon harus diberi nomor halaman dengan huruf (baik dalam hal UX dan dalam hal penggunaan indeks), bukan dengan mencatat nomor. Diskusi semakin tak terbatas-gulir dipesan berdasarkan tanggal (sekali lagi memungkinkan penggunaan indeks), bukan oleh paginasi oleh nomor pos. Dan seterusnya.

Madhur Bhaiya
sumber
Bagaimana cara menjalankan kedua ini sebagai operasi atom? Bagaimana jika seseorang menyisipkan baris sebelum permintaan SELECT COUNT (*)? Terima kasih.
Dom
@ Tom jika Anda memiliki MySQL8 +, Anda dapat menjalankan kedua kueri dalam satu permintaan menggunakan fungsi Window; tetapi ini tidak akan menjadi solusi optimal karena indeks tidak akan digunakan dengan benar. Pilihan lain adalah mengelilingi kedua pertanyaan ini dengan LOCK TABLES <tablename>dan UNLOCK TABLES. Opsi ketiga dan (IMHO terbaik) adalah memikirkan kembali pagination. Silakan baca: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya
14

Menurut artikel berikut: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Jika Anda memiliki INDEKS pada klausa tempat Anda (jika id diindeks pada kasus Anda), maka lebih baik untuk tidak menggunakan SQL_CALC_FOUND_ROWS dan menggunakan 2 kueri sebagai gantinya, tetapi jika Anda tidak memiliki indeks pada apa yang Anda masukkan dalam klausa mana Anda (id dalam kasus Anda) kemudian menggunakan SQL_CALC_FOUND_ROWS lebih efisien.

patapouf_ai
sumber
8

IMHO, alasan mengapa 2 pertanyaan

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

lebih cepat daripada menggunakan SQL_CALC_FOUND_ROWS

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

harus dilihat sebagai kasus tertentu.

Itu sebenarnya tergantung pada selektivitas klausa WHERE dibandingkan dengan selektivitas implisit yang setara dengan ORDER + LIMIT.

Seperti yang dikatakan oleh Arvids dalam komentar ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), fakta bahwa EXPLAIN menggunakan, atau tidak, tabel temporay, harus menjadi dasar yang baik untuk mengetahui apakah SCFR akan lebih cepat atau tidak.

Tetapi, seperti yang saya tambahkan ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), hasilnya benar-benar tergantung pada kasusnya. Untuk paginator tertentu, Anda bisa sampai pada kesimpulan bahwa “untuk 3 halaman pertama, gunakan 2 pertanyaan; untuk halaman-halaman berikut, gunakan SCFR ”!

Pierre-Olivier Vares
sumber
6

Menghapus beberapa SQL yang tidak perlu dan kemudian COUNT(*)akan lebih cepat dari SQL_CALC_FOUND_ROWS. Contoh:

SELECT Person.Id, Person.Name, Job.Description, Card.Number
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
LEFT JOIN Card ON Card.Person_Id = Person.Id
WHERE Job.Name = 'WEB Developer'
ORDER BY Person.Name

Kemudian hitung tanpa bagian yang tidak perlu:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'
Jessé Catrinck
sumber
3

Ada opsi lain yang bisa Anda tolak:

1.) Fungsi jendela akan mengembalikan ukuran aktual secara langsung (diuji dalam MariaDB):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2.) Berpikir di luar kotak, sebagian besar waktu pengguna tidak perlu mengetahui ukuran EXACT dari tabel, perkiraan seringkali cukup baik.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Code4R7
sumber