7

テーブルtがあり、テーブルtに15000のエントリがあるとします。

クエリを想定します

SELECT * FROM t WHERE t.nid <1000

1000行を返します

しかし、最初の10行だけが必要なので、LIMITを実行します

SELECT * FROM t WHERE t.nid <1000 LIMIT 10

上記のLIMIT句を使用して10行の情報を返すだけでなく、WHERE句で設定された条件を満たす行の総数も返す単一のクエリを作成することは可能ですか。したがって、10を返すことに加えて上記の行では、WHERE句を満たす合計1000行があるため、1000を返します...両方とも1つのクエリで返されます

4

6 に答える 6

9

推奨されるソリューション

まず、found_rows()関数は移植性がなく(MySQL拡張機能です)、削除されます。ユーザー@Zveddochkaが指摘したように、MySQL8.0.17ではすでに非推奨になっています。

しかし、もっと重要なことは、適切なインデックスを使用すると、2つのクエリを実行する方が実際には高速であることがわかります。このSQL_CALC_FOUND_ROWSディレクティブは、追加のリカバリコストが発生する「仮想スキャン」によって実現されます。クエリにインデックスが付けられていない場合、このコストはと同じにCOUNT()なります。したがって、2つのクエリを実行すると、コストが2倍になります。つまり、を使用SQL_CALC_FOUND_ROWSすると、実行速度が50%速くなります。

しかし、クエリ適切にインデックス付けされるとどうなりますか?Perconaの人たちはそれをチェックしました。また、COUNT()メタデータとインデックスにのみアクセスするため、が高速であるだけでSQL_CALC_FOUND_ROWSなく、追加コストが発生しないため、なしのクエリが高速であることがわかります。2つのクエリを組み合わせたコストは、拡張された単一のクエリのコストよりも低くなります

SQL_CALC_FOUND_ROWSの結果は次のとおりです。各b値について、キャッシュされていない実行には20〜100秒、ウォームアップ後は2〜5秒かかります。このような違いは、このクエリに必要なI / Oによって説明できます。mysqlは、LIMIT句なしでこのクエリが生成できるすべての10k行にアクセスします。

The results are following: it takes 0.01-0.11 sec to run this query first time and 0.00-0.02 sec for all consecutive runs.

So, as we can see, total time for SELECT+COUNT (0.00-0.15 sec) is much less than execution time for original query (2-100 sec). Let’s take a look at EXPLAINs...

So, what to do?

// Run two queries ensuring they satisfy exactly the same conditions

$field1 = "Field1, Field2, blah blah blah";
$field2 = "COUNT(*) AS rows";

$where  = "Field5 = 'X' AND Field6 = 'Y' AND blah blah";

$cntQuery = "SELECT {$field2} FROM {$joins} WHERE {$where}";
$rowQuery = "SELECT {$field1} FROM {$joins} WHERE {$where} LIMIT {$limit}";

Now the first query returns the count, the second query returns the actual data.

Old answer (useful just for non-indexed tables)

Don't do this. If you find out this section of the answer works for you better than the section above, it's almost certainly a signal that something else is not optimal in your setup - most likely you're not using the indexes properly, or you need to update your MySQL server, or run an analyze/optimize of the database to update cardinality statistics.

できますが、パフォーマンスのキラーになると思います。

最善のオプションは、SQL_CALC_FOUND_ROWSMySQL拡張機能を使用し、2番目のクエリを発行してFOUND_ROWS()を使用して行の全数を回復することです。

 SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;
 SELECT FOUND_ROWS();

たとえば、http ://www.arraystudio.com/as-workshop/mysql-get-total-number-of-rows-when-using-limit.htmlを参照してください。

LIMITまたは、句なしで完全なクエリを実行し、最初の10行のみを取得することもできます。次に、必要に応じて1つのクエリを使用し、を介して行数を取得することもできますmysql_num_rows()。これは理想的ではありませんが、ほとんどのクエリにとってそれほど壊滅的ではありません。

ただし、これを最後に行う場合は、クエリを閉じてそのリソースを解放するように十分注意してください。完全な結果セットよりも少ない値を取得し、rsハンドルを解放するのを忘れることが、「メタデータロック」の顕著な原因の1つであることがわかりました。

于 2012-09-28T18:28:48.607 に答える
4

を試すことができSQL_CALC_FOUND_ROWSます。これにより、ステートメントを再度実行しなくても、合計レコード数を取得できます。

SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;   -- get records
SELECT FOUND_ROWS();                -- get count

参照:http ://dev.mysql.com/doc/refman/5.0/en/information-functions.html

于 2012-09-28T18:27:22.837 に答える
4

「上記のLIMIT句を使用して10行の情報を返すだけでなく、WHERE句で設定された条件を満たす行の総数も返す単一のクエリを作成することは可能ですか?」

はい、ウィンドウ関数COUNT(*) OVER()(MySQL 8.0+)を使用して、単一のクエリで両方を実行できます。

SELECT t.*, COUNT(*) OVER() AS cnt 
FROM t 
WHERE t.nid <1000 
LIMIT 10;  

db<>フィドルデモ


サイドノート:

LIMIT明示的ORDER BYでない場合は非決定論的です。複数の実行間で異なる結果が返される可能性があります。

于 2021-08-01T10:19:07.410 に答える
2

話し合う必要のあることがたくさんあります。

  • LIMITなしのaORDER BYはやや予測不可能であるため、やや無意味です。

  • ただし、を追加するORDER BYと、すべての行を検索して並べ替えてから、必要な10個だけを配信する必要がある場合があります。

  • または、ORDER BYによって適切に処理される場合がありますINDEX

特定のクエリが2つのクエリに変換された場合(8.0.17以降の必要に応じて)、

SELECT * FROM t WHERE t.nid < 1000 LIMIT 10;
SELECT COUNT(*) FROM t WHERE t.nid < 1000;

それらのそれぞれがから恩恵を受けることに注意してくださいINDEX(nid)。1つ目はインデックスのBTreeから10個のアイテムを選択し、次にデータのBTreeでそれらを検索します。それぞれで10行だけがタッチされます。2つ目は、インデックスが1000に達するまでスキャンし、データBTreeに触れないようにします。

アドバイスに従ってORDERBYを追加すると、最初のクエリは次のようになります。

SELECT * FROM t WHERE t.nid < 1000 ORDER BY t.nid LIMIT 10;

上記と同じように機能します。だが

SELECT * FROM t WHERE t.nid < 1000 ORDER BY t.abcd LIMIT 10;

たくさんの行をスキャンする必要があり、かなり遅くなります。そして、おそらく一時テーブルとファイルソートを使用します。(詳細を確認EXPLAINしてください。) INDEX(nid, abcd)役立つでしょうが、ほんの少しです。

また、インデックスが「カバー」できる場合など、他のバリエーションもあります。

「1つのクエリ」を持つことの目標は何ですか?

  • スピード?-上記で説明したように、より適切な他の要因があります。

  • 一貫性?--たとえば、最初のクエリからN行を取得し、から少数の行を取得することを回避するために、トランザクションが必要になる場合がありますCOUNT

     BEGIN;
     SELECT * ...
     SELECT COUNT(*) ...
     COMMIT;
    
  • 単一のコマンド?--2つのステートメントを組み合わせたストアドプロシージャについて考えてみます。または

     SELECT * FROM t WHERE t.nid < 1000 LIMIT 10
     UNION ALL
     SELECT COUNT(*) FROM t WHERE t.nid < 1000;
    

ただし、列の数が異なるため、これは注意が必要です。したがって、2番目のクエリの列数を同じにするためには、多少の手間がかかります。別のバリアントにはが含まれGROUP BY WITH ROLLUPます。(しかし、製造するのはさらに難しいかもしれません。)

Lukaszの答えは有望に見えます。ただし、追加の列が表示されるため(これは良い場合があります)、パフォーマンスをテストする必要があります。8.0を使用していて、その回答がうまく機能する場合は、その回答を受け入れてください。

于 2021-08-02T00:57:24.630 に答える
0

Count(*)の時間計算量はO(1)であるため、サブクエリを使用できます

SELECT *, (SELECT COUNT(*) FROM t WHERE t.nid <1000) AS cnt
FROM t 
WHERE t.nid <1000 
LIMIT 10
于 2021-08-02T15:46:25.960 に答える
-1

FOUND_ROWS()が必要なようです

SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;
SELECT FOUND_ROWS();
于 2012-09-28T18:29:03.897 に答える