どちらが最速ですか?SELECT SQL_CALC_FOUND_ROWS FROM `table`、またはSELECT COUNT(*)


176

SQLクエリによって返される行数を制限する場合、通常はページングで使用されますが、レコードの合計数を決定するには2つの方法があります。

方法1

SQL_CALC_FOUND_ROWS元のにオプションを含め、SELECT次のコマンドを実行して行の総数を取得しますSELECT FOUND_ROWS()

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

方法2

クエリを通常どおり実行し、次のコマンドを実行して行の総数を取得します SELECT COUNT(*)

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

どの方法が最善/最速ですか?

回答:


120

場合によります。この件に関するMySQLパフォーマンスブログの投稿を参照してください。http//www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

簡単な要約:ピーターはそれはあなたのインデックスや他の要因に依存すると言います。投稿へのコメントの多くは、SQL_CALC_FOUND_ROWSが2つのクエリを実行するよりもほとんど常に遅い-時には最大10倍遅い-と述べているようです。


27
これを確認できます。168,000行のデータベースで4つの結合を使用してクエリを更新しました。最初の100行だけを選択すると、SQL_CALC_FOUND_ROWS20秒以上かかりました。別のCOUNT(*)クエリを使用すると、5秒未満かかりました(カウント+結果クエリの両方)。
Sam Dufel

9
非常に興味深い発見。以来MySQLのドキュメントが明示的にそれが示唆SQL_CALC_FOUND_ROWS速くなります、私はそれが実際には(もしあれば)どのような状況で不思議です速いです!
svidgen 2013年

12
古いトピックですが、まだ面白い人のために!INNODBのチェックを10のチェックから終えたところ、9.2(1クエリ)に対して26(2query)であることがわかります。SELECT SQL_CALC_FOUND_ROWS tblA。*、tblB.id AS '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 FROM tblAtblBtblCtblDtblY tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
この実験を実行したところ、SQLC_CALC_FOUND_ROWSは2つのクエリよりもはるかに高速でした。現在、私のメインテーブルはわずか65kで数百の2つの結合ですが、メインクエリはSQLC_CALC_FOUND_ROWSの有無にかかわらず0.18秒かかりますが、COUNT(id)を使用して2番目のクエリを実行すると、0.25だけかかりました。
transilvlad 2014年

1
考えられるパフォーマンスの問題に加えてFOUND_ROWS()、MySQL 8.0.17では非推奨になっていることを考慮してください。@ madhur-bhaiyaの回答も参照してください。
arueckauer

19

「最良の」アプローチを選択する場合、速度よりも重要な考慮事項は、コードの保守性と正確性です。その場合、単一のクエリを維持するだけでよいので、SQL_CALC_FOUND_ROWSが推奨されます。単一のクエリを使用すると、メインクエリとカウントクエリの間にわずかな違いが生じる可能性が完全に排除され、COUNTが不正確になる可能性があります。


11
これはセットアップによって異なります。ある種のORMまたはクエリビルダーを使用している場合、両方のクエリのwhere条件を同じにして、選択したフィールドをカウントに入れ替えて、制限をなくすのは非常に簡単です。基準を2回書いてはいけません。
mpen 14

独自のMySQL機能を使用するSQLクエリよりも、かなり標準的で理解しやすい2つの単純なSQLクエリを使用してコードを維持するほうがよいと指摘します。これは、新しいMySQLバージョンでは非推奨です。
thomasrutter

15

MySQLは非推奨になりました SQL_CALC_FOUND_ROWSバージョン8.0.17以降で機能を。

だから、され、常に優先してクエリを実行し検討することLIMIT、及びその後で2番目のクエリCOUNT(*)とせずにLIMIT、追加の行があるかどうかを判断します。

ドキュメントから:

SQL_CALC_FOUND_ROWSクエリ修飾子と付随するFOUND_ROWS()関数は、MySQL 8.0.17で非推奨になり、将来のMySQLバージョンで削除される予定です。

COUNT(*)は特定の最適化の対象です。SQL_CALC_FOUND_ROWSは、一部の最適化を無効にします。

代わりに次のクエリを使用してください。

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

また、MySQL WL#12615でSQL_CALC_FOUND_ROWS説明されているように、一般により多くの問題があることが確認されています

SQL_CALC_FOUND_ROWSには多くの問題があります。まず第一に、それは遅いです。多くの場合、COUNT()のため、クエリをLIMITで実行してから、同じクエリに対して個別のSELECT COUNT(を実行する方が安価です。)は結果セット全体を検索するときに実行できない最適化(f​​ilesortなど)を利用できるためです。 COUNT(*)の場合はスキップできますが、CALC_FOUND_ROWSでは、正しい結果を保証するために一部のファイルソート最適化を無効にする必要があります)

さらに重要なことに、多くの状況でセマンティクスが非常に不明確です。特に、クエリに複数のクエリブロックがある場合(UNIONなど)、有効なクエリを生成すると同時に「would-have-been」行の数を計算する方法はありません。イテレーターエグゼキューターがこれらの種類のクエリに向かって進んでいるため、同じセマンティクスを維持しようとすることは本当に困難です。さらに、クエリに複数のLIMITがある場合(派生テーブルなど)、SQL_CALC_FOUND_ROWSが参照する必要があるのは必ずしも明確ではありません。したがって、そのような自明ではないクエリは、イテレータエグゼキュータで以前とは異なるセマンティクスを取得する必要があります。

最後に、SQL_CALC_FOUND_ROWSが役立つと思われるほとんどの使用例は、LIMIT / OFFSET以外のメカニズムで解決する必要があります。たとえば、電話帳は、レコード番号ではなく、文字(UXの観点とインデックスの使用の両方)でページ番号を付ける必要があります。ディスカッションは、投稿番号によるページ番号順ではなく、日付順(インデックスの使用を許可する順)で、無限スクロールの順序になっています。等々。


この2つの選択をアトミック操作として実行する方法は?SELECT COUNT(*)クエリの前に誰かが行を挿入するとどうなりますか?ありがとう。
ドム

@Dom MySQL8 +を使用している場合は、ウィンドウ関数を使用して1つのクエリで両方のクエリを実行できます。ただし、インデックスが適切に使用されないため、これは最適なソリューションではありません。別のオプションは、これらの2つのクエリをLOCK TABLES <tablename>およびで囲むことですUNLOCK TABLES。3番目のオプションと(最高のIMHO)ページネーションを再考することです。読んでください:mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya

14

次の記事によると:https : //www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

where句にINDEXがある場合(ケースでidにインデックスが付けられている場合)、SQL_CALC_FOUND_ROWSを使用せず、代わりに2つのクエリを使用することをお勧めしますが、where句に入力したものにインデックスがない場合(あなたの場合はid)次にSQL_CALC_FOUND_ROWSを使用するが効率的です。


8

IMHO、2つのクエリを実行する理由

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

SQL_CALC_FOUND_ROWSを使用するよりも高速

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

特定のケースと見なす必要があります。

実際には、ORDER + LIMITに相当する暗黙的な選択性と比較して、WHERE句の選択性に依存します。

Arvidsがコメントで述べたように(http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394が使用するかどうかという事実テンポレイテーブルは、SCFRが高速になるかどうかを知るための良いベースになるはずです。

しかし、私が追加したように(http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482)、結果は本当に、本当にケースに依存します。特定のページ編集者について、「最初の3ページには2つのクエリを使用します。次のページでは、SCFRを使用してください。


6

一部の不要なSQLを削除COUNT(*)すると、よりも高速になりSQL_CALC_FOUND_ROWSます。例:

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

次に、不要な部分なしでカウントします。

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

3

ベンチマークする他のオプションがあります:

1.)ウィンドウ関数は実際のサイズを直接返します(MariaDBでテスト済み):

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

2.)箱から出して考えると、ほとんどの場合、ユーザーはテーブルの正確なサイズを知る必要はありませんが、多くの場合、概算で十分です。

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.