web-dev-qa-db-ja.com

(多数?)の値でのMySQL "IN"演算子のパフォーマンス

私は最近RedisとMongoDBを試していますが、MongoDBまたはRedisのいずれかにid'sの配列を格納する場合が多いようです。 MySQL[〜#〜] in [〜#〜]演算子について尋ねているので、この質問にはRedisを使い続けます。

IN演算子内に多数(300-3000)のid'sをリストすることは、次のようになります。

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

productscategoriesテーブルのような単純なものを想像してください一緒に参加して、特定のcategoryからproductsを取得します。上記の例では、Redisの特定のカテゴリ(category:4:product_ids)ID 4のカテゴリからすべての製品IDを返し、SELECT演算子内の上記のINクエリに配置します。

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

これは「依存する」状況ですか?または、具体的な「これは(許容されない)」、「速い」、「遅い」、またはLIMIT 25、またはそれは役に立ちませんか?

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

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

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

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

76

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

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

ただし、おそらくセットにギャップがあり、その時点で有効な値のリストを使用する方がよい場合があります(ギャップの数が比較的少ない場合を除き、使用できる場合:

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

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

33

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

私が抱えていた唯一の問題は、max_allowed_packetファイルからmy.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
20
jbaylina

任意の数の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);
11
Vladimir Jotov

INは問題なく、最適化されています。必ずインデックス付きフィールドで使用してください。大丈夫です。

機能的には次と同等です:

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

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

4
David Fells

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

最近解決したケースでは、2つのwhere節がありました。1つは2,50個のパラメーターで、もう1つは3,500個のパラメーターで、4,000万件のレコードのテーブルを照会しました。

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

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

2
yoyodunno