MySQL“IN”运算符性能上(大?)个值

时间:2010-12-22 23:37:19

标签: mysql sql performance operators

我最近一直在尝试使用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(category:4:product_ids)中的给定类别下,我返回ID为4的类别中的所有产品ID,并将它们放在SELECT内的IN查询中1}} operator。

这有多高效?

这是“依赖”的情况吗?或者是否有具体的“这是(不可接受的)或”快速“或”慢“或者我应该添加LIMIT 25,还是没有帮助?

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

或者我应该修剪Redis返回的产品ID数组,将其限制为25,只添加25个id到查询而不是3000和LIMIT - 从查询中添加到25?

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

非常感谢任何建议/反馈!

6 个答案:

答案 0 :(得分:31)

一般来说,如果IN列表太大(对于某些定义错误的'太大'值通常在100或更小的区域内),使用连接会变得更有效率,如果需要,可以创建一个临时表来保存数字。

如果数字是密集的(没有间隙 - 样本数据表明),那么你可以用WHERE id BETWEEN 300 AND 3000做得更好。

但是,假设集合中存在间隙,此时最好不要使用有效值列表(除非间隙数量相对较少,在这种情况下可以使用:

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

或者差距是什么。

答案 1 :(得分:19)

我一直在做一些测试,as David Fells says in his answer,它已经做了很好的优化。作为参考,我创建了一个包含1,000,000个寄存器的InnoDB表,并使用“IN”运算符进行选择,其中包含500,000个随机数,在我的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

答案 2 :(得分:10)

您可以创建一个临时表,您可以在其中放置任意数量的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);

答案 3 :(得分:4)

IN很好,并且经过了优化。确保你在索引字段上使用它,你没事。

它在功能上等同于:

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

就数据库引擎而言。

答案 4 :(得分:2)

在大型记录列表中使用带有大参数集的IN实际上会很慢。

在我最近解决的情况下,我有两个where子句,一个有2,50个参数,另一个有3,500个参数,查询一个包含4千万条记录的表。

我的查询使用标准WHERE IN花了5分钟。通过使用 IN 语句的子查询(将参数放在他们自己的索引表中),我将查询缩短到两秒钟。

根据我的经验,为MySQL和Oracle工作。

答案 5 :(得分:-2)

当您为IN运算符提供许多值时,首先必须对其进行排序以删除重复项。至少我怀疑。因此,提供太多的值并不好,因为排序需要N log N时间。

我的经验证明,将值集切割为较小的子集并将应用程序中所有查询的结果组合在一起可以获得最佳性能。我承认我在不同的数据库(Pervasive)上积累了经验,但同样适用于所有引擎。我的每组值为500-1000。或多或少明显变慢。