Hangisi en hızlı? `Table 'dan SQL_CALC_FOUND_ROWS veya SELECT COUNT (*)


176

Genellikle disk belleği olarak kullanılan bir SQL sorgusu tarafından döndürülecek satır sayısını sınırladığınızda, toplam kayıt sayısını belirlemek için iki yöntem vardır:

Yöntem 1

SQL_CALC_FOUND_ROWSSeçeneği orijinal belgeye dahil edin SELECTve ardından çalıştırarak toplam satır sayısını elde edin SELECT FOUND_ROWS():

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

Yöntem 2

Sorguyu normal şekilde çalıştırın ve ardından çalıştırarak toplam satır sayısını alın SELECT COUNT(*)

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

Hangi yöntem en iyi / en hızlı?

Yanıtlar:


120

Değişir. Bu konuyla ilgili MySQL Performans Blog yayınına bakın: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Kısa bir özet: Peter bunun dizinlerinize ve diğer faktörlere bağlı olduğunu söylüyor. Gönderiye yapılan yorumların çoğu, SQL_CALC_FOUND_ROWS'un iki sorgu çalıştırmaktan neredeyse her zaman daha yavaş - bazen 10 kata kadar daha yavaş olduğunu söylüyor.


27
Bunu onaylayabilirim - Ben sadece 168.000 satır veritabanında 4 birleştirme ile bir sorgu güncelledim. Sadece ilk 100 satırı seçmek SQL_CALC_FOUND_ROWS20 saniyeyi aştı; ayrı bir COUNT(*)sorgu kullanmak 5 saniyenin altında sürdü (hem count + sonuç sorguları için).
Sam Dufel

9
Çok ilginç bulgular. Yana MySQL'ın belgelerine açıkça ortaya koymaktadır SQL_CALC_FOUND_ROWShızlı olacaktır, ben aslında (eğer varsa) Ne durumlarda merak olduğunu hızlı!
svidgen

12
eski konu, ama hala ilginç olanlar için! 10 çekten INNODB üzerindeki kontrolümü yeni bitirdim. 9.2 (1 sorgu) ile 26 (2 sorgu) olduğunu söyleyebilirim SELECT SQL_CALC_FOUND_ROWS tblA. *, TblB.id AS 'b_id', tblB.city AS 'b_city', tblC.id AS 'c_id', tblC.type A.Ş.'nin 'c_type', tblD.id A.Ş.'nin 'd_id', tblD.extype A.Ş.'nin 'd_extype', tblY.id A.Ş.'nin 'y_id' DAN tblY.ydt AS y_ydt tblA, tblB, tblC, tblD, tblY NEREDE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
Bu denemeyi yeni çalıştırdım ve SQLC_CALC_FOUND_ROWS iki sorgudan çok daha hızlıydı. Şimdi benim ana tablo sadece 65k ve birkaç yüz iki birleşim, ancak ana sorgu SQLC_CALC_FOUND_ROWS ile veya olmadan 0.18 saniye sürer, ancak COUNT ( id) ile ikinci bir sorgu çalıştırdığımda sadece 0.25 aldı.
14'te transilvlad

1
Olası performans sorunlarına ek FOUND_ROWS()olarak, MySQL 8.0.17'de kullanımdan kaldırılmış olduğunu da göz önünde bulundurun . Ayrıca bakınız @ madhur-bhaiya'nın cevabı.
arueckauer

19

"En iyi" yaklaşımı seçerken, hızdan daha önemli bir husus, kodunuzun sürdürülebilirliği ve doğruluğu olabilir. Öyleyse, SQL_CALC_FOUND_ROWS tercih edilir, çünkü yalnızca tek bir sorgu tutmanız gerekir. Tek bir sorgu kullanmak, ana ve sayım sorguları arasında yanlış bir COUNT değerine yol açabilecek küçük bir fark olasılığını tamamen engeller.


11
Bu, kurulumunuza bağlıdır. Bir tür ORM veya sorgu oluşturucu kullanıyorsanız, her iki sorgu için de ölçütleri kullanmak, bir sayım için seçim alanlarını değiştirmek ve sınırı bırakmak için aynı işlemi kullanmak çok kolaydır. Kriterleri asla iki kez yazmamalısınız.
mpen

Daha yeni MySQL sürümlerinde kullanımdan kaldırılan, tescilli bir MySQL özelliği kullanan bir tane yerine iki basit oldukça standart, anlaşılması kolay SQL sorgusu kullanarak kod sürdürmeyi tercih ederim.
thomasrutter

15

MySQL, SQL_CALC_FOUND_ROWS8.0.17 ve sonraki sürümlerle işlevselliği kullanımdan kaldırmaya başladı .

Bu nedenle, her zaman sorgunuzu çalıştırmayı LIMITve sonra ek satırlar olup olmadığını belirlemek için COUNT(*)ve olmadan ikinci bir sorgu kullanmayı düşünmek tercih edilirLIMIT .

Gönderen docs :

SQL_CALC_FOUND_ROWS sorgu değiştiricisi ve ona eşlik eden FOUND_ROWS () işlevi MySQL 8.0.17'den itibaren kullanımdan kaldırılmıştır ve gelecekteki bir MySQL sürümünde kaldırılacaktır.

COUNT (*) belirli optimizasyonlara tabidir. SQL_CALC_FOUND_ROWS bazı optimizasyonların devre dışı bırakılmasına neden olur.

Bunun yerine şu sorguları kullanın:

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

Ayrıca, MySQL WL # 12615'teSQL_CALC_FOUND_ROWS açıklandığı gibi, genel olarak daha fazla sorun yaşadığı gözlemlenmiştir :

SQL_CALC_FOUND_ROWS'un bir takım sorunları vardır. Her şeyden önce, yavaş. COUNT ( ), sonuç kümesinin tamamını (ör. Filesort) ararken gerçekleştirilemeyen optimizasyonları kullanabildiğinden, sorguyu aynı sorgu için LIMIT ve ardından ayrı bir SELECT COUNT ( ) çalıştırmak daha ucuz olacaktır. COUNT (*) için atlanabilirken, CALC_FOUND_ROWS ile doğru sonucu garanti etmek için bazı dosya sırası optimizasyonlarını devre dışı bırakmalıyız)

Daha da önemlisi, bazı durumlarda çok net olmayan anlambilimine sahiptir. Özellikle, bir sorguda birden çok sorgu bloğu varsa (örn. UNION ile), geçerli bir sorgu oluştururken aynı zamanda “olurdu” satır sayısını hesaplamanın bir yolu yoktur. Yineleyici yürütücü bu tür sorgulara doğru ilerlerken, aynı semantiği korumaya çalışmak gerçekten zordur. Ayrıca, sorguda birden fazla LIMIT varsa (örneğin, türetilmiş tablolar için) SQL_CALC_FOUND_ROWS'un hangisine başvurması gerektiği açık değildir. Bu nedenle, bu tür önemsiz olmayan sorgular, yineleyici yürütücüsünde daha önce sahip olduklarına kıyasla mutlaka farklı semantikler alacaktır.

Son olarak, SQL_CALC_FOUND_ROWS'un yararlı göründüğü kullanım durumlarının çoğu LIMIT / OFFSET dışındaki mekanizmalarla çözülmelidir. Örneğin, bir telefon defteri kayıt numarasına göre değil, harfle (hem UX açısından hem de dizin kullanımı açısından) sayfalandırılmalıdır. Tartışmalar, posta numarasına göre sayfalandırılmış değil, tarihe göre giderek artan bir şekilde sonsuz kaydırma sırasına göre sıralanır (yine dizin kullanımına izin verir). Ve bunun gibi.


Bu iki seçim atomik işlem olarak nasıl yapılır? Birisi SELECT COUNT (*) sorgusundan önce bir satır eklerse ne olur? Teşekkürler.
Dom

@Dom MySQL8 + 'nız varsa, Pencere işlevlerini kullanarak her iki sorguyu tek bir sorguda çalıştırabilirsiniz; ancak dizinler düzgün kullanılmayacağından bu en uygun çözüm olmayacaktır. Başka bir seçenek de bu iki sorguyu LOCK TABLES <tablename>ve ile çevrelemektir UNLOCK TABLES. Üçüncü seçenek ve (en iyi IMHO) sayfalandırmayı yeniden düşünmektir. Lütfen okuyun: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya


8

IMHO, neden 2 sorgu

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

SQL_CALC_FOUND_ROWS kullanmaktan daha hızlı

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

özel bir durum olarak görülmelidir.

Gerçekte, SİPARİŞ + SINIR ile eşdeğer örtük olanın seçiciliğine kıyasla WHERE maddesinin seçiciliğine bağlıdır.

Arvids'in yorumda söylediği gibi ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), EXPLAIN'in kullandığı ya da kullanmadığı gerçeği, bir geçici tablo, SCFR'nin daha hızlı olup olmayacağını bilmek için iyi bir temel olmalıdır.

Ancak, eklediğim gibi ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), sonuç gerçekten duruma bağlıdır. Belirli bir sayfa için, “ilk 3 sayfa için 2 sorgu kullanın; sonraki sayfalar için bir SCFR kullanın ”!


6

Gereksiz bazı SQL'leri kaldırmak ve daha sonra COUNT(*)daha hızlı olacaktır SQL_CALC_FOUND_ROWS. Misal:

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

Sonra gereksiz parça olmadan sayın:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'

3

Kıyaslama yapabileceğiniz başka seçenekler de vardır:

1.) Bir pencere fonksiyonu gerçek boyutu doğrudan döndürecektir (MariaDB'de test edilmiştir):

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

2.) Kutunun dışında düşünmek, çoğu zaman kullanıcıların tablonun EXACT boyutunu bilmesine gerek yoktur , yaklaşık bir değer genellikle yeterlidir.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.