確かに多くの人がmySQLのFOUND_ROWS()関数の存在を知っています。 これは、指定された条件を満たすテーブル内の行の総数を計算するために最もよく使用されます。 通常、次のようにSQL_CALC_FOUND_ROWSディレクティブとともに使用されます。
Mysql> SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE column > 1 LIMIT 0, 50;
Mysql> SELECT FOUND_ROWS();
2番目のクエリの結果は、条件 "column> 1"(最初のクエリで返されたものだけでなく)を満たすテーブル "table"の行の総数になります。
この関数のいくつかの機能は、SQL_CALC_FOUND_ROWSディレクティブなしで使用する場合に考慮する必要があります。mySQLのドキュメントは誠意をもって警告しています。この場合、処理された行の総数を返します(返されません!)。 例:
Mysql> SELECT * FROM table LIMIT 0, 50;
Mysql> SELECT FOUND_ROWS();
結果は、予想どおり、「50」になります。 ただし、次のクエリはあまり期待しない結果を返します。
Mysql> SELECT * FROM table WHERE column > 1 LIMIT 50, 50;
Mysql> SELECT FOUND_ROWS();
最初のクエリが50行を返すという事実にもかかわらず、結果は「100」になります。 mySQLは正確に100行を調べる必要がありました。
さらに、SQL_CALC_FOUND_ROWSでUNIONを使用する場合、いくつかの特定の要件も表示されます。
- SQL_CALC_FOUND_ROWSディレクティブは、最初のクエリで1回だけ表示されます。
- UNION ALLを使用する場合にのみ、正確な結果が返されます。 ALLなしでUNIONを使用すると、一部の行が削除されますが、これはFOUND_ROWS()関数では考慮されません。
- グローバルLIMITを使用しない場合、SQL_CALC_FOUND_ROWSディレクティブは無視されません。
MySQLのドキュメントでは、この関数を使用して、クエリパラメータを満たすテーブル内の行の総数を計算することを明示的に推奨しています(たとえば、リストをページに分割する場合)。
Mysql> SELECT * FROM table WHERE column > 1 LIMIT 50, 50;
Mysql> SELECT COUNT(*) FROM table WHERE column > 1;
質問:FOUND_ROWS()関数を使用するオプションが本当に速い場合、なぜどこでも使用されないのですか? 私は両方のアプローチを比較することにしました。 このためにテーブルが作成されました:
CREATE TABLE `table_1` (
`id` int (10) unsigned NOT NULL auto_increment,
`column_1` varchar (32) default NULL ,
`column_2` varchar (32) default NULL ,
PRIMARY KEY (`id`),
KEY `column_2` (`column_2`)
) ENGINE=MyISAM AUTO_INCREMENT=1;
* This source code was highlighted with Source Code Highlighter .
テーブルには3つの列が含まれます。auto_incrementとPRIMARYキーを持つid数値フィールド。 インデックスのないテキスト列column_1とインデックスcolumn_2のあるテキスト列。 テーブルは、次のPHPスクリプトを使用して作成されました。
<?php
for ($i = 0; $i < 457128; $i ++) {
$db->insert ( 'table_1' , array ( 'column_1' , 'column_2' ), array ( md5(uniqid(rand(), true )), md5(uniqid(rand(), true )) ) );
}
?>
* This source code was highlighted with Source Code Highlighter .
$ dbは、mySQLを操作するためのPHP関数のラッパーです。 特に、データベースクエリの実行時間を測定します。
次に、次のタイプのサンプルが作成されました。
- PRIMARYキーによる選択を伴う「標準」クエリ
SELECT * FROM `table_1` WHERE `id` < $id_limit LIMIT 1000
SELECT COUNT(*) FROM `table_1` WHERE `id` < $id_limit
- PRIMARYキーによる選択を伴うSQL_CALC_FOUND_ROWS:
SELECT SQL_CALC_FOUND_ROWS * FROM `table_1` WHERE `id` < $id_limit LIMIT 1000
SELECT FOUND_ROWS() AS count
- 列なしの選択による「標準」クエリ
SELECT * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit%' LIMIT 1000
SELECT COUNT(*) FROM `table_1` WHERE `column_1` LIKE '$column_1_limit%'
- SQL_CALC_FOUND_ROWSと列選択WITHOUT KEY
SELECT SQL_CALC_FOUND_ROWS * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit%' LIMIT 1000
SELECT FOUND_ROWS() AS count
- KEYを使用したVARCHAR列によるサンプリングを伴う「標準」クエリ
SELECT * FROM `table_1` WHERE `column_2` LIKE '$column_2_limit%' LIMIT 1000
SELECT COUNT(*) FROM `table_1` WHERE `column_2` LIKE '$column_2_limit%'
- SQL_CALC_FOUND_ROWSと列選択WITHOUT KEY
SELECT SQL_CALC_FOUND_ROWS * FROM `table_1` WHERE `column_2` LIKE '$column_2_limit%' LIMIT 1000
SELECT FOUND_ROWS() AS count
- 両方の列を選択した「標準」クエリ
SELECT * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_2%' AND `column_2` LIKE '$column_2_limit_2%' LIMIT 1000
SELECT COUNT(*) FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_2%' AND `column_2` LIKE '$column_2_limit_2%'
- 両方の列を選択したSQL_CALC_FOUND_ROWS
SELECT SQL_CALC_FOUND_ROWS * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_2%' AND `column_2` LIKE '$column_2_limit_2%' LIMIT 1000
SELECT FOUND_ROWS() AS count
- 両方の列でサンプリングした「標準」クエリ+ PRIMARY
SELECT * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_3%' AND `column_2` LIKE '$column_2_limit_3%' AND `id` < $id_limit_2 LIMIT 1000
SELECT COUNT(*) FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_3%' AND `column_2` LIKE '$column_2_limit_3%' AND `id` < $id_limit_2
- SQL_CALC_FOUND_ROWS、両方の列を選択+ PRIMARY
SELECT SQL_CALC_FOUND_ROWS * FROM `table_1` WHERE `column_1` LIKE '$column_1_limit_3%' AND `column_2` LIKE '$column_2_limit_3%' AND `id` < $id_limit_2 LIMIT 1000
SELECT FOUND_ROWS() AS count
各クエリは異なるパラメータで10回実行されましたが、1回の反復で同じタイプの「標準」クエリとSQL_CALC_FOUND_ROWSクエリのパラメータは同じです。 すなわち:
for ($i = 0; $i < 10; $i ++) {
$id_limit = rand(15000, 20000);
$id_limit_2 = rand(15000, 20000);
$column_1_limit = rand(1, 9);
$column_2_limit = rand(1, 9);
$column_1_limit_2 = rand(10, 20);
$column_2_limit_2 = rand(10, 20);
$column_1_limit_3 = rand(20, 30);
$column_2_limit_3 = rand(20, 30);
// –
}
* This source code was highlighted with Source Code Highlighter .
テスト結果は図で見ることができます:
mySQLのドキュメントにあるように、すべてが明確ではないことがわかりました。 インデックス付きの列によるサンプリングを使用する場合、「クラシック」スキームは間違いなく高速です。 インデックスのない列と混合クエリを使用する場合、FOUND_ROWS()関数は高速になりますが、そのゲインは非常に小さくなります。
したがって、アプローチの選択は、特定の各ケースの問題です。 パフォーマンスがほとんど重要でなく、コードの記述の利便性と速度が重要な場合、FOUND_ROWS()+ SQL_CALC_FOUND_ROWSを使用することは非常に可能です。 それ以外の場合、明らかに、2つのクエリの古典的なスキームを使用することが望ましいです。
テストは2台のマシンで実行されました。
- Windows XP SP3、Intel Pentium Core 2 Duo E8300 @ 2.83 GHz、2 GB、mySQL 5.0.51a
- Ubuntu 8.04、AMD Opteron 2344 HEクアッドコア、4 GB、5.0.51a-3ubuntu5.4
このグラフは、Windows XPでの結果を示しています。 結果(サンプリング時間ではない)は、両方のシステムで同じです。