(大きい?)数の値に対するMySQLの「IN」演算子のパフォーマンス


93

私は最近RedisとMongoDBを試していますが、MongoDBまたはRedisのいずれかにIDの配列を格納する場合が多いようです。MySQL IN演算子について質問しているので、この質問についてはRedisを使用します。

IN演算子内に多数(300〜3000)のIDをリストすることは、どれほどパフォーマンスが高いのか疑問に思いました。これは次のようになります。

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)

通常は一緒に結合して特定のカテゴリから製品を取得する、製品カテゴリのテーブルのような単純なものを想像してみてください。上記の例では、Redis()の特定のカテゴリの下で、ID 4のカテゴリからすべての製品IDを返し、演算子内の上記のクエリに配置していることがわかります。category:4:product_idsSELECTIN

これはどのくらいのパフォーマンスですか?

これは「状況によって異なります」ですか?または、具体的な「これは(受け入れられない)」または「速い」または「遅い」LIMIT 25がありますか、それとも追加する必要がありますか、それとも役に立ちませんか?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25

または、Redisによって返される製品IDの配列をトリミングして25に制限し、クエリLIMIT内から3000ではなく25 IDのみをクエリに追加して25に追加する必要がありますか?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)

任意の提案/フィードバックは大歓迎です!


あなたが何を求めているのか正確にはわかりませんか?「idIN(1,2,3、... 3000))」を使用した1つのクエリは、「id = value」を使用した3000クエリよりも高速です。ただし、「category = 4」を使用した結合は、上記の両方よりも高速になります。
ロニス2010

そうです、商品は複数のカテゴリに属する​​ことができるので、「カテゴリ= 4」を実行することはできません。Redisを使用して、特定のカテゴリに属する​​製品のすべてのIDを保存し、それをクエリします。本当の問題は、のid IN (1,2,3 ... 3000)JOINテーブルと比較してどのようにパフォーマンスするかということだと思いますproducts_categories。それともあなたが言っていたのですか?
Michael van Rooijen 2010

ただのMySQLにそのバグから気をつけてstackoverflow.com/questions/3417074/...
イタイMoav -Malimovka

もちろん、これがインデックス付き行を取得する他の方法ほど効率的ではない理由はありません。それは、データベースの作成者がそれをテストして最適化したかどうかに依存します。計算の複雑さの観点から、最悪の場合、IN句に対してO(n log N)ソートを実行し(アルゴリズムによっては、表示されているようなソート済みリストでは線形になる場合もあります)、次に線形交差/ルックアップを実行します。 。
jberryman 2017

回答:


39

一般的に、INリストが大きくなりすぎると(通常、100以下の領域にある「大きすぎる」という不明確な値の場合)、結合を使用する方が効率的になり、必要に応じて一時テーブルを作成します。数字を保持します。

数値が密な集合である場合(ギャップがない-サンプルデータが示唆している)、を使用するとさらにうまくいくことができますWHERE id BETWEEN 300 AND 3000

ただし、おそらくセットにギャップがあるため、結局のところ、有効な値のリストを使用する方がよい場合があります(ギャップの数が比較的少ない場合を除き、次のように使用できます。

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836

またはギャップが何であれ。


46
「結合を使用して一時テーブルを作成する」の例を教えてください。
ジェイク

データセットがインターフェイス(複数選択要素)からのものであり、選択されたデータにギャップがあり、このギャップがシーケンシャルギャップではない場合(欠落:457、490、658、..)、機能しAND id NOT BETWEEN XXX AND XXXないため、次のようにすることをお勧めします。(x = 1 OR x = 2 OR x = 3 ... OR x = 99)@DavidFellsが書いたのと同等のものに固執します。
deepcell

私の経験では、eコマースWebサイトでの作業では、最大50の無関係な製品IDの検索結果を表示する必要があります。「1.50の個別のクエリ」では、「2。「IN」に多くの値を持つ1つのクエリ」よりも良い結果が得られました。句""。現時点では、クエリ#2が監視システムで常に低速クエリとして表示されるのに対し、#1は実行量に関係なく表示されないことを除いて、それを証明する方法はありません。何百万人も...誰かが同じ経験をしていますか?(おそらく、より良いキャッシュに関連付けるか、他のクエリをクエリ間でインターレースできるようにすることができます...)
ChaimKlar19年

24

私はいくつかのテストを行ってきましたが、David Fellsが彼の回答述べているように、それは非常によく最適化されています。参考までに、1,000,000個のレジスターを含むInnoDBテーブルを作成し、500,000個の乱数を使用して「IN」演算子を使用して選択を実行しました。MACではわずか2.5秒しかかかりません。偶数レジスタのみを選択するのに0.5秒かかります。

私が抱えていた唯一の問題はmax_allowed_packetmy.cnfファイルからパラメータを増やす必要があることです。そうでない場合は、不思議な「MYSQLがなくなりました」というエラーが生成されます。

テストを行うために使用するPHPコードは次のとおりです。

$NROWS =1000000;
$SELECTED = 50;
$NROWSINSERT =15000;

$dsn="mysql:host=localhost;port=8889;dbname=testschema";
$pdo = new PDO($dsn, "root", "root");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec("drop table if exists `uniclau`.`testtable`");
$pdo->exec("CREATE  TABLE `testtable` (
        `id` INT NOT NULL ,
        `text` VARCHAR(45) NULL ,
        PRIMARY KEY (`id`) )");

$before = microtime(true);

$Values='';
$SelValues='(';
$c=0;
for ($i=0; $i<$NROWS; $i++) {
    $r = rand(0,99);
    if ($c>0) $Values .= ",";
    $Values .= "( $i , 'This is value $i and r= $r')";
    if ($r<$SELECTED) {
        if ($SelValues!="(") $SelValues .= ",";
        $SelValues .= $i;
    }
    $c++;

    if (($c==100)||(($i==$NROWS-1)&&($c>0))) {
        $pdo->exec("INSERT INTO `testtable` VALUES $Values");
        $Values = "";
        $c=0;
    }
}
$SelValues .=')';
echo "<br>";


$after = microtime(true);
echo "Insert execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);  
$sql = "SELECT count(*) FROM `testtable` WHERE id IN $SelValues";
$result = $pdo->prepare($sql);  
$after = microtime(true);
echo "Prepare execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);

$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Random selection = $c Time execution time =" . ($after-$before) . "s<br>";



$before = microtime(true);

$sql = "SELECT count(*) FROM `testtable` WHERE id %2 = 1";
$result = $pdo->prepare($sql);
$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Pairs = $c Exdcution time=" . ($after-$before) . "s<br>";

そして結果:

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s

他の人のために、2013年後半のMBPのVirtualBox(CentOS)で実行されているi7を追加します。出力の3行目(質問に関連する行)は次のとおりです。ランダム選択= 500744実行時間= 53.458173036575s ..アプリケーションによっては、53秒が許容される場合があります。私の用途では、そうではありません。また、偶数のテストは、モジュロ演算子(%)と等号演算子(=)を使用するため、当面の質問には関係がないことに注意してくださいIN()
rinogo 2015年

これは、この機能のない同様のクエリを使用して、IN演算子を使用してクエリを比較する方法であるため関連性があります。お使いのマシンがswapipngであるか、別の仮想マシンで動作しているため、ダウンロード時間が長くなる可能性があります。
jbaylina 2015年

14

任意の数のIDを配置し、ネストされたクエリを実行できる一時テーブルを作成できます。例:

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));

選択します:

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);

6
サブクエリを使用する代わりに、一時テーブルに参加することをお勧めします
scharette 2017

3
@loopkinは、結合とサブクエリでこれをどのように行うかを説明できますか?
ジェフソロモン

3
@jeffSolomon SELECT products.id、name、price FROM products JOIN tmp_IDs on products.id = tmp_IDs.ID;
scharette 2017

この答え!私は長いレジストリのために非常に非常に速く、探していたものである
ダミアン・ラファエルLattenero

どうもありがとう、男。それは信じられないほど速く動作します。
mrHalfer

4

INレコードの大きなリストで大きなパラメータセットを使用すると、実際には時間がかかります。

最近解決した場合、2つのwhere句がありました。1つは2,50のパラメーターを持ち、もう1つは3,500のパラメーターを持ち、4,000万レコードのテーブルをクエリします。

私のクエリは、標準を使用して5分かかりましたWHERE IN。代わりに、INステートメントのサブクエリを使用する(パラメーターを独自のインデックス付きテーブルに配置する)ことで、クエリを2秒に短縮しました。

私の経験では、MySQLとOracleの両方で働いていました。


1
「代わりに、INステートメントのサブクエリを使用する(パラメーターを独自のインデックス付きテーブルに配置する)」という点については、私はあなたの意見を理解できませんでした。「WHEREIDIN(1,2,3)」を使用する代わりに、「WHERE ID IN(SELECT id FROMxxx)」を使用する必要があるということですか?
Istiyakテーラー

4

INうまく、よく最適化されています。インデックス付きフィールドで使用することを確認してください。問題はありません。

機能的には次のものと同等です。

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)

DBエンジンに関する限り。


1
そうでもないです。INクロースを使用してDBから5kレコードをフェッチします。INクロースにはPKのリストが含まれているため、関連する列にインデックスが付けられ、一意であることが保証されます。EXPLAINによると、全表スキャンは、「fifo-queue-alike」スタイルでPKルックアップを使用する代わりに実行されます。
Antoniossss 2016

MySQLでは、それらが「機能的に同等」であるとは思いません。INパフォーマンスを向上させるために最適化を使用します。
JoshuaPinter19年

1
Josh、答えは2011年からでした-それ以来状況は変わったと思いますが、当時、INは完全に一連のORステートメントに変換されていました。
DavidFells19年

1
この答えは正しくありません。高性能MySQLから:MySQLではそうではありません。これはIN()リストの値をソートし、高速バイナリ検索を使用して値がリストにあるかどうかを確認します。これは、リストのサイズではO(log n)ですが、同等の一連のOR句はリストのサイズではO(n)です(つまり、大きなリストの場合ははるかに遅くなります)。
バート

バート-はい。この回答は廃止されました。編集を提案してください。
DavidFells19年

-2

IN演算子に多くの値を指定する場合、最初に演算子を並べ替えて重複を削除する必要があります。少なくとも私はそれを疑う。したがって、ソートにはN log N時間がかかるため、あまり多くの値を指定するのは適切ではありません。

私の経験では、値のセットを小さなサブセットにスライスし、アプリケーション内のすべてのクエリの結果を組み合わせると、最高のパフォーマンスが得られることが証明されました。別のデータベース(Pervasive)で経験を収集したことは認めますが、同じことがすべてのエンジンに当てはまる可能性があります。セットあたりの値の数は500〜1000でした。多かれ少なかれかなり遅かった。


これは7年後のことですが、この回答の問題は、知識に基づいた推測に基づくコメントであるということです。
Giacomo19 6819
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.