私は最近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)
productsとcategoriesテーブルのような単純なものを想像してください一緒に参加して、特定の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)
提案/フィードバックは大歓迎です!
一般的に、IN
リストが大きすぎる場合(通常、100以下の領域にある「大きすぎる」という不明確な値の場合)、結合を使用して、必要に応じて、一時テーブルに番号を保持します。
数値が密集している(ギャップがない-サンプルデータが示唆する)場合、WHERE id BETWEEN 300 AND 3000
を使用するとさらに改善できます。
ただし、おそらくセットにギャップがあり、その時点で有効な値のリストを使用する方がよい場合があります(ギャップの数が比較的少ない場合を除き、使用できる場合:
WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836
またはギャップが何であれ。
私はいくつかのテストを行ってきましたが、 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
任意の数の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);
IN
は問題なく、最適化されています。必ずインデックス付きフィールドで使用してください。大丈夫です。
機能的には次と同等です:
(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
DBエンジンに関する限り。
IN
をレコードの大きなリストに設定された大きなパラメーターで使用すると、実際には時間がかかります。
最近解決したケースでは、2つのwhere節がありました。1つは2,50個のパラメーターで、もう1つは3,500個のパラメーターで、4,000万件のレコードのテーブルを照会しました。
私のクエリは、標準のWHERE IN
を使用して5分かかりました。代わりに[〜#〜] in [〜#〜]ステートメントにサブクエリを使用することにより(独自のインデックス付きテーブルにパラメータを設定する)、クエリを2秒に短縮しました。
私の経験では、MySQLとOracleの両方で働いていました。